PwT.N W7

Transkrypt

PwT.N W7
Budowa aplikacji w technologii .NET
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>
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: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;
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" ...>&lt;</Button>
<TextBlock Name="lblPosition" .../>
<Button Name="cmdNext" ...>&gt;</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

Podobne dokumenty