Rheinwerk Computing < openbook > Rheinwerk Computing - Professionelle Bücher. Auch für Einsteiger.
Professionelle Bücher. Auch für Einsteiger.

Inhaltsverzeichnis
Vorwort zur 6. Auflage
1 Allgemeine Einführung in .NET
2 Grundlagen der Sprache C#
3 Das Klassendesign
4 Vererbung, Polymorphie und Interfaces
5 Delegates und Ereignisse
6 Strukturen und Enumerationen
7 Fehlerbehandlung und Debugging
8 Auflistungsklassen (Collections)
9 Generics – Generische Datentypen
10 Weitere C#-Sprachfeatures
11 LINQ
12 Arbeiten mit Dateien und Streams
13 Binäre Serialisierung
14 XML
15 Multithreading und die Task Parallel Library (TPL)
16 Einige wichtige .NET-Klassen
17 Projektmanagement und Visual Studio 2012
18 Einführung in die WPF und XAML
19 WPF-Layout-Container
20 Fenster in der WPF
21 WPF-Steuerelemente
22 Elementbindungen
23 Konzepte von WPF
24 Datenbindung
25 Weitere Möglichkeiten der Datenbindung
26 Dependency Properties
27 Ereignisse in der WPF
28 WPF-Commands
29 Benutzerdefinierte Controls
30 2D-Grafik
31 ADO.NET – Verbindungsorientierte Objekte
32 ADO.NET – Das Command-Objekt
33 ADO.NET – Der SqlDataAdapter
34 ADO.NET – Daten im lokalen Speicher
35 ADO.NET – Aktualisieren der Datenbank
36 Stark typisierte DataSets
37 Einführung in das ADO.NET Entity Framework
38 Datenabfragen des Entity Data Models (EDM)
39 Entitätsaktualisierung und Zustandsverwaltung
40 Konflikte behandeln
41 Plain Old CLR Objects (POCOs)
Stichwort

Download:
- Beispiele, ca. 62,4 MB

Jetzt Buch bestellen
Ihre Meinung?

Spacer
Visual C# 2012 von Andreas Kühnel
Das umfassende Handbuch
Buch: Visual C# 2012

Visual C# 2012
Rheinwerk Computing
1402 S., 6., aktualisierte und erweiterte Auflage 2013, geb., mit DVD
49,90 Euro, ISBN 978-3-8362-1997-6
Pfeil 25 Weitere Möglichkeiten der Datenbindung
Pfeil 25.1 »ItemsControl«-Steuerelemente anpassen
Pfeil 25.1.1 Den Style eines »ListBoxItem«-Elements ändern
Pfeil 25.1.2 DataTemplates festlegen
Pfeil 25.1.3 »DataTemplates« mit Trigger
Pfeil 25.2 Alternative Datenbindungen
Pfeil 25.2.1 Die Klasse »ObjectDataProvider«
Pfeil 25.3 Navigieren, Filtern, Sortieren und Gruppieren
Pfeil 25.3.1 Navigieren
Pfeil 25.3.2 Sortieren
Pfeil 25.3.3 Filtern
Pfeil 25.3.4 Gruppieren
Pfeil 25.4 Das Steuerelement »DataGrid«
Pfeil 25.4.1 Elementare Eigenschaften des »DataGrid«
Pfeil 25.4.2 Spalten definieren
Pfeil 25.4.3 Details einer Zeile anzeigen

Rheinwerk Computing - Zum Seitenanfang

25.3 Navigieren, Filtern, Sortieren und GruppierenZur nächsten Überschrift

Neben der reinen Datendarstellung spielen oft auch andere Aspekte eine wichtige Rolle. Beispielsweise wollen die Anwender nicht alle Daten sehen, sondern diese nach gewissen Kriterien filtern. Oder die Anwender möchten die Daten sortieren und zwischen den Daten wahlfrei navigieren können. Das alles mit der WPF umzusetzen ist nicht besonders schwierig, weil die wichtigsten Komponenten dazu von der WPF bereitgestellt werden.

Binden Sie eine Auflistung von Daten an ein WPF-Steuerelement, binden Sie im Grunde genommen nicht direkt an die Daten. Stattdessen erzeugt die WPF einen View auf die Collection, die wie ein Wrapper agiert. Der automatisch erstellte View vom Typ CollectionView implementiert das Interface ICollectionView. Von CollectionView gibt es drei verschiedene Ableitungen:

  • ItemCollection
  • ListCollectionView
  • BindingListCollectionView

