• Nie Znaleziono Wyników

Budowa aplikacji w technologii .NET wykład 7 – konwersja, walidacja, szablony, widoki

N/A
N/A
Protected

Academic year: 2021

Share "Budowa aplikacji w technologii .NET wykład 7 – konwersja, walidacja, szablony, widoki"

Copied!
85
0
0

Pełen tekst

(1)

wykł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>

(2)

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)
(4)

• 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.

(5)

[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;

} }

(6)

• 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)

• 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}}"/>

(8)
(9)

• 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.

(10)

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));

(11)

• 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.

(12)
(13)

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)

<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>

(15)
(16)

• 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)

• 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();

} }

(18)
(19)

• 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)

• 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>

(21)
(22)

• 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)

• 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)

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)

• 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)

<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

(27)
(28)

• 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)

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)

...

if ((price < Min) || (price > Max)) {

return new ValidationResult(false,

"Not in the range " + Min + " to " + Max + ".");

} else {

return new ValidationResult(true, null);

} } }

(31)

<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.

(32)
(33)

• 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());

} }

(34)
(35)

• 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)

private bool FormHasErrors(out string message) {

StringBuilder sb = new StringBuilder();

GetErrors(sb, gridProductDetails);

message = sb.ToString();

return message != "";

}

(37)

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);

} }

(38)

• 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>

(39)
(40)

<TextBlock ... ToolTip="{Binding

ElementName=adornerPlaceholder,

Path=AdornedElement.(Validation.Errors)[0]

.ErrorContent}">*</TextBlock>

...

<AdornedElementPlaceholder Name="adornerPlaceholder" />

(41)

• 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>

(42)
(43)

<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>

(44)
(45)

• 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)

• Korzystanie z szablonów umieszczonych w zasobach:

<ListBox Name="lista" Margin="5"

HorizontalContentAlignment="Stretch"

ItemTemplate="{StaticResource BookDataTemplate}"/>

(47)

<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>

(48)

<Grid Name="gridProductDetails"

Grid.IsSharedSizeScope="True">

<ListBox Name="lista" Margin="5"

HorizontalContentAlignment="Stretch"

ItemTemplate="{StaticResource BookDataTemplate}"/>

(49)
(50)

<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)

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;

//...

}

(52)
(53)

• Różnicowanie szablonów danych:

<DataTemplate x:Key="BookDataTemplate">

<Border ... Background="{Binding Path=Price,

Converter={StaticResource PriceToBackgroundConverter}}">

...

</Border>

</DataTemplate>

(54)
(55)

• 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");

} } }

(56)

<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>

(57)
(58)

• 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)

{

return HighlightTemplate;

} else {

return DefaultTemplate;

} } }

(60)

<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)

• 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>

(62)
(63)

• 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)

• 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);

};

(65)
(66)

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;

} }

(67)

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)

Uwaga: nie można łączyć kilku filtrów – należy raczej zaprojektować filtr z wieloma warunkami.

(69)

• Sortowanie na podstawie wskazanej własności danych:

ListCollectionView view =

(ListCollectionView)CollectionViewSource.GetDefaultView(lista .ItemsSource);

view.SortDescriptions.Add(new SortDescription("Title", ListSortDirection.Ascending));

(70)
(71)

• 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();

(72)
(73)

• Jest zbliżone do sortowania:

view.GroupDescriptions.Add(new

PropertyGroupDescription("Author"));

(74)
(75)

• 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)

<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.

(77)
(78)

• 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)

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)

view.SortDescriptions.Add(new SortDescription("Price", ListSortDirection.Ascending));

PriceRangeProductGrouper grouper = new PriceRangeProductGrouper();

grouper.GroupInterval = 10;

view.GroupDescriptions.Add(new

PropertyGroupDescription("Price", grouper));

(81)
(82)

• 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" ...>&lt;</Button>

<TextBlock Name="lblPosition" .../>

<Button Name="cmdNext" ...>&gt;</Button>

</Grid>

</Window>

(83)

• 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)

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();

}

(85)

Cytaty

Powiązane dokumenty

• IsColumnWidthFlexible – decyduje, czy kontener może dopasować rozmiar kolumny (jeśli false, użyta jest dokładna wartość ColumnWidth, jeśli true – ColumnWidth to

• IsFilled – jeśli true, wnętrze jest wypełniane przy użyciu pędzla Path.Fill PathFigure to kształt który narysowany jest linią składającą się z wielu

Wymaga stworzenia obiektu animacji, ustawienia własności i odpalenia jej przy pomocy metody BeginAnimation() (zadeklarowanej w interfejsie

• Jeśli chcemy aby szablony były używane w obrębie wielu aplikacji należy stworzyć osobny słownik zasobów (Resource Dictionary) – Solution Explorer -&gt; Add -&gt;. New

• Positions – kolekcja wszystkich punktów siatki (wierzchołków trójkątów). Często jeden punkt jest wierzchołkiem kilku trójkątów, np: sześcian wymaga 12 trójkątów,

Szablony uniwersalnych obiektowych struktur danych umożliwiają powielanie kodu dla różnych typów elementów przechowywanych w tych strukturach:. pochodzących

[r]

prawdopodobie«stwa wyst¡pienia okre±lonych warto±ci rzutu momentu p¦du cz¡stki na o±