wykład 7 – konwersja, walidacja, szablony, widokiii.uwb.edu.pl/rybnik/dot NET/PwT.N W7.pdf ·...
Transcript of wykład 7 – konwersja, walidacja, szablony, widokiii.uwb.edu.pl/rybnik/dot NET/PwT.N W7.pdf ·...
Budowa aplikacji w technologii .NETwykład 7 – konwersja, walidacja, szablony, widoki
<Window ... Title="Księgarnia"> <Grid> ... <ListBox Name="lista" DisplayMemberPath="Title"/> <GridSplitter Grid.Column="1" Width="5" HorizontalAlignment="Center"/> <Grid Grid.Column="2" DataContext="{Binding ElementName=lista, Path=SelectedItem}" > ... <Label ...>Tytuł:</Label> <TextBox ... Text="{Binding Path=Title}" /> <Label ...>Autor:</Label> <TextBox ... Text="{Binding Path=Author}"/> <Label ...>Cena:</Label> <TextBox ... Text="{Binding Path=Price}"/> </Grid> </Grid></Window>
1/85
public class Book{ public string Title { get; set; } public string Author { get; set; } public decimal Price { get; set; } public Book(string title, string author, decimal price) { Title = title; Author = author; Price = price; }}
List<Book> lst = new List<Book>();lst.Add(new Book("Lód", "Jacek Dukaj", 57.99M));lst.Add(new Book("Inne pieśni", "Jacek Dukaj", 48.50M));...lista.ItemsSource = lst;
2/85
3/85
Konwersja danych
• Odpowiada za konwertowanie źródłowych danych, zanim zostaną wyświetlone (np. z niskopoziomowej reprezentacji w postać czytelną dla użytkownika) oraz konwersję nowych wartości, nim zostaną zapamiętane.
• Używana jest do:◦ formatowania danych (np. konwersja liczby na string),◦ tworzenia obiektów WPF (np. przy wyświetlaniu obrazków),◦ warunkowej modyfikacji pewnych własności elementów interfejsu.
4/85
Value Converter
[ValueConversion(typeof(decimal), typeof(string))]public class PriceConverter : IValueConverter{ public object Convert(object value, Type targetType, object parameter, CultureInfo culture) { decimal price = (decimal)value; return price.ToString("C", culture); } public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) { string price = value.ToString(); decimal result; if (Decimal.TryParse(price, NumberStyles.Any, culture, out result)) { return result; } return value; }}
5/85
Value Converter
• Ustawienia języka:
<Window ... xmlns:local="clr-namespace:WpfApp1" Language="pl-PL">
• Wybór konwertera:
<Label Grid.Row="2" Margin="3">Cena:</Label><TextBox Grid.Column="1" Grid.Row="2" Margin="3"> <TextBox.Text> <Binding Path="Price"> <Binding.Converter> <local:PriceConverter/> </Binding.Converter> </Binding> </TextBox.Text></TextBox>
6/85
Value Converter
• Konwerter w zasobach:
<Window.Resources> <local:PriceConverter x:Key="PriceConverter" /></Window.Resources>
• Korzystanie:
<TextBox Grid.Column="1" Grid.Row="2" Margin="3" Text="{Binding Path=Price, Converter={StaticResource PriceConverter}}"/>
7/85
Value Converter
8/85
Tworzenie obiektów z Value Converterem
• Baza danych może przechowywać dane binarne reprezentujące obraz produktu.• Konwerter pozwala skonwertować tablicę bajtów na obiekt klasy BitmapImage:
◦ tworzymy obiekt BitmapImage,◦ odczytujemy dane obrazka w MemoryStream,◦ wywołujemy BitmapImage.BeginInit(),◦ ustawiamy własność StreamSource na nasz MemoryStream,◦ wywołujemy EndInit() aby zakończyć ładowanie obrazka.
• Prostszy przykład: pole ImagePath przechowuje ścieżkę, a obrazki są zapisane na dysku.
9/85
Tworzenie obiektów z Value Converterem
public class ImagePathConverter : IValueConverter{ private string imageDirectory = Directory.GetCurrentDirectory(); public string ImageDirectory { get { return imageDirectory; } set { imageDirectory = value; } } public object Convert(...) { string imagePath = System.IO.Path.Combine(ImageDirectory, (string)value); return new BitmapImage(new Uri(imagePath)); } public object ConvertBack(...) { throw new NotSupportedException(); }}
• obrazek można odczytać też ze zdalnej lokacji:
return new BitmapImage(new Uri( (string)value, UriKind.Absolute));
10/85
Tworzenie obiektów z Value Converterem
• Wykorzystanie:
<Window.Resources> <local:ImagePathConverter x:Key="ImagePathConverter" /></Window.Resources>
<Image Margin="3" Grid.Row="3" Grid.Column="1" Stretch="Uniform" HorizontalAlignment="Center" Source="{Binding Path=ImagePath, Converter={StaticResource ImagePathConverter}}">
• W wypadku braku obrazka – możemy łapać wyjątek w metodzie Convert() i np. zwracać Binding.DoNothing lub jakiś obrazek domyślny.
11/85
Tworzenie obiektów z Value Converterem
12/85
Formatowanie warunkowe
public class PriceToBackgroundConverter : IValueConverter{ public decimal MaximumPriceToHighlight { get; set; } public Brush HighlightBrush { get; set; } public Brush DefaultBrush { get; set; } public object Convert(...) { decimal price = (decimal)value; if (price <= MaximumPriceToHighlight) return HighlightBrush; else return DefaultBrush; } public object ConvertBack(...) { throw new NotSupportedException(); }}
13/85
Formatowanie warunkowe
<Window.Resources> ... <local:PriceToBackgroundConverter x:Key="PriceToBackgroundConverter" DefaultBrush="{x:Null}" HighlightBrush="GreenYellow" MaximumPriceToHighlight="29.99"/></Window.Resources>
<Grid DataContext="{Binding ElementName=lista, Path=SelectedItem}" Grid.Column="2" Background="{Binding Path=Price, Converter={StaticResource PriceToBackgroundConverter}}">...</Grid>
14/85
Formatowanie warunkowe
15/85
MultiConverter
• Pozwala kilka własności skonwertować na jedną wartość.
<Window.Resources> <local:PriceVatConverter x:Key="PriceVatConverter" /></Window.Resources><TextBox Grid.Column="1" Grid.Row="2" Margin="3"> <TextBox.Text> <MultiBinding Converter="{StaticResource PriceVatConverter}"> <Binding Path="Price"></Binding> <Binding Path="VAT"></Binding> </MultiBinding> </TextBox.Text></TextBox>
16/85
MultiConverter
• Wartości w tablicy values są w tej samej kolejności, co Bindingi w definicji w XAMLu.
public class PriceVatConverter : IMultiValueConverter{ public object Convert(object[] values, ...) { try { decimal price = (decimal)values[0]; decimal vat = (decimal)values[1]; return (price * (1 + vat)).ToString("C", culture); } catch {return Binding.DoNothing;} } public object[] ConvertBack(object value, Type[] t, ...) { throw new NotSupportedException(); }}
17/85
MultiConverter
18/85
Walidacja
• Pozwala kontrolować poprawność danych przy przesyłaniu ich z elementu docelowego do źródła.
Rzucanie wyjątku:
private decimal price;public decimal Price{ get { return price; } set { if (value <= 0) throw new ArgumentException( "Cena musi być większa od 0."); price = value; }}
19/85
Walidacja
• Wyjątki wiązania danych są ignorowane, dlatego potrzebujemy jeszcze reguły walidacji:
<TextBox Grid.Column="1" Grid.Row="2" Margin="3"> <TextBox.Text> <Binding Path="Price"> <Binding.Converter> <StaticResource ResourceKey="PriceConverter"/> </Binding.Converter> <Binding.ValidationRules> <ExceptionValidationRule/> </Binding.ValidationRules> </Binding> </TextBox.Text></TextBox>
20/85
Walidacja
21/85
Walidacja
• W wypadku nieudanej walidacji WPF:◦ ustawia własność dołączoną Validation.HasError na true,◦ tworzy ValidationError zawierający szczegóły błędu,◦ jeśli ustawiono Binding.NotifyOnValidationError na true, podnosi zdarzenie
Validation.Error.• Zmienia się również wygląd kontrolki (wykorzystanie szablonu
Validation.ErrorTemplate).
22/85
Walidacja
• Niekiedy nie chcemy rzucać wyjątków przy każdym błędzie użytkownika:
public class Book : IDataErrorInfo{ ... private decimal price; public decimal Price { get { return price; } set { price = value; } }
23/85
Walidacja
public class Book : IDataErrorInfo{ ...
public string this[string columnName] { get { if (columnName == "Price") { if (price <= 0) return "Cena musi być większa od 0."; } return null; } } public string Error { get { return null; } }}
24/85
Walidacja
• Inny przykład:
public string this[string columnName] { get { if (propertyName == "Code") { bool valid = true; foreach (char c in Code) { if (!Char.IsLetterOrDigit(c)) { valid = false; break; } } if (!valid) return "Może zawierać tylko cyfry i litery."; } return null; }}
25/85
Walidacja
<TextBox Grid.Column="1" Grid.Row="2" Margin="3"> <TextBox.Text> <Binding Path="Price"> <Binding.Converter> <StaticResource ResourceKey="PriceConverter"/> </Binding.Converter> <Binding.ValidationRules> <DataErrorValidationRule/> </Binding.ValidationRules> </Binding> </TextBox.Text></TextBox>
• Możliwe jest łączenie obu podejść.• Możemy skorzystać ze skrótu – zamiast dodawać ExceptionValidationRule i
DataErrorValidationRule, ustawiamy na true:◦ Binding.ValidatesOnExceptions◦ Binding.ValidatesOnDataErrors
26/85
Walidacja
27/85
Walidacja
• Własne reguły walidacji.
public class PositivePriceRule : ValidationRule{ private decimal min = 0; private decimal max = Decimal.MaxValue; public decimal Min { get { return min; } set { min = value; } } public decimal Max { get { return max; } set { max = value; } }
28/85
Walidacja
public class PositivePriceRule : ValidationRule{ ... public override ValidationResult Validate(object value, CultureInfo culture) { decimal price = 0; try { if (((string)value).Length > 0) price = Decimal.Parse((string)value, NumberStyles.Any, culture); } catch { return new ValidationResult(false, "Illegal characters."); }
29/85
Walidacja
... if ((price < Min) || (price > Max)) { return new ValidationResult(false, "Not in the range " + Min + " to " + Max + "."); } else { return new ValidationResult(true, null); } }}
30/85
Walidacja
<TextBox Grid.Column="1" Grid.Row="2" Margin="3"> <TextBox.Text> <Binding Path="Price"> <Binding.Converter> <StaticResource ResourceKey="PriceConverter"/> </Binding.Converter> <Binding.ValidationRules> <local:PositivePriceRule Min="0.01" Max="999.99" /> </Binding.ValidationRules> </Binding> </TextBox.Text></TextBox>
• Uwaga: możemy dodać dowolną liczbę reguł walidacji.
31/85
Walidacja
32/85
Walidacja
• Reakcja na błędy walidacji:◦ flaga NotifyOnValidationError:
<Binding Path="Price" NotifyOnValidationError="True"> ...</Binding>
◦ zdarzenie:
<Grid Validation.Error="validationError">
◦ obsługa:
private void validationError(object sender, ...){ if (e.Action == ValidationErrorEventAction.Added) { MessageBox.Show(e.Error.ErrorContent.ToString()); }}
33/85
Walidacja
34/85
Walidacja
• Lista błędów walidacji:
private void cmdOK_Click(object sender, RoutedEventArgs e){ string message; if (FormHasErrors(out message)) { // Errors still exist. MessageBox.Show(message); } else { // ... }}
35/85
Walidacja
private bool FormHasErrors(out string message){ StringBuilder sb = new StringBuilder(); GetErrors(sb, gridProductDetails); message = sb.ToString(); return message != "";}
36/85
Walidacja
private void GetErrors(StringBuilder sb, DependencyObject obj){ foreach (object child in LogicalTreeHelper.GetChildren(obj)) { TextBox element = child as TextBox; if (element == null) continue; if (Validation.GetHasError(element)) { sb.Append(element.Text + " has errors:\r\n"); foreach (ValidationError error in Validation.GetErrors(element)) { sb.Append(" " + error.ErrorContent.ToString()); sb.Append("\r\n"); } } // sprawdź dzieci GetErrors(sb, element); }}
37/85
Walidacja
• Własne style powiadomienia:
<TextBox Grid.Column="1" Grid.Row="2" Margin="3,3,20,3"> <Validation.ErrorTemplate> <ControlTemplate> <DockPanel LastChildFill="True"> <TextBlock DockPanel.Dock="Right" Foreground="Red" FontSize="14" FontWeight="Bold">*</TextBlock> <Border BorderBrush="Green" BorderThickness="1"> <AdornedElementPlaceholder /> </Border> </DockPanel> </ControlTemplate> </Validation.ErrorTemplate> <TextBox.Text> ... </TextBox.Text></TextBox>
38/85
Walidacja
39/85
Walidacja
<TextBlock ... ToolTip="{Binding ElementName=adornerPlaceholder, Path=AdornedElement.(Validation.Errors)[0] .ErrorContent}">*</TextBlock> ... <AdornedElementPlaceholder Name="adornerPlaceholder" />
40/85
Szablony danych
• Fragment kodu XAMLa, który mówi w jaki sposób ma być wyświetlany dowiązany obiekt danych:◦ kontrolki zawartości obsługują to poprzez własność ContentTemplate◦ kontrolki list – poprzez ItemTemplate (stosowane do każdego obiektu kolekcji)
Pozwala zastąpić to:
<ListBox Name="lista" Margin="5" DisplayMemberPath="Title"/>
Tym:
<ListBox Name="lista" Margin="5"> <ListBox.ItemTemplate> <DataTemplate> <TextBlock Text="{Binding Path=Title}"/> </DataTemplate> </ListBox.ItemTemplate></ListBox>
41/85
Szablony danych
42/85
Szablony danych
<ListBox Name="lista" Margin="5" HorizontalContentAlignment="Stretch"> <ListBox.ItemTemplate> <DataTemplate> <Border Margin="5" BorderThickness="1" BorderBrush="SteelBlue" CornerRadius="4"> <Grid Margin="3"> <Grid.RowDefinitions> <RowDefinition></RowDefinition> <RowDefinition></RowDefinition> </Grid.RowDefinitions> <TextBlock FontWeight="Bold" Text="{Binding Path=Title}"></TextBlock> <TextBlock Grid.Row="1" Text="{Binding Path=Author}"></TextBlock> </Grid> </Border> </DataTemplate> </ListBox.ItemTemplate></ListBox>
43/85
Szablony danych
44/85
Szablony danych
• Umieszczanie szablonów w zasobach:
<Window.Resources> ... <DataTemplate x:Key="BookDataTemplate"> <Border Margin="5" BorderThickness="1" BorderBrush="SteelBlue" CornerRadius="4"> <Grid Margin="3"> <Grid.RowDefinitions> <RowDefinition></RowDefinition> <RowDefinition></RowDefinition> </Grid.RowDefinitions> <TextBlock FontWeight="Bold" Text="{Binding Path=Title}"></TextBlock> <TextBlock Grid.Row="1" Text="{Binding Path=Author}"></TextBlock> </Grid> </Border> </DataTemplate></Window.Resources>
45/85
Szablony danych
• Korzystanie z szablonów umieszczonych w zasobach:
<ListBox Name="lista" Margin="5" HorizontalContentAlignment="Stretch" ItemTemplate="{StaticResource BookDataTemplate}"/>
46/85
Szablony danych
<DataTemplate x:Key="BookDataTemplate"> <Border ...> <Grid Margin="3"> <Grid.RowDefinitions> <RowDefinition/><RowDefinition/> </Grid.RowDefinitions> <Grid.ColumnDefinitions> <ColumnDefinition Width="auto" SharedSizeGroup="ikona"></ColumnDefinition> <ColumnDefinition ></ColumnDefinition> </Grid.ColumnDefinitions> <TextBlock Grid.Column="1" FontWeight="Bold" Text="{Binding Path=Title}"></TextBlock> <TextBlock Grid.Column="1" Grid.Row="1" Text="{Binding Path=Author}"></TextBlock> <Image Grid.RowSpan="2" MaxHeight="64" Source="{Binding Path=ImagePath, Converter={StaticResource ImagePathConverter}}"> </Image> </Grid> </Border> </DataTemplate>
47/85
...
<Grid Name="gridProductDetails" Grid.IsSharedSizeScope="True"> <ListBox Name="lista" Margin="5" HorizontalContentAlignment="Stretch" ItemTemplate="{StaticResource BookDataTemplate}"/>
48/85
Szablony danych
49/85
Szablony danych
<DataTemplate x:Key="BookDataTemplate"> ... <Button Click="cmdDoKoszyka" Tag="{Binding Path=ProductID}">Do koszyka...</Button></DataTemplate>
private void cmdDoKoszyka(object sender, RoutedEventArgs e){ Button cmd = (Button)sender; int productID = (int)cmd.Tag; //...}
50/85
Szablony danych
Inne rozwiązanie:
<DataTemplate x:Key="BookDataTemplate"> ... <Button Click="cmdDoKoszyka" Tag="{Binding}"> Do koszyka...</Button></DataTemplate>
private void cmdDoKoszyka(object sender, RoutedEventArgs e){ Button cmd = (Button)sender; Book book = (Book)cmd.Tag; lista.SelectedItem = book; //...}
51/85
Szablony danych
52/85
Szablony danych
• Różnicowanie szablonów danych:
<DataTemplate x:Key="BookDataTemplate"> <Border ... Background="{Binding Path=Price, Converter={StaticResource PriceToBackgroundConverter}}"> ... </Border></DataTemplate>
53/85
Szablony danych
54/85
Szablony danych
• Wybór szablonów:
public class BookTemplateSelector : DataTemplateSelector{ public override DataTemplate SelectTemplate(object item, DependencyObject container) { Book product = (Book)item; Window window = Application.Current.MainWindow; if (product.CategoryName == "Horror") { return (DataTemplate)window.FindResource("HorrorBookTemplate"); } else { return (DataTemplate)window.FindResource("DefaultBookTemplate"); } }}
55/85
Szablony danych
<Window.Resources> <DataTemplate x:Key="DefaultBookTemplate"> ... </DataTemplate> <DataTemplate x:Key="HorrorBookTemplate"> <Border Margin="5" BorderThickness="2" BorderBrush="Red" CornerRadius="4" Background="Black" TextBlock.Foreground="White"> ... </Border> </DataTemplate></Window.Resources>
<ListBox Name="lista" Margin="5" HorizontalContentAlignment="Stretch"> <ListBox.ItemTemplateSelector> <local:BookTemplateSelector/> </ListBox.ItemTemplateSelector></ListBox>
56/85
Szablony danych
57/85
Szablony danych
• Lepsze (bardziej uniwersalne) rozwiązanie:
public class SingleCriteriaHighlightTemplateSelector : DataTemplateSelector{ public DataTemplate DefaultTemplate { get; set; } public DataTemplate HighlightTemplate { get; set; } public string PropertyToEvaluate { get; set; } public string PropertyValueToHighlight { get; set; }
public override DataTemplate SelectTemplate(object item, DependencyObject container) { Product product = (Product)item; Type type = product.GetType(); PropertyInfo property = type.GetProperty(PropertyToEvaluate);
58/85
if (property.GetValue(product, null).ToString() == PropertyValueToHighlight) { return HighlightTemplate; } else { return DefaultTemplate; } }}
59/85
Szablony danych
<ListBox Name="lista" HorizontalContentAlignment="Stretch"> <ListBox.ItemTemplateSelector> <local:SingleCriteriaHighlightTemplateSelectorDefaultTemplate="{StaticResource DefaultBookTemplate}"HighlightTemplate="{StaticResource HorrorBookTemplate}"PropertyToEvaluate="CategoryName"PropertyValueToHighlight="Horror"> </local:SingleCriteriaHighlightTemplateSelector> </ListBox.ItemTemplateSelector></ListBox>">
• Uwaga: wybór szablonu następuje raz, w momencie tworzenia dowiązania. Jeśli zmiana stanu obiektu może wymagać wyboru innego szablonu, możemy wymusić to ręcznie (np. w PropertyChanged):
DataTemplateSelector selector = lista.ItemTemplateSelector;lista.ItemTemplateSelector = null;lista.ItemTemplateSelector = selector;
60/85
Zmiana układu listy
• Możemy zastąpić domyślny kontener listy:
<ListBox Name="lista" Margin="5" ScrollViewer.HorizontalScrollBarVisibility="Disabled"> <ListBox.ItemTemplateSelector> <local:BookTemplateSelector/> </ListBox.ItemTemplateSelector> <ListBox.ItemsPanel> <ItemsPanelTemplate> <WrapPanel></WrapPanel> </ItemsPanelTemplate> </ListBox.ItemsPanel></ListBox>
61/85
Zmiana układu listy
62/85
Widoki danych – Data Views
• Widok – znajduje się pomiędzy źródłem danych a powiązaną kontrolką.• To widok śledzi „aktualny element listy”, udostępnia sortowanie, filtrowanie,
grupowanie.• Widok jest typu:
◦ BindingListCollectionView – jeśli źródło danych jest typu IbindingList,◦ ListCollectionView – jeśli źródło nie jest typu IbindingList, ale Ilist◦ CollectionView – jeśli nie jest ani IbindingList, ani Ilist, a tylko Ienumerable.
• Dostęp do widoku:
ICollectionView view = CollectionViewSource.GetDefaultView(lista.ItemsSource);
63/85
Widoki danych – filtrowanie
• Pozwala pokazać jedynie podzbiór rekordów listy spełniających pewne warunki.
ListCollectionView view = (ListCollectionView)CollectionViewSource.GetDefaultView(lista.ItemsSource);view.Filter = FilterBook;
public bool FilterBook(Object item){ Book product = (Book)item; return (product.Price> 100);}
albo:
view.Filter = delegate(object item){ Book product = (Book)item; return (product.Price > 30);};
64/85
Widoki danych – filtrowanie
65/85
Widoki danych – filtrowanie
public class ProductByPriceFilter{ public decimal MinimumPrice { get; set; } public ProductByPriceFilter(decimal minimumPrice) { MinimumPrice = minimumPrice; } public bool FilterItem(Object item) { Book product = item as Book; if (product != null) { return (product.Price > MinimumPrice); } return false; }}
66/85
Widoki danych – filtrowanie
private void cmdFilter_Click(object sender, ...){ decimal minimumPrice; if (Decimal.TryParse(txtMinPrice.Text, out minimumPrice)) { ListCollectionView view = CollectionViewSource.GetDefaultView(lista.ItemsSource) as ListCollectionView; if (view != null) { ProductByPriceFilter filter = new ProductByPriceFilter(minimumPrice); view.Filter = filter.FilterItem; } }}
Usunięcie filtra:
view.Filter = null;
67/85
Widoki danych – filtrowanie
• Uwaga: nie można łączyć kilku filtrów – należy raczej zaprojektować filtr z wieloma warunkami.
68/85
Widoki danych – sortowanie
• Sortowanie na podstawie wskazanej własności danych:
ListCollectionView view = (ListCollectionView)CollectionViewSource.GetDefaultView(lista.ItemsSource);
view.SortDescriptions.Add(new SortDescription("Title", ListSortDirection.Ascending));
69/85
Widoki danych – sortowanie
70/85
Widoki danych – sortowanie
• Własna procedura sortowaniea (tylko dla ListCollectionView).
public class SortByNameLength : System.Collections.IComparer{ public int Compare(object x, object y) { Book bookX = (Book)x; Book bookY = (Book)y; return bookX.Title.Length.CompareTo(bookY.Title.Length); }}
view.CustomSort = new SortByNameLength();
71/85
Widoki danych – sortowanie
72/85
Widoki danych – grupowanie
• Jest zbliżone do sortowania:
view.GroupDescriptions.Add(new PropertyGroupDescription("Author"));
73/85
Widoki danych – grupowanie
74/85
Widoki danych – grupowanie
• A na czym polega różnica? Czyli: jak rozróżnić grupy?• ItemsControl.GroupStyle:
◦ ContainerStyle – styl dla każdego elementu grupy◦ ContainerStyleSelector◦ HeaderTemplate – nagłówek dla grupy◦ HeaderTemplateSelector◦ Panel – wybór panelu przechowującego grupę
75/85
Widoki danych – grupowanie
<ListBox ...> <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>
• Uwaga: nie dowiązujemy do obiektu danych, ale do PropertyGroupDescription, stąd własność Name.
76/85
Widoki danych – grupowanie
77/85
Widoki danych – grupowanie
• Grupowanie przedziałami:
public class PriceRangeProductGrouper : IValueConverter{ public int GroupInterval { get; set; } public object Convert(object value, Type targetType, object parameter, CultureInfo culture) { decimal price = (decimal)value;
78/85
if (price < GroupInterval) { return String.Format(culture, "Mniej niż {0:C}", GroupInterval); } else { int interval = (int)price / GroupInterval; int lowerLimit = interval * GroupInterval; int upperLimit = (interval + 1) * GroupInterval; return String.Format(culture, "{0:C} – {1:C}", lowerLimit, upperLimit); } } public object ConvertBack(...) { throw new NotSupportedException( "This converter is for grouping only."); }}
79/85
Widoki danych – grupowanie
view.SortDescriptions.Add(new SortDescription("Price", ListSortDirection.Ascending));
PriceRangeProductGrouper grouper = new PriceRangeProductGrouper();
grouper.GroupInterval = 10;
view.GroupDescriptions.Add(new PropertyGroupDescription("Price", grouper));
80/85
Widoki danych – grupowanie
81/85
Widoki danych – nawigacja
• Widok udostępnia metody i własności służące do nawigacji, np. Count, CurrentItem, CurrentPosition, MoveCurrentToFirst(), MoveCurrentToLast(), MoveCurrentToNext(), MoveCurrentToPrevious(), MoveCurrentToPosition().
• Można to robić nawet bez listy:
<Window ...> ... <Grid> ... <Label ...>Tytuł:</Label> <TextBox ...Text="{Binding Path=Title}" /> <Label ...>Autor:</Label> <TextBox ...Text="{Binding Path=Author}"/> ... <Button Name="cmdPrev" ...><</Button> <TextBlock Name="lblPosition" .../> <Button Name="cmdNext" ...>></Button> </Grid></Window>
82/85
Widoki danych – nawigacja
• W klasie okna zadeklarujmy referencję na widok:
private ListCollectionView view;
• W momencie ładowania okna stwórzmy lub załądujmy listę danych i pobierzmy widok:
List<Book> lst = new List<Book>();lst.Add(...);...
this.DataContext = lst;view = (ListCollectionView)CollectionViewSource.GetDefaultView(this.DataContext);view.CurrentChanged += view_CurrentChanged;
83/85
Widoki danych – nawigacja
private void view_CurrentChanged(object sender, EventArgs e){ lblPosition.Text = "Pozycja " + (view.CurrentPosition+1).ToString() + " z " + view.Count.ToString(); cmdPrev.IsEnabled = view.CurrentPosition > 0; cmdNext.IsEnabled = view.CurrentPosition < view.Count-1;}
private void cmdPrev_Click(object sender, RoutedEventArgs e){ view.MoveCurrentToPrevious();}
private void cmdNext_Click(object sender, RoutedEventArgs e){ view.MoveCurrentToNext();}
84/85
Widoki danych – nawigacja
85/85