niedziela, 3 kwietnia 2011

MultiValueConverter oraz coś o bindowaniu i wyświetlaniu danych w kontrolkach wykorzystujących listy

W poprzednim artykule opisałem w jaki sposób konwertować pojedyncza wartość. W tym artykule pokażę, w jaki sposób radzić sobie z łączeniem wielu wartości. Oprócz tego omówię zastosowanie szablonów i stylów podczas bindowania do kolekcji typu list. Tak jak poprzednio informacje tyczą się zarówno Silverlight jak i WPF. Przykłady będą oparte o WPF - zastosowanie w Silverlight jest takie samo.


1. Konwersja kilku wartości w jedną
Korzystając z ostatniego przykładu (poprzedni post), spróbujemy wyświetlić cenę i datę jej wprowadzenia, efekt o który nam chodzi wygląda następująco:


Aby uzyskać taki efekt możemy postąpić na dwa sposoby. Do tego celu wykorzystamy ostatnio poznane właściwości ale w wersji multi:

String Format:


Code:
<TextBlock Grid.Row="4" Grid.ColumnSpan="2">
                <TextBlock.Text>
                    <MultiBinding StringFormat="{}{0:C}, {1:d}">
                        <Binding ElementName="dgridProducts" Path="SelectedItem.StandardCost" />
                        <Binding ElementName="dgridProducts" Path="SelectedItem.SellStartDate" />
                    </MultiBinding>
                </TextBlock.Text>
            </TextBlock>

Wykorzystaliśmy tutaj String Format. Teraz kolejny przykład wykorzystujący ValueInStockConverter. Jest to mechanizm podobny do poprzednio pokazanego Value converter. Najważniejszą różnicą jest to, iż otrzymuje on jako argument tablicę obiektów wartości z których tworzymy wynik:


Code:
<TextBlock Grid.Row="4" Grid.ColumnSpan="2">
                <TextBlock.Text>
                    <MultiBinding Converter="{StaticResource dpc}">
                        <Binding ElementName="dgridProducts" Path="SelectedItem.StandardCost" />
                        <Binding ElementName="dgridProducts" Path="SelectedItem.SellStartDate" />
                    </MultiBinding>
                </TextBlock.Text>
            </TextBlock>


Code:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows.Data;

namespace WpfApplication1
{
    public class DatePriceConverter : IMultiValueConverter
    {
        public object Convert(object[] values, Type targetType, object parameter, System.GlobalizationCultureInfo culture)
        {
            decimal price = (decimal)values[0];
            DateTime sellDate = (DateTime)values[1];

            return string.Format("{0:C}, {1:d}", price, sellDate);
        }

        public object[] ConvertBack(object value, Type[] targetTypes, object parameter, System.GlobalizationCultureInfo culture)
        {
            throw new NotImplementedException();
        }
    }
}

Wynik w drugim przypadku jest taki sam jak w pierwszym.


2. Kontrolki wykorzystujące listy do wyświetlania elementów
W tym rozdziale postaram się przybliżyć kilka ciekawych technik pozwalających w łatwy sposób osiągnąć ciekawy efekt wizualny, podczas bindowania do kontrolek typu listy jak np.drzewa, listy, pasek narzędzi, elementy menu, pasek stanu itd. Zbudowane są one z małych bloczków zwanych elementami. Element to np. przycisk na pasku narzędzi. W przypadku tych kontrolek mamy możliwość bindowania do dwóch kluczowych własności: stylu i szablonu.