Je nachdem, um welche Art von Liste der Wrapper gelegt wird, wird ein passender View erzeugt. Der folgenden Tabelle können Sie Details dazu entnehmen.

Tabelle 25.2 Klassen, die das Interface »ICollectionView« implementieren

Klasse Beschreibung

ItemCollection

Diese Klasse wird nur von den Steuerelementen verwendet, die von ItemsControl abgeleitet sind. Diese Klasse ist ohne Konstruktor und wird von den Steuerelementen intern verwendet. Die Eigenschaft Items ist vom Typ ItemCollection.

CollectionView

Dieser View ist für einfache Auflistungen gedacht, die das Interface IEnumerable implementieren.

ListCollectionView

Mit diesem View werden die Auflistungen gekapselt, die das Interface IList implementieren.

BindingListCollectionView

Mit diesem View werden die Auflistungen gekapselt, die das Interface IBindingList oder IBindingListView implementieren. Ein typischer Vertreter ist die Klasse System.Data.DataView.

Ein CollectionView hat neben dem Sortieren, Filtern und Gruppieren einer Liste auch noch eine andere wichtige Aufgabe: Er verfolgt das aktuell in einem ItemsControl ausgewählte Item. Da sich mehrere Steuerelemente an denselben View binden können, werden neu ausgewählte Listeneinträge automatisch mit den Inhalten der Steuerelemente synchronisiert, die denselben View binden.

Interessant ist auch die Tatsache, dass die drei Klassen ItemCollection, ListCollectionView und BindingListCollectionView die Schnittstelle IEditableCollectionView implementieren. Über dieses Interface werden den Views Methoden wie beispielsweise AddNew, Remove, RemoveAt oder auch CancelEdit verfügbar gemacht.

Die durch die WPF automatisch implizit erzeugte ICollectionView wird auch als DefaultView bezeichnet. Sie wird erstellt, wenn der ItemsSource-Eigenschaft eines ItemsControl-Elements eine Auflistung zugewiesen wird. Es sei angemerkt, dass Sie auch explizit einen CollectionView erstellen und diesen der ItemsSource-Eigenschaft angeben können. Die WPF ihrerseits erzeugt dann keinen DefaultView.

Die Referenz auf den CollectionView kann man auch mit Programmcode abrufen. Dazu dient die statische Methode GetDefaultView der Klasse CollectionViewSource. Der Methode wird als Argument die Datenquelle übergeben, z. B.:

ICollectionView view = CollectionViewSource.GetDefaultView(liste);

Listing 25.17 Die Referenz auf den DefaultView abrufen


Rheinwerk Computing - Zum Seitenanfang

25.3.1 NavigierenZur nächsten ÜberschriftZur vorigen Überschrift

Haben Sie eine Collection von Datenobjekten an ein Fenster gebunden und beabsichtigen Sie, nur die jeweiligen Eigenschaftswerte eines Datenobjekts in ContentControls anzuzeigen, müssen Sie eine Navigation zu den anderen Datenobjekten ermöglichen. Umgesetzt wird so etwas in der Regel mit Navigationsschaltflächen, die eine Navigation zum nächsten Listenelement und zurück zum vorherigen ermöglichen. Üblicherweise wird meist auch die Navigation zum ersten und zum letzten Datenobjekt in der Liste bereitgestellt.

Über die Schnittstelle ICollectionView werden zur Umsetzung dieser Anforderungen zahlreiche Methoden angeboten. Dazu gehören:

  • MoveCurrentToFirst
  • MoveCurrentToLast
  • MoveCurrentToNext
  • MoveCurrentToPrevious
  • MoveCurrentToPosition

In Abbildung 25.4 ist das nächste Beispielprogramm dargestellt, in dem einige wichtige Methoden der Navigation verwendet werden.

Abbildung

Abbildung 25.4 Ausgabe des Beispielprogramms »NavigationSample«

Grundlage des Beispiels ist eine ADO.NET-Datenbankabfrage der Datenbank Northwind. Es wird dabei die Tabelle Products abgefragt. Wenn Sie den Code der ADO.NET-Abfrage nicht verstehen, ist das nicht entscheidend zum Verständnis des Beispiels.

