Im Folgenenden soll gezeigt werden, wie es mit relativ einfachen Mitteln (das sieht jetzt viel aus, ist aber eigentlich ganz übersichtlich) möglich ist, eine an ein ListView gebundene Collection zu filtern, sortieren und/oder zu gruppieren. Zuerst muss eine neue WPF-Applikation erstellt werden: Als Basis für die folgenden Beispiele wird nachfolgende Kontakt-Klasse und Enum benutzt, welche Informationen über eine Person beinhaltet. Eine Liste dieser Kontaktdaten wird dann geordnet. Imports System.Collections.ObjectModel Imports System.ComponentModel Public Enum KontaktTyp Privat Geschäftlich Sonstige End Enum Public Class Kontakt Private mTyp As KontaktTyp = KontaktTyp.Sonstige Private mName As String = String.Empty Private mVorname As String = String.Empty Private mGeburtsDatum As Date Public Property Typ() As KontaktTyp Get Return mTyp End Get Set(ByVal value As KontaktTyp) mTyp = value End Set End Property Public Property Name() As String Get Return mName End Get Set(ByVal value As String) mName = value End Set End Property Public Property Vorname() As String Get Return mVorname End Get Set(ByVal value As String) mVorname = value End Set End Property Public Property Geburtsdatum() As Date Get Return mGeburtsDatum End Get Set(ByVal value As Date) mGeburtsDatum = value End Set End Property Public Sub New() End Sub Public Sub New(ByVal vorname As String, ByVal name As String, _ ByVal geburtsdatum As Date, ByVal typ As KontaktTyp) mVorname = vorname mName = name mGeburtsDatum = geburtsdatum mTyp = typ End Sub Public Overrides Function ToString() As String Return String.Format("{0}, {1}", mName, mVorname) End Function End Class Das Fenster für die Darstellung sieht dann so aus. <Window x:Class="Window1" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"; xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"; Title="ListView filtern, sortieren und gruppieren in WPF" Height="431" Width="681"> <Grid> <Grid.RowDefinitions> <RowDefinition Height="1*" /> <RowDefinition Height="130" /> <RowDefinition Height="35" /> </Grid.RowDefinitions> <Grid.ColumnDefinitions> <ColumnDefinition Width="200" /> <ColumnDefinition Width="200" /> <ColumnDefinition Width="200" /> <ColumnDefinition Width="1*" /> </Grid.ColumnDefinitions> <ListView ItemsSource="{Binding Kontakte}" Margin="12" Name="ListView1" Grid.ColumnSpan="4"> <ListView.View> <GridView> <GridViewColumn Width="180" Header="Name" DisplayMemberBinding="{Binding Name}"/> <GridViewColumn Width="140" Header="Vorname" DisplayMemberBinding="{Binding Vorname}" /> <GridViewColumn Width="140" Header="Typ" DisplayMemberBinding="{Binding Typ}" /> <GridViewColumn Width="140" Header="Geburtsdatum" DisplayMemberBinding="{Binding Geburtsdatum, StringFormat='{}{0:dd.MM.yyyy}'}" /> </GridView> </ListView.View> </ListView> </Grid> </Window> Der code-behind: Imports System.Collections.ObjectModel Imports System.ComponentModel Class Window1 Private mKontakte As New ObservableCollection(Of Kontakt) Public Property Kontakte() As ObservableCollection(Of Kontakt) Get Return mKontakte End Get Set(ByVal value As ObservableCollection(Of Kontakt)) mKontakte = value End Set End Property Public Sub New() InitializeComponent() ListView1.DataContext = Me ' Beispiel Liste mKontakte.Add(New Kontakt("Bernd", "Schuster", #1/8/1960#, KontaktTyp.Geschäftlich)) mKontakte.Add(New Kontakt("Lisa", "Müller", #1/1/1965#, KontaktTyp.Privat)) mKontakte.Add(New Kontakt("Stefan", "Meyer", #1/2/1970#, KontaktTyp.Sonstige)) mKontakte.Add(New Kontakt("Klaus", "Schulz", #1/3/1975#, KontaktTyp.Geschäftlich)) mKontakte.Add(New Kontakt("Martin", "Schmidt", #1/4/1980#, KontaktTyp.Sonstige)) mKontakte.Add(New Kontakt("Christina", "Fischer", #1/5/1985#, KontaktTyp.Geschäftlich)) mKontakte.Add(New Kontakt("Kurt", "Wagner", #1/6/1990#, KontaktTyp.Privat)) mKontakte.Add(New Kontakt("Anne", "Bauer", #1/7/1995#, KontaktTyp.Sonstige)) mKontakte.Add(New Kontakt("Kai", "Krüger", #1/8/2000#, KontaktTyp.Privat)) End Sub End Class Das Ganze sollte jetzt so aussehen: ListCollectionView Das Prinzip in WPF ist es für das ListView sozusagen eine extra Ansicht zu erstellen, die nur im Standardfall der echten Auflistung entspricht. Man kann diese Ansicht beeinflussen, ohne damit die echte Auflistung zu ändern. An die ListCollectionView der Kontakte-Liste, mit der man das umsetzen kann, kommt man mit folgendem Code: Dim view As ListCollectionView = _ CType(CollectionViewSource.GetDefaultView(mKontakte), _ ListCollectionView) Um die Ansicht manuell zu aktualisieren, kann man .Refresh() aufrufen. Dies wird nicht benötigt, wenn die Auflistung irgendwie geändert wird. Der Refresh-Vorgang wird dann automatisch ausgelöst. Wenn der Typ der Auflistung INotifyPropertyChanged implementiert, führt das Auslösen des PropertyChanged-Events auch zu einem Refresh. view.Refresh() Das ist die Aufgabe für den Update-Button, der jetzt hinzugefügt wird. <Grid> '... <Button Grid.Column="2" Grid.Row="2" HorizontalAlignment="Right" Margin="0,6,11,0" Name="btnUpdate" Width="75" Height="23" VerticalAlignment="Top">Update </Button> </Grid> Private Sub btnUpdate_Click(ByVal sender As Object, _ ByVal e As RoutedEventArgs) Handles btnUpdate.Click Dim view As ListCollectionView = _ CType(CollectionViewSource.GetDefaultView(mKontakte), _ ListCollectionView) view.Refresh() End Sub Ich benutze in diesem Beispiel eine ObservableCollection, da diese Vorteile im Gegensatz zur BindingList hat, bzw. deren CollectionView. Die BindingListCollectionView hat genauso wie die ItemCollection des ListView keine CustomSort Eigenschaft. Ansonsten sind BindingList und ObservableCollection im Verhalten gleich was das DataBinding angeht. Filtern Die Filter-Eigenschaft der ListCollectionView ist vom Typ System.Predicate(Of Object) und diese kann mit einer Funktion bedient werden. Public Function Filter(ByVal itemObj As Object) As Boolean ' ... End Function itemObj ist in diesem Fall der Kontakt, welcher überprüft wird. Es wird im Fall einer Aktualisierung der Ansicht für jeden Item diese Funktionaufgerufen. Der Boolean Wert, der zurück gegeben wird, zeigt ganz einfach ob der Item angezeigt wird oder nicht. Man könnte diese Funktion nun direkt in die Window-Klasse schreiben, aber da die Filterung von einigen Optionen abhängig gemacht werden und am Ende kein Spagetti-Code rauskommen soll, wird das alles in eine Klasse gepackt. Beim Filtern soll es möglich sein nach einem Keyword zu suchen, nur einen bestimmten oder alle KontaktTypen anzuzeigen und eine Grenze für das Geburtsdatum zu setzen. Dazu einfach folgende Filter-Klasse hinzufügen. Public Class KontaktFilter Private mSuchwort As String = String.Empty Private mNurJuengerAls As Date Private mFilterTypen As Boolean = False Private mErlaubterTyp As KontaktTyp = KontaktTyp.Sonstige Public Property Suchwort() As String Get Return mSuchwort End Get Set(ByVal value As String) mSuchwort = value End Set End Property Public Property NurJuengerAls() As Date Get Return mNurJuengerAls End Get Set(ByVal value As Date) mNurJuengerAls = value End Set End Property Public Property FilterTypen() As Boolean Get Return mFilterTypen End Get Set(ByVal value As Boolean) mFilterTypen = value End Set End Property Public Property ErlaubterTyp() As KontaktTyp Get Return mErlaubterTyp End Get Set(ByVal value As KontaktTyp) mErlaubterTyp = value End Set End Property Public Function Filter(ByVal itemObj As Object) As Boolean Dim item As Kontakt = TryCast(itemObj, Kontakt) If item IsNot Nothing Then ' Wenn eine Bedingung nicht erfüllt wird, ist der Item raus If mSuchwort <> String.Empty Then ' Nach Keyword suchen If Not (item.Vorname.ToLower.Contains(mSuchwort.ToLower) Or _ item.Name.ToLower.Contains(mSuchwort.ToLower)) Then Return False End If End If If mFilterTypen Then ' Nur bestimmten Typ erlauben If mErlaubterTyp <> item.Typ Then Return False End If End If ' Geburtstagsgrenze If item.Geburtsdatum < mNurJuengerAls Then Return False End If ' und so weiter ... Return True Else Return False End If End Function End Class Die Window-Klasse wird mit folgendem Code erweitert. Class Window1 Private mFilter As New KontaktFilter ' Brauchen wir für das DataBinding Public ReadOnly Property Filter() As KontaktFilter Get Return mFilter End Get End Property ... Public Sub New() txtNurJuengerAls.DataContext = Me txtSuchwort.DataContext = Me cmbTyp.ItemsSource = New KontaktTyp() {KontaktTyp.Geschäftlich, _ KontaktTyp.Privat, _ KontaktTyp.Sonstige} cmbTyp.DataContext = Me chkFilterTypen.DataContext = Me ... Dim view As ListCollectionView = _ CType(CollectionViewSource.GetDefaultView(mKontakte), _ ListCollectionView) ' Hier wird der zu benutzende Filter gesetzt; ' jede Änderung im Filter wird beim Refresh übernommen, ' da die Funktion neu aufgerufen wird view.Filter = New Predicate(Of Object)(AddressOf mFilter.Filter) End Sub End Class Window-XAML <Window x:Class="Window1" > <Grid> '... <GroupBox Grid.Row="1" Header="Filter" Name="GroupBox1" Margin="5,0" > <Grid> <TextBox Height="23" Margin="47,13,11,0" Name="txtNurJuengerAls" VerticalAlignment="Top" Text="{Binding Filter.NurJuengerAls,Mode=TwoWay,StringFormat='{}{0:dd.MM.yyyy}'}" /> <TextBlock Height="21" Margin="7,15,0,0" Name="TextBlock1" VerticalAlignment="Top" HorizontalAlignment="Left" Width="40" Text="Geb." /> <TextBox Margin="47,42,11,0" Name="txtSuchwort" Height="23.04" VerticalAlignment="Top" Text="{Binding Filter.Suchwort,Mode=TwoWay}" /> <TextBlock HorizontalAlignment="Left" Margin="7,44,0,0" Name="TextBlock2" Text="Name" Width="40" Height="21.04" VerticalAlignment="Top" /> <ComboBox Height="23" Margin="47,71.04,11,0" Name="cmbTyp" VerticalAlignment="Top" SelectedItem="{Binding Filter.ErlaubterTyp,Mode=TwoWay}" IsEnabled="{Binding IsChecked,ElementName=chkFilterTypen}"/> <CheckBox IsChecked="{Binding Filter.FilterTypen,Mode=TwoWay}" IsThreeState="False" Height="16" Margin="7,0,0,17" Name="chkFilterTypen" VerticalAlignment="Bottom" HorizontalAlignment="Left" Width="41">Typ</CheckBox> </Grid> </GroupBox> </Grid> </Window> Jetzt einfach mal verschiedene Keywords (Buchstaben oder Teile von Namen) oder die anderen Optionen ausprobieren und danach "Update" klicken. Sortieren Um sortieren zu können muss man eine Klasse erstellen, welche das Interface IComparer implementiert. Damit werden dann die verschiedenen Items verglichen und geordnet. An sich verläuft alles wie bei einem normalen Comparer. In dieser Klasse gibt es noch die Option der Richtung der Sortierung und der Eigenschaft des Kontakts nach dem sortiert wird. Imports System.ComponentModel Public Enum KontaktEigenschaft Name Vorname Typ Geburtsdatum End Enum Public Class KontaktSortierer Implements IComparer Private mSortiertNach As KontaktEigenschaft = KontaktEigenschaft.Name Private mSortierRichtung As ListSortDirection = ListSortDirection.Ascending Public Property SortiertNach() As KontaktEigenschaft Get Return mSortiertNach End Get Set(ByVal value As KontaktEigenschaft) mSortiertNach = value End Set End Property Public Property SortierRichtung() As ListSortDirection Get Return mSortierRichtung End Get Set(ByVal value As ListSortDirection) mSortierRichtung = value End Set End Property Public Function Compare(ByVal x As Object, ByVal y As Object) As Integer _ Implements System.Collections.IComparer.Compare Dim itemX As Kontakt = TryCast(x, Kontakt) Dim itemY As Kontakt = TryCast(y, Kontakt) If itemX IsNot Nothing AndAlso itemY IsNot Nothing Then Dim sortMultiplier As Integer = 0 If mSortierRichtung = ListSortDirection.Ascending Then : sortMultiplier = 1 Else : sortMultiplier = -1 End If Select Case mSortiertNach Case KontaktEigenschaft.Name Return String.Compare(itemX.Name, itemY.Name) * sortMultiplier Case KontaktEigenschaft.Vorname Return String.Compare(itemX.Vorname, itemY.Vorname) * sortMultiplier Case KontaktEigenschaft.Typ If CInt(itemX.Typ) > CInt(itemY.Typ) Then Return 1 * sortMultiplier ElseIf CInt(itemX.Typ) < CInt(itemY.Typ) Then Return -1 * sortMultiplier Else Return 0 End If Case KontaktEigenschaft.Geburtsdatum Return Date.Compare(itemX.Geburtsdatum, itemY.Geburtsdatum) * sortMultiplier End Select End If Return -1 End Function End Class Zur Window-Klasse kommt folgendes hinzu: Class Window1 Private mSortierung As New KontaktSortierer ' Brauchen wir für das DataBinding Public ReadOnly Property Sortierung() As KontaktSortierer Get Return mSortierung End Get End Property ' ... Public Sub New() cmbSortiertNach.ItemsSource = _ New KontaktEigenschaft() {KontaktEigenschaft.Name, _ KontaktEigenschaft.Vorname, _ KontaktEigenschaft.Typ, _ KontaktEigenschaft.Geburtsdatum} cmbSortiertNach.DataContext = Me cmbSortierRichtung.ItemsSource = _ New ListSortDirection() {ListSortDirection.Ascending, _ ListSortDirection.Descending} cmbSortierRichtung.DataContext = Me ' ... Dim view As ListCollectionView = _ CType(CollectionViewSource.GetDefaultView(mKontakte), _ ListCollectionView) view.Filter = New Predicate(Of Object)(AddressOf mFilter.Filter) ' Die Sortierung hinzufügen view.CustomSort = mSortierung End Sub End Class Window-XAML <Window x:Class="Window1" > <Grid> '... <GroupBox Header="Sortierung" Name="GroupBox2" Grid.Column="1" Grid.Row="1" Margin="5,0"> <Grid> <ComboBox SelectedItem="{Binding Sortierung.SortiertNach,Mode=TwoWay}" Height="23" Margin="15,13,15,0" Name="cmbSortiertNach" VerticalAlignment="Top" /> <ComboBox SelectedItem="{Binding Sortierung.SortierRichtung,Mode=TwoWay}" Margin="15,42,15,0" Name="cmbSortierRichtung" Height="23.04" VerticalAlignment="Top" /> </Grid> </GroupBox> </Grid> </Window> Gruppieren Das Gruppieren ist erstmal das Leichteste von allem. Es reicht eigentlich folgendes der Ansicht hinzuzufügen. view.GroupDescriptions.Add(New PropertyGroupDescription("Typ")) Damit würden die Items nach der Eigenschaft Typ gruppiert werden. Man kann es aber auch noch individueller gestalten, z.B. bei Eigenschaften, wo die Auswahlmöglichkeit nicht so begrenzt ist. Würde man nach der Eigenschaft Geburtsdatum gruppieren, würde in diesem Beispiel jeder Kontakt seine eigene Gruppe bekommen. Das ist aber nicht der tiefere Sinn der Gruppierung, also kann man noch einen IValueConverter übergeben. Mit diesem können die Items individuell gruppiert werden. Wenn kein Eigenschaftenname übergeben wird, wird dem Converter das gesamte Item-Objekt übergeben. Bei einem übergebenen Namen wird das Objekt der Eigenschaft übergeben, in dem folgendem Fall ein Datum. Die Gruppierer-Klasse kann natürlich genauso individuell gestaltet werden wie der Filter und der Sortierer. In diesem Beispiel werden die Geburtstage in Dekaden gruppiert. Public Class GeburtsdekadenGruppierer Implements IValueConverter Public Function Convert(ByVal value As Object, _ ByVal targetType As System.Type, _ ByVal parameter As Object, _ ByVal culture As System.Globalization.CultureInfo) As Object _ Implements System.Windows.Data.IValueConverter.Convert If value.GetType Is GetType(Date) Then Dim d As Date = CDate(value) If d > #1/1/1950# And d < #1/1/1959# Then Return "1950 - 1959" ElseIf d > #1/1/1960# And d < #1/1/1969# Then Return "1960 - 1969" ElseIf d > #1/1/1970# And d < #1/1/1979# Then Return "1970 - 1979" ElseIf d > #1/1/1980# And d < #1/1/1989# Then Return "1980 - 1989" ElseIf d > #1/1/1990# And d < #1/1/1999# Then Return "1990 - 1999" ElseIf d > #1/1/2000# And d < #1/1/2009# Then Return "2000 - 2009" Else Return "2010 - Heute" End If Else Return "Sonstige" End If End Function Public Function ConvertBack(ByVal value As Object, _ ByVal targetType As System.Type, _ ByVal parameter As Object, _ ByVal culture As System.Globalization.CultureInfo) As Object _ Implements System.Windows.Data.IValueConverter.ConvertBack Throw New NotSupportedException("Rückwärtige Konvertierung wird nicht unterstützt.") End Function End Class Um das Ganze anzuwenden, wird der Window-Klasse folgendes hinzugefügt: Class Window1 Private mGruppierungAktiviert As Boolean = False Private mGruppierung As KontaktEigenschaft = KontaktEigenschaft.Typ ' Brauchen wir für das DataBinding Public Property GruppierungAktiviert() As Boolean Get Return mGruppierungAktiviert End Get Set(ByVal value As Boolean) mGruppierungAktiviert = value End Set End Property Public Property Gruppierung() As KontaktEigenschaft Get Return mGruppierung End Get Set(ByVal value As KontaktEigenschaft) mGruppierung = value End Set End Property ' ... Public Sub New() chkGrpAktiviert.DataContext = Me cmbGruppierung.ItemsSource = _ New KontaktEigenschaft() {KontaktEigenschaft.Typ, _ KontaktEigenschaft.Geburtsdatum} cmbGruppierung.DataContext = Me ' ... End Sub Private Sub btnUpdate_Click(ByVal sender As Object, _ ByVal e As RoutedEventArgs) Handles btnUpdate.Click Dim view As ListCollectionView = _ CType(CollectionViewSource.GetDefaultView(mKontakte), _ ListCollectionView) view.GroupDescriptions.Clear() If mGruppierungAktiviert Then If mGruppierung = KontaktEigenschaft.Typ Then view.GroupDescriptions.Add(New PropertyGroupDescription("Typ")) ElseIf mGruppierung = KontaktEigenschaft.Geburtsdatum Then view.GroupDescriptions.Add(New _ PropertyGroupDescription("Geburtsdatum", New GeburtsdekadenGruppierer)) End If End If view.Refresh() End Sub End Class Window-XAML <Window x:Class="Window1" > <Grid> '... <GroupBox Header="Gruppierung" Margin="5,0" Name="GroupBox3" Grid.Column="2" Grid.Row="1"> <Grid> <CheckBox IsChecked="{Binding GruppierungAktiviert,Mode=TwoWay}" Margin="17,15,23,0" Name="chkGrpAktiviert" Height="16.04" VerticalAlignment="Top">Aktiviert</CheckBox> <ComboBox SelectedItem="{Binding Gruppierung,Mode=TwoWay}" Margin="17,44,23,0" Name="cmbGruppierung" Height="23.04" VerticalAlignment="Top" /> </Grid> </GroupBox> </Grid> </Window> Jetzt werden die Items zwar schon gruppiert, aber das ist noch nicht so richtig erkennbar. Dafür bietet WPF auch eine Lösung mit der Bestimmung eines eigenen Styles für ein GroupItem. In diesem Style werden die Items dann in Expandern gruppiert. Dafür dem ListView den folgenden GroupStyle hinzufügen. <Window x:Class="Window1" > <Grid> '... <ListView ItemsSource="{Binding Kontakte}" Margin="12" Name="ListView1" Grid.ColumnSpan="4"> <ListView.View> '... </ListView.View> <ListView.GroupStyle> <GroupStyle> <GroupStyle.ContainerStyle> <Style TargetType="{x:Type GroupItem}"> <Setter Property="Margin" Value="0,0,0,5"/> <Setter Property="Template"> <Setter.Value> <ControlTemplate TargetType="{x:Type GroupItem}"> <Expander Margin="2,0,2,0" Header="{Binding Path=Content.Name, RelativeSource={RelativeSource Mode=FindAncestor, AncestorType={x:Type GroupItem}}}" BorderBrush="Black" BorderThickness="1" Background="LightBlue"> <Expander.Content> <Border Background="White" Margin="2" CornerRadius="3"> <ItemsPresenter /> </Border> </Expander.Content> </Expander> </ControlTemplate> </Setter.Value> </Setter> </Style> </GroupStyle.ContainerStyle> </GroupStyle> </ListView.GroupStyle> </ListView> '... </Grid> </Window> Das Ganze sollte am Ende so aussehen: So... das war's dann auch schon.
Dieser Workshop wurde bereits 39.048 mal aufgerufen.
Anzeige
![]() ![]() ![]() (einschl. Beispielprojekt!) Ein absolutes Muss - Geballtes Wissen aus mehr als 8 Jahren vb@rchiv! - nahezu alle Tipps & Tricks und Workshops mit Beispielprojekten - Symbol-Galerie mit mehr als 3.200 Icons im modernen Look Weitere Infos - 4 Entwickler-Vollversionen (u.a. sevFTP für .NET), Online-Update-Funktion u.v.m. |
vb@rchiv CD Vol.6 ![]() ![]() Geballtes Wissen aus mehr als 8 Jahren vb@rchiv! Online-Update-Funktion Entwickler-Vollversionen u.v.m. Tipp des Monats ![]() Manfred Bohn IndexOf für mehrdimensionale Arrays Die generische Funktion "IndexOf" ermittelt das erste Auftreten eines bestimmten Wertes in einem n-dimensionalen Array sevGraph (VB/VBA) ![]() Grafische Auswertungen Präsentieren Sie Ihre Daten mit wenig Aufwand in grafischer Form. sevGraph unterstützt hierbei Balken-, Linien- und Stapel-Diagramme (Stacked Bars), sowie 2D- und 3D-Tortendiagramme und arbeitet vollständig datenbankunabhängig! |
|||||||||||||
Microsoft, Windows und Visual Basic sind entweder eingetragene Marken oder Marken der Microsoft Corporation in den USA und/oder anderen Ländern. Weitere auf dieser Homepage aufgeführten Produkt- und Firmennamen können geschützte Marken ihrer jeweiligen Inhaber sein. |