Budowa aplikacji w technologii.net wykład 7 konwersja, walidacja, szablony, widoki 1/85 <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>
2/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;
3/85
4/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.
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
6/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>
7/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"/>
Value Converter 8/85
9/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.
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
11/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.
Tworzenie obiektów z Value Converterem 12/85
13/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();
14/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>
Formatowanie warunkowe 15/85
16/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>
17/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();
MultiConverter 18/85
19/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;
20/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>
Walidacja 21/85
22/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).
23/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;
24/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;
25/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;
26/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
Walidacja 27/85
28/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;
29/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.");
30/85 Walidacja... if ((price < Min) (price > Max)) return new ValidationResult(false, "Not in the range " + Min + " to " + Max + "."); else return new ValidationResult(true, null);
31/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.
Walidacja 32/85
33/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());
Walidacja 34/85
35/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 //...
36/85 Walidacja private bool FormHasErrors(out string message) StringBuilder sb = new StringBuilder(); GetErrors(sb, gridproductdetails); message = sb.tostring(); return message!= "";
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
38/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>
Walidacja 39/85
40/85 Walidacja <TextBlock... ToolTip="Binding ElementName=adornerPlaceholder, Path=AdornedElement.(Validation.Errors)[0].ErrorContent">*</TextBlock>... <AdornedElementPlaceholder Name="adornerPlaceholder" />
41/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>
Szablony danych 42/85
43/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>
Szablony danych 44/85
45/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>
46/85 Szablony danych Korzystanie z szablonów umieszczonych w zasobach: <ListBox Name="lista" Margin="5" HorizontalContentAlignment="Stretch" ItemTemplate="StaticResource BookDataTemplate"/>
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
48/85... <Grid Name="gridProductDetails" Grid.IsSharedSizeScope="True"> <ListBox Name="lista" Margin="5" HorizontalContentAlignment="Stretch" ItemTemplate="StaticResource BookDataTemplate"/>
Szablony danych 49/85
50/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; //...
51/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; //...
Szablony danych 52/85
53/85 Szablony danych Różnicowanie szablonów danych: <DataTemplate x:key="bookdatatemplate"> <Border... Background="Binding Path=Price, Converter=StaticResource PriceToBackgroundConverter">... </Border> </DataTemplate>
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
56/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>
Szablony danych 57/85
58/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);
59/85 if (property.getvalue(product, null).tostring() == PropertyValueToHighlight) return HighlightTemplate; else return DefaultTemplate;
60/85 Szablony danych <ListBox Name="lista" HorizontalContentAlignment="Stretch"> <ListBox.ItemTemplateSelector> <local:singlecriteriahighlighttemplateselector DefaultTemplate="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;
61/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>
Zmiana układu listy 62/85
63/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);
64/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); ;
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
67/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;
68/85 Widoki danych filtrowanie Uwaga: nie można łączyć kilku filtrów należy raczej zaprojektować filtr z wieloma warunkami.
69/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));
Widoki danych sortowanie 70/85
71/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();
Widoki danych sortowanie 72/85
73/85 Widoki danych grupowanie Jest zbliżone do sortowania: view.groupdescriptions.add(new PropertyGroupDescription("Author"));
Widoki danych grupowanie 74/85
75/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ę
76/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.
Widoki danych grupowanie 77/85
78/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;
79/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.");
80/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));
Widoki danych grupowanie 81/85
82/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>
83/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;
84/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();
Widoki danych nawigacja 85/85