In einer ListBox werden alle Artikel aus der Tabelle mit ihrem Artikelbezeichner (Spalte ProductName) angezeigt. Wird zur Laufzeit ein neuer Eintrag ausgewählt, werden im rechten Teilbereich des Fensters einige Detailinformationen zum ausgewählten Artikel in Textboxen angezeigt. Die Navigation zum nächsten oder zum vorherigen Datensatz wird mit Hilfe von zwei Schaltflächen im oberen Bereich des Fensters unterstützt. Darüber hinaus wird die Position des aktuell ausgewählten Datensatzes nebst der Gesamtanzahl eingespielt.

Sehen wir uns zunächst den C#-Programmcode in der Code-Behind-Datei des Fensters an.

// Beispiel: ..\Kapitel 25\NavigationSample
public partial class MainWindow : Window
{
private BindingListCollectionView view;
public MainWindow() {
// Tabelle aus der Datenbank abrufen
SqlConnection con = new SqlConnection();
con.ConnectionString = @"Server=.\SQLEXPRESS;Database=Northwind; " +
"Integrated Security=SSPI";
DataSet ds = new DataSet();
SqlDataAdapter da = new SqlDataAdapter("SELECT * FROM Products", con);
da.Fill(ds);
InitializeComponent();
this.DataContext = ds.Tables[0];
view = (BindingListCollectionView)CollectionViewSource.
GetDefaultView(this.DataContext);
view.CurrentChanged += view_CurrentChanged;
}
}

Listing 25.18 Vorläufiger Programmcode in der Code-Behind-Datei

Es ist auf Klassenebene ein Feld vom Typ BindingListCollectionView deklariert, das im Konstruktor initialisiert wird. Die Entscheidung für diesen Typ hängt damit zusammen, dass wir später ein DataSet mit der Tabelle der Produkte vorliegen haben, die wir in einen DataView umwandeln müssen. Gemäß Tabelle 25.1 kommt daher nur der BindingListCollectionView als View-Objekt in Frage.

Nach der Datenbankabfrage und dem Aufruf der Methode InitializeComponent wird die Tabelle an den Datenkontext des Window gebunden. Anschließend wird die BindingListCollectionView-Variable wie oben beschrieben initialisiert.

Die Klasse CollectionView vererbt ihren Ableitungen neben Eigenschaften und Methoden auch einige Ereignisse. Eines davon ist CurrentChanged, das ausgelöst wird, wenn sich das aktuell ausgewählte Element im View geändert hat. Wir binden an das Ereignis einen Ereignishandler, um die aktuelle Position innerhalb der Liste anzuzeigen.

Widmen wir uns nun der ListBox. Hier zunächst der XAML-Code, um die Bindung an die Datenquelle zu verstehen:

<ListBox Name="lstProducts" 
DisplayMemberPath="ProductName"
ItemsSource="{Binding}"
SelectionChanged="lstProducts_SelectionChanged" />

Listing 25.19 Datenbindung der ListBox

Mit der Eigenschaft ItemsSource wird auf die Bindung des Window zurückgegriffen, und mit DisplayMemberPath wird die Spalte angegeben, die in der ListBox angezeigt werden soll. Es wird zudem das Ereignis SelectionChanged behandelt, da wir den View davon in Kenntnis setzen müssen, dass sich die ausgewählte Datenzeile geändert hat. Hier auch dazu sofort der Ereignishandler:

private void lstProducts_SelectionChanged(object sender, 
SelectionChangedEventArgs e){
view.MoveCurrentTo(lstProducts.SelectedItem);
}

Listing 25.20 Festlegung des neuen aktuellen Elements

Einfach zu verstehen sind die beiden Ereignishandler der Schaltflächen zur Navigation zum nächsten oder vorherigen Artikel im View.

private void cmdNext_Click(object sender, RoutedEventArgs e) {
view.MoveCurrentToNext();
}
private void cmdPrevious_Click(object sender, RoutedEventArgs e) {
view.MoveCurrentToPrevious();
}

Listing 25.21 Ereignishandler der Navigationsschaltflächen

Jetzt fehlt nur noch der Ereignishandler des Events CurrentChanged.

void view_CurrentChanged(object sender, EventArgs e) {
txtPosition.Text = "Datensatz " + (view.CurrentPosition + 1).ToString() +
" von " + view.Count.ToString();
cmdPrevious.IsEnabled = view.CurrentPosition > 0;
cmdNext.IsEnabled = view.CurrentPosition < view.Count - 1;
}

Listing 25.22 Der Ereignishandler des Ereignisses »CurrentChanged«

Der View liefert in der Eigenschaft CurrentPosition die Indexposition des aktuellen Elements der Liste. Da diese Eigenschaft die Ordinalposition liefert, müssen wir das durch die Addition der Zahl 1 zur Ausgabe der Position des Listenelements berücksichtigen. Zudem nutzen wir CurrentPosition auch dazu aus, im Bedarfsfall eine der beiden Navigationsschaltflächen zu deaktivieren, falls der erste oder der letzte Datensatz aus der Liste das aktuell ausgewählte Element ist.


Rheinwerk Computing - Zum Seitenanfang

25.3.2 SortierenZur nächsten ÜberschriftZur vorigen Überschrift

Die Sortierung nach einer Eigenschaft der durch einen View beschriebenen Elemente ist nicht schwierig. Dazu stellt die Schnittstelle ICollectionView den implementierenden Klassen mit SortDescriptions eine passende Eigenschaft bereit, der das gewünschte Sortierkriterium angegeben wird. Da diese Eigenschaft eine Liste beschreibt, können Sie auch mehrere Sortierkriterien angeben.

Jedes Sortierkriterium wird durch ein Objekt vom Typ SortDescription beschrieben. Um ein Sortierkriterium zu definieren, übergeben Sie dem Konstruktor zwei Argumente. Im ersten Argument wird die Eigenschaft genannt, nach der die Liste sortiert werden soll. Das zweite Argument beschreibt die Richtung der Sortierung mit der Enumeration ListSortDirection, die die beiden Member Ascending (für eine aufsteigende Reihenfolge) und Descending (für eine absteigende Reihenfolge) enthält.

Um Ihnen zu zeigen, wie Sie eine Sortierung umsetzen können, greifen wir auf das Beispielprogramm NavigationSample des letzten Abschnitts zurück und wollen die Produkte, die in der ListBox angezeigt werden, dem Artikelnamen nach sortieren. Den erforderlichen Programmcode können wir grundsätzlich in jedem passenden Ereignishandler implementieren, in unserem Beispiel bietet sich aber der Konstruktor bestens an.

// Beispiel: ..\Kapitel 25\ViewSortSample
public MainWindow()
{
[...]
this.DataContext = ds.Tables[0];
view = (BindingListCollectionView)CollectionViewSource.
GetDefaultView(this.DataContext);
view.CurrentChanged += view_CurrentChanged;
SortDescription sort = new SortDescription("ProductName",
ListSortDirection.Ascending);
view.SortDescriptions.Add(sort);
lstProducts.SelectedIndex = 0;
}

Listing 25.23 Sortieren eines Views

Zur Umsetzung mehrerer Sortierkriterien werden mehrere SortDescription-Objekte benötigt, die nacheinander abgearbeitet werden. So ließe sich die Produktliste zuerst nach Kategorien sortieren (Eigenschaft CategoryID) und danach innerhalb der Kategorien nach dem Preis (Eigenschaft UnitPrice).

[...]
SortDescription sortCat = new SortDescription("CategoryID",
ListSortDirection.Ascending);
SortDescription sortPrice = new SortDescription("Unitprice",
ListSortDirection.Ascending);
view.SortDescriptions.Add(sortCat);
view.SortDescriptions.Add(sortPrice);
[...]

Listing 25.24 Mehrere Sortierkriterien


Rheinwerk Computing - Zum Seitenanfang

25.3.3 FilternZur nächsten ÜberschriftZur vorigen Überschrift

Das Filtern ermöglicht uns, aus einer Liste nur diejenigen Elemente anzuzeigen, die einer bestimmten Bedingung genügen. Hinsichtlich des Filterns eines Views müssen wir jedoch zwei Fälle unterscheiden: das Filtern einer herkömmlichen Collection und das Filtern einer ADO.NET-DataTable. Wir wollen uns beide Varianten ansehen.

Das Filtern einer Collection

Das Filtern einer Collection erfolgt mit der Eigenschaft Filter des Views. Am besten sehen wir uns zuerst die Definition der Filter-Eigenschaft an:

public virtual Predicate<Object> Filter { get; set;}

Die Eigenschaft erwartet einen Delegaten vom Typ Predicate, der auf eine Filtermethode zeigt, die Sie bereitstellen müssen. Auch die Definition des Delegates müssen wir uns ansehen, um den Filter zu verstehen:

public delegate bool Predicate<Object>(Object item)

Der Rückgabewert ist bool. Innerhalb der Filtermethode wird jedes Listenelement aufgerufen und dahingehend einer Prüfung unterzogen, ob es den Bedingungen entspricht oder nicht. Im ersten Fall muss die Filtermethode true zurückliefern. Um beispielsweise aus einer Auflistung von Person-Objekten, in denen die Eigenschaften Name, Alter und Ort beschrieben werden, alle Personen herauszufiltern, die in einer bestimmten Stadt wohnen, könnte der Code wie folgt aussehen:

// Beispiel: ..\Kapitel 25\CollectionFilterSample
public partial class MainWindow : Window {
private List<Person> liste = new List<Person>();
public MainWindow() {
FillListe();
InitializeComponent();
lstPersons.ItemsSource = liste;
ListCollectionView view = CollectionViewSource.GetDefaultView(
lstPersons.ItemsSource) as ListCollectionView;
view.Filter = new Predicate<object>(FilterPersons);
}
public bool FilterPersons(object item) {
Person pers = (Person)item;
return (pers.Ort == "Essen");
}
private void FillListe() {
liste.Add(new Person { Name = "Müller", Alter=43, Ort = "Duisburg" });
liste.Add(new Person { Name = "Meier", Alter=43, Ort = "Bonn" });
liste.Add(new Person { Name = "Schmidt", Alter=43, Ort = "Essen" });
[...]
}
}
class Person {
public string Name { get; set; }
public int Alter { get; set; }
public string Ort { get; set; }
}

Listing 25.25 Festlegen eines Filters

Der XAML-Code zum Testen des hardcodierten Filters ist denkbar einfach:

<Window ...
Title="MainWindow" Height="150" Width="150">
<Grid>
<ListBox Name="lstPersons" DisplayMemberPath="Name"></ListBox>
</Grid>
</Window>

Listing 25.26 XAML-Code des Beispiels »CollectionFilterSample«

Auch wenn dieses Beispiel sehr gut funktioniert, hat es einen gravierenden Nachteil. Es ist hinsichtlich der Filterbedingung unflexibel, da ein Benutzer nicht in der Lage ist, das Filterkriterium zur Laufzeit der Anwendung dynamisch festzulegen.

Das wollen wir natürlich anders gestalten und bedienen uns dazu eines simplen Tricks. Wir implementieren die Filtermethode in einer separaten Klasse. Innerhalb der Klasse legen wir eine Eigenschaft fest, die die Filterbedingung beschreibt. Bevor der Filter gesetzt wird, muss die Klasse instanziiert werden und ihr dabei die Bedingung übergeben werden. Dazu eignet sich der Konstruktor. Innerhalb der Filtermethode wird die Bedingung beim Setzen des Filters ausgewertet.

// Beispiel: ..\Kapitel 25\CollectionDynamicFilterSample
class FilterPersonByCity {
public string Ort { get; set; }
public FilterPersonByCity(string city) {
Ort = city;
}
public bool FilterPersons(object item) {
Person pers = (Person)item;
return (pers.Ort == Ort);
}
}

Listing 25.27 Wrapper-Klasse um die Filtermethode

Diese Klasse wollen wir nun testen und ergänzen das Fenster des Beispiels CollectionFilterSample um eine TextBox und eine Schaltfläche. Der Anwender kann eine Stadt in die TextBox eintragen. Beim Klicken auf die Schaltfläche wird der Filter entsprechend neu gesetzt und aktiviert, und in der ListBox werden alle Personen angezeigt, die der Bedingung entsprechen. Bleibt die TextBox leer, wird der Filter durch Setzen auf null gelöscht.

public partial class MainWindow : Window {
private List<Person> liste = new List<Person>();
private ListCollectionView view;
public MainWindow() {
FillListe();
InitializeComponent();
lstPersons.ItemsSource = liste;
view = CollectionViewSource.GetDefaultView
(lstPersons.ItemsSource) as ListCollectionView;
}
private void btnSetFilter_Click(object sender, RoutedEventArgs e) {
FilterPersonByCity filter = new FilterPersonByCity(txtCity.Text);
if (txtCity.Text == "")
view.Filter = null;
else
view.Filter = new Predicate<object>(filter.FilterPersons);
}
}

Listing 25.28 Änderungen und Ergänzungen im Code des »Window«

Filtern einer »DataTable«

Das Filtern eines Views vom Typ ListCollectionView ist mit der Eigenschaft Filter möglich. Auch die Klasse BindingListCollectionView, die im Zusammenhang mit einer ADO.NET-DataTable verwendet wird, stellt eine Filter-Eigenschaft zur Verfügung. Allerdings nur offiziell, denn diese Eigenschaft wird von BindingListCollectionView nicht weiter unterstützt und wirft eine Ausnahme, wenn man dennoch versucht, sie zu benutzen.

Stattdessen wird von BindingListCollectionView die Eigenschaft CustomFilter unterstützt. Diese Eigenschaft erwartet den Filter in Form einer Zeichenfolge. Das macht den Einsatz sehr einfach, da die Zeichenfolge entsprechend der aus SQL bekannten Filterbedingung als WHERE-Klausel formuliert wird.

Das folgende Beispielprogramm nutzt erneut die Tabelle Products der Northwind-Datenbank. Der Filter wird in einer TextBox festgelegt und durch Klicken auf eine Schaltfläche aktiviert.

// Beispiel: ..\Kapitel 25\DataTableFilterSample
<Window ...
Title="DataTableFilter" Height="250" Width="550">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="250" />
<ColumnDefinition />
</Grid.ColumnDefinitions>
<ListBox Name="lstProducts" DisplayMemberPath="ProductName"
Margin="10"></ListBox>
<StackPanel Grid.Column="1">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="60"/>
<ColumnDefinition/>
</Grid.ColumnDefinitions>
<TextBlock HorizontalAlignment="Left" VerticalAlignment="Center"
Foreground="Wheat" Text="UnitPrice > " />
<TextBox Grid.Column="1" Name="txtUnitPrice" Margin="5"
Background="AntiqueWhite"></TextBox>
</Grid>
<Button Name="btnSetFilter" Margin="5" Click="btnSetFilter_Click">
Filter setzen
</Button>
</StackPanel>
</Grid>
</Window>

Dazu gehört der folgende C#-Code:

public partial class MainWindow : Window {
private BindingListCollectionView view;
public MainWindow() {
[...]
da.Fill(ds);
InitializeComponent();
lstProducts.ItemsSource = ds.Tables[0].DefaultView;
view = (BindingListCollectionView)CollectionViewSource.
GetDefaultView(lstProducts.ItemsSource);
}
private void btnSetFilter_Click
(object sender, RoutedEventArgs e) {
view.CustomFilter = "UnitPrice > " + txtUnitPrice.Text;
}
}

Listing 25.29 Code des Beispielprogramms »DataTableFilterSample«

Sie können in der TextBox auch eine Filterbedingung festlegen, die mehrere Filterbedingungen mit AND oder OR verknüpft. Das sollte auch nicht erstaunen, da der Filter gemäß den Regeln der SQL-WHERE-Klausel formuliert wird (siehe Abbildung 25.5).

Abbildung

Abbildung 25.5 Das Beispielprogramm mit mehreren Filtern


Rheinwerk Computing - Zum Seitenanfang

25.3.4 GruppierenZur nächsten ÜberschriftZur vorigen Überschrift

Das Gruppieren der angezeigten Daten erfolgt ebenfalls über ein View-Objekt. Um präziser zu sein, die Schnittstelle ICollectionView stellt dafür allen Ableitungen die Eigenschaft GroupDescriptions zur Verfügung, die vom Typ ObservableCollection<GroupDescription> ist. Da die Klasse GroupDescription ihrerseits selbst abstrakt definiert ist, gibt es mit PropertyGroupDescription eine spezialisierte Klasse, die minimal eine Zeichenfolge entgegennimmt, die die Eigenschaft angibt, nach der gruppiert werden soll, z. B.:

view.GroupDescriptions.Add(new PropertyGroupDescription("CategoryID"));

In dieser Anweisung wird die Tabelle Products nach der CategoryID gruppiert.

Um die Gruppierung anzeigen zu können, vererbt die Klasse ItemsControl allen Ableitungen die Eigenschaft GroupStyle. Auch diese Eigenschaft beschreibt eine Collection, diesmal vom Typ ObservableCollection<GroupStyle>. Die GroupStyle-Klasse verfügt über die Eigenschaft HeaderTemplate, mit der die Darstellung der Kopfzeile einer Gruppe als DataTemplate beschrieben wird.

Angenommen, wir würden beabsichtigen, die in der Tabelle Products enthaltenen Artikel der Kategorie nach zu gruppieren und in einer ListBox anzuzeigen.

Abbildung

Abbildung 25.6 Ausgabe des Beispielprogramms »SimpleGroupingSample«

Der XAML-Code dazu sieht wie folgt aus:

// Beispiel: ..\Kapitel 25\SimpleGroupingSample
<Window ... Title="MainWindow" Height="350" Width="300">
<Window.Resources>
<CollectionViewSource x:Key="groupView">
<CollectionViewSource.GroupDescriptions>
<PropertyGroupDescription PropertyName="CategoryID" />
</CollectionViewSource.GroupDescriptions>
</CollectionViewSource>
</Window.Resources>
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto"></RowDefinition>
<RowDefinition></RowDefinition>
</Grid.RowDefinitions>
<ListBox Grid.Row="1" Margin="7,3,7,10" Name="lstProducts"
ItemsSource="{Binding Source={StaticResource groupView}}" >
<ListBox.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding ProductName}"/>
</DataTemplate>
</ListBox.ItemTemplate>
<ListBox.GroupStyle>
<GroupStyle>
<GroupStyle.HeaderTemplate>
<DataTemplate>
<TextBlock Text="{Binding Path=Name}" FontWeight="Bold"
Foreground="White" Background="LightGreen"
Margin="0,5,0,0" Padding="3"/>
</DataTemplate>
</GroupStyle.HeaderTemplate>
</GroupStyle>
</ListBox.GroupStyle>
</ListBox>
</Grid>
</Window>

Jetzt folgt natürlich auch noch der C#-Code:

public MainWindow() {
// Tabelle Products in den Speicher laden
InitializeComponent();
CollectionViewSource viewSource =
(CollectionViewSource)this.FindResource("groupView");
viewSource.Source = ds.Tables[0].DefaultView;
}

Listing 25.30 Code des Beispielprogramms »SimpleGroupingSample«

Im Konstruktor des Window wird zuerst die Tabelle Products mit ADO.NET-Code in den Speicher geladen und anschließend als View einem in der XAML-Datei definierten CollectionViewSource-Objekt als Datenquelle übergeben.

In der XAML-Datei ist das CollectionViewSource-Objekt im Resource-Abschnitt definiert. Mit PropertyGroupDescription wird die Gruppierung aller Artikel anhand der Eigenschaft CategoryID festgelegt.

<CollectionViewSource x:Key="groupView">       
<CollectionViewSource.GroupDescriptions>
<PropertyGroupDescription PropertyName="CategoryID" />
</CollectionViewSource.GroupDescriptions>
</CollectionViewSource>

Listing 25.31 Gruppierungseigenschaft festlegen

Die Eigenschaft GroupStyle legt den Stil der Gruppendarstellung fest. Dazu wird der Eigenschaft HeaderTemplate ein DataTemplate übergeben, das ein TextBlock-Element enthält, das gebunden werden muss.

Hinsichtlich der Bindung des TextBlocks muss man allerdings ein wenig in die Trickkiste greifen, um zum gewünschten Ziel zu kommen (die CategoryID in der Kopfzeile anzuzeigen). Sie dürfen nämlich nicht an die Eigenschaft CategoryID binden, wie zuerst zu vermuten wäre. Stattdessen erfolgt die Bindung an die Name-Eigenschaft des PropertyGroupDescription-Objekts:

<TextBlock Text="{Binding Path=Name}" ... />

Erweiterte Gruppierung

Sehen wir uns noch einmal Abbildung 25.6 an. Die Werte der Spalte CategoryID als Text in das HeaderTemplate zu schreiben ist mit Sicherheit nicht das, was wir dem Anwender anbieten wollen. Hier wäre der Kategoriename hinter CategoryID wünschenswert. Dieser verbirgt sich in der Spalte CategoryName der Tabelle Categories, die sich in einer 1:n-Beziehung zur Tabelle Products befindet.

Das Problem könnte sehr einfach mit einer SQL-JOIN-Abfrage gelöst werden. Diesen Weg möchte ich Ihnen aber nicht zeigen, weil er zu wenig Allgemeingültigkeit hat. Wir wollen uns stattdessen den gewünschten Kategoriebezeichner direkt aus der Tabelle Categories besorgen und den durch CategoryID beschriebenen Zahlenwert durch den Namen der Kategorie ersetzen. Hier bietet es sich an, einen passenden Konverter zu schreiben, der genau diese Anforderung erfüllt.