Podstawowe właściwości tych kontrolek to m.in.:
ItemSource - źródło danych
DisplayMemberPath - właściwość która ma zostać wyświetlona dla poszczególnych elementów
ItemStringFormat - działa w taki sam sposób jak wcześciej omawiany Binding.StringFormat
ItemContainerStyle - pozwala na ustawienie stylu konteneru wyświetlanego elementu
ItemContainerStyleSelector - pozwala na ustawienie różnych styli dla różnych elementów listy
AlternationCount - pozwala na wyświetlanie na przemian stylów - np wartość dwa spowoduje wyświetlanie danego stylu co drugi element
ItemTemplate - pozwala nadać szablon dla tworzonych elementów
ItemsPanel - Panel na którym umieszczone są wszystkie elementy (najczęściej VirtualizingStackPanel

O ile 3 pierwsze właściwości są zapewne wszystkim znane i wykorzystywane, o tyle warto wspomnieć o pozostałych.

ItemContainerStyle
Przejdźmy od razu do przykładu:


Code:
<ListBox Name="lboxProducts" DisplayMemberPath="Name" Width="250">
            <ListBox.ItemContainerStyle>
                <Style TargetType="{x:Type ListBoxItem}">
                    <Setter Property="Background" Value="BlueViolet" />
                    <Setter Property="Margin" Value="4" />
                    <Setter Property="Padding" Value="4" />
                    <Style.Triggers>
                        <Trigger Property="IsSelected" Value="True">
                            <Setter Property="Background" Value="Yellow" />
                            <Setter Property="Foreground" Value="White" />
                        </Trigger>
                    </Style.Triggers>
                </Style>
            </ListBox.ItemContainerStyle>
        </ListBox>

W łatwy sposób zmieniliśmy wygląd elementów ListBoxa. Możemy teraz dla przykładu zmienić poszczególne elementy na konkretne kontrolki np. RadioButtony czy też CheckBoxy:


Code:
<Window x:Class="WpfApplication1.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        Title="MainWindow" Height="554" Width="996" Loaded="Window_Loaded"
        xmlns:local="clr-namespace:WpfApplication1">
    <Window.Resources>
        <Style x:Key="RadioButtonListStyle" TargetType="{x:Type ListBox}">
            <Setter Property="ItemContainerStyle">
                <Setter.Value>
                    <Style TargetType="{x:Type ListBoxItem}" >
                        <Setter Property="Margin" Value="2" />
                        <Setter Property="Template">
                            <Setter.Value>
                                <ControlTemplate TargetType="{x:Type ListBoxItem}">
                                    <RadioButton Focusable="False" IsChecked="{Binding Path=IsSelected, Mode=TwoWay, RelativeSource={RelativeSource TemplatedParent} }">
                                        <ContentPresenter></ContentPresenter>
                                    </RadioButton>
                                </ControlTemplate>
                            </Setter.Value>
                        </Setter>
                    </Style>
                </Setter.Value>
            </Setter>
        </Style>
    </Window.Resources>
    <Grid>
        <ListBox Name="lboxProducts" DisplayMemberPath="Name" Width="250" Style="{StaticResource ResourceKey=RadioButtonListStyle}">
        </ListBox>
    </Grid>
</Window>

Zmiana stylu co w n-tym elemencie
Bardzo często można spotkać się z efektem zmiany stylu co n-tego elementu, np. co drugi posiada dodatkowy cień. Silverlight jak i WPF posiada wbudowany mechanizm umożliwiający uzyskanie takiego elementu w bardzo łatwy i szybki sposób. Zobaczmy na przykład:


Code:
<Window x:Class="WpfApplication1.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        Title="MainWindow" Height="554" Width="996" Loaded="Window_Loaded"
        xmlns:local="clr-namespace:WpfApplication1">
    <Grid>
        <ListBox Name="lboxProducts" DisplayMemberPath="Name" Width="250" AlternationCount="2">
            <ListBox.ItemContainerStyle>
                <Style TargetType="{x:Type ListBoxItem}">
                    <Style.Triggers>
                        <Trigger Property="ItemsControl.AlternationIndex" Value="1">
                            <Setter Property="Background" Value="LightBlue" />
                        </Trigger>
                    </Style.Triggers>
                </Style>
            </ListBox.ItemContainerStyle>
        </ListBox>
    </Grid>
</Window>

Efekt wywołania:



Style selector
Jeżeli potrzebujemy, aby styl został przypisany pod względem odpowiedniego warunku np. w zależności od danych zawartych w konkretnym elemencie, musimy posłużyć się implementacją własnej klasy, która dziedziczy po klasie StyleSelector. 
Zobaczmy na przykład:


Code:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows.Controls;
using System.Windows;

namespace WpfApplication1
{
    class ProductByColorSelector : StyleSelector
    {
        public override System.Windows.Style SelectStyle(object item, System.WindowsDependencyObject container)
        {
            Product product = (Product)item;
            string style = string.Empty;
            Window window = Application.Current.MainWindow;
            Style returnStyle = null;
            switch (product.Color)
            {
                case "Red":
                    returnStyle = (Style)window.FindResource("Red");
                    break;
                case "Blue":
                    returnStyle = returnStyle = (Style)window.FindResource("Blue");;
                    break;
                case "Yellow":
                    returnStyle = returnStyle = (Style)window.FindResource("Yellow");
                    break;
                default:
                    return base.SelectStyle(item, container);
                    break;
            }

            return returnStyle; 
        }
    }
}

<Window x:Class="WpfApplication1.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        Title="MainWindow" Height="554" Width="996" Loaded="Window_Loaded"
        xmlns:local="clr-namespace:WpfApplication1">
    <Window.Resources>
        <Style x:Key="Yellow" TargetType="{x:Type ListBoxItem}">
            <Setter Property="Background" Value="LightYellow" />
            <Setter Property="Padding" Value="2" />
        </Style>
        <Style x:Key="Red" TargetType="{x:Type ListBoxItem}">
            <Setter Property="Background" Value="OrangeRed" />
            <Setter Property="Padding" Value="2" />
        </Style>
        <Style x:Key="Blue" TargetType="{x:Type ListBoxItem}">
            <Setter Property="Background" Value="AliceBlue" />
            <Setter Property="Padding" Value="2" />
        </Style>
        <local:ProductByColorSelector x:Key="styleSelector" />
    </Window.Resources>
    <Grid>
        <ListBox Name="lboxProducts" DisplayMemberPath="Name" Width="250" ItemContainerStyleSelector="{StaticResource ResourceKey=styleSelector}">
        </ListBox>
    </Grid>
</Window>

Efekt który uzyskamy będzie następujący:


Oczywiście można się posłużyć bardziej skomplikowaną klasą bardziej generyczną i np. jako nazwy stylów wprowadzić właściwości i odpowiednio wprowadzić je podczas deklaracji stylu dla kontrolki.


Tyle na dzisiaj odnośnie stylów dla elementów w listboxach i podobnych kontrolkach. Informacje które tutaj zawarte są (jak i w poprzednim poście) przydadzą się aby zrozumieć kolejny mój post odnośnie zmiany wyglądu ToolTipa dla wykresów.

Brak komentarzy:

Prześlij komentarz