// Beispiel: ..\Kapitel 25\AdvancedGroupingSample
public class GroupingConverter : IValueConverter {
private DataSet ds = new DataSet();
public GroupingConverter() {
SqlConnection con = new SqlConnection();
con.ConnectionString = @"...";
string sql = "SELECT CategoryID, CategoryName FROM Categories";
SqlDataAdapter da = new SqlDataAdapter(sql, con);
da.FillSchema(ds, SchemaType.Source);
da.Fill(ds);
}
public object Convert(object value, Type targetType, object parameter,
CultureInfo culture)

{
DataRow row = ds.Tables[0].Rows.Find((int)value);
return row["CategoryName"].ToString();
}
public object ConvertBack(object value, Type targetType,
object parameter, CultureInfo culture)

{
throw new NotImplementedException();
}
}

Listing 25.32 Die »Converter«-Klasse

Im XAML-Code geben wir nun den Namespace dieser Klasse mit

xmlns:local="clr-namespace:AdvancedGroupingSample"

bekannt und erstellen im Resource-Abschnitt des Fensters ein Objekt dieser Klasse:

<Window.Resources>
<local:GroupingConverter x:Key="converter" />
[...]
</Window.Resources>

Nun folgt noch der letzte Schritt, denn dem PropertyGroupDescription-Objekt muss das Converter-Objekt mit der Eigenschaft Converter bekannt gegeben werden:

<PropertyGroupDescription PropertyName="CategoryID" 
Converter="{StaticResource converter}" />

Das Beispiel SimpleGroupingSample auf der Buch-DVD wurde zudem um die Sortierung ergänzt. Dabei werden zunächst alle Kategorien sortiert, und innerhalb derselben wird nach dem Produktnamen sortiert:

<CollectionViewSource x:Key="groupView">
[...]
<CollectionViewSource.SortDescriptions>
<sys:SortDescription PropertyName="CategoryID"
Direction="Ascending"/>
<sys:SortDescription PropertyName="ProductName"
Direction="Ascending"/>
</CollectionViewSource.SortDescriptions>
</CollectionViewSource>

Da der Typ SortDescription keinem XML-Namespace zugeordnet ist, müssen die Datei WindowsBase.dll und der CLR-Namespace System.ComponentModel, in dem diese Klasse definiert ist, mit

xmlns:sys="clr-namespace:System.ComponentModel;assembly=WindowsBase"

bekannt gegeben werden.



Ihre Meinung

Wie hat Ihnen das Openbook gefallen? Wir freuen uns immer über Ihre Rückmeldung. Schreiben Sie uns gerne Ihr Feedback als E-Mail an kommunikation@rheinwerk-verlag.de.

<< zurück
  Zum Rheinwerk-Shop
Zum Rheinwerk-Shop: Visual C# 2012

Visual C# 2012
Jetzt Buch bestellen


 Ihre Meinung?
Wie hat Ihnen das Openbook gefallen?
Ihre Meinung

 Buchempfehlungen
Zum Rheinwerk-Shop: Professionell entwickeln mit Visual C# 2012






 Professionell
 entwickeln mit
 Visual C# 2012


Zum Rheinwerk-Shop: Windows Presentation Foundation






 Windows Presentation
 Foundation


Zum Rheinwerk-Shop: Schrödinger programmiert C++






 Schrödinger
 programmiert C++


Zum Rheinwerk-Shop: C++ Handbuch






 C++ Handbuch


Zum Rheinwerk-Shop: C/C++






 C/C++


 Lieferung
Versandkostenfrei bestellen in Deutschland, Österreich und der Schweiz
InfoInfo





Copyright © Rheinwerk Verlag GmbH 2013
Für Ihren privaten Gebrauch dürfen Sie die Online-Version natürlich ausdrucken. Ansonsten unterliegt das Openbook denselben Bestimmungen, wie die gebundene Ausgabe: Das Werk einschließlich aller seiner Teile ist urheberrechtlich geschützt.
Alle Rechte vorbehalten einschließlich der Vervielfältigung, Übersetzung, Mikroverfilmung sowie Einspeicherung und Verarbeitung in elektronischen Systemen.


Nutzungsbestimmungen | Datenschutz | Impressum

Rheinwerk Verlag GmbH, Rheinwerkallee 4, 53227 Bonn, Tel.: 0228.42150.0, Fax 0228.42150.77, service@rheinwerk-verlag.de

Cookie-Einstellungen ändern