wtorek, 5 kwietnia 2011

Silverlight Toolkit - Charting cz. 2

Wracam do tematu który poruszałem całkiem nie tak dawno a dotyczył on tworzeniu wykresów w Silverlight (wszystko co tutaj jest opisane tyczy się także WPF - przykłady są pisane przy użyciu Silverlight).
Pisałem tam o bardziej skomplikowanych rzeczach niż tylko samo stworzenie wykresu. Kolejnym etapem będzie zmiana wyglądu tooltipa któremu oprócz standardowych informacji dodamy możliwość wyświetlania na jakim polu z legendy się znajduje.






Jak widać w podstawowej wersji oprócz samego wyglądu to i same informacje które przekazuje nie przekonują do polubienia go.
Spróbujmy na początek zmienić treść podpowiedzi. W tym celu potrzebujemy zmodyfikować styl, który tworzy podpowiedź. Skąd wziąć styl, który jest domyślnie ustawiony dla wykresu? Otóż domyślne style dla każdego z wykresów znajdują się w plikach typ_wykresuDataPoint. Folder ze stylami znajduje się w miejscu gdzie zainstalowany został silverlight toolkit. W moim przypadku ścieżka do folderu to:
C:\Program Files (x86)\Microsoft SDKs\Silverlight\v4.0\Toolkit\Apr10\Source\Source code.zip\Controls.DataVisualization.Toolkit\Charting\DataPoint
Tak więc z folderu tego pobieram interesujący mnie plik stylów, czyli PieDataPoint.
Następnie tworzymy nowy słownik zasobów w projekcie (Silverlight Resource Dictionary). Wklejamy do niego następnie definicję stylu znajdującego się w pliku PieDataPoint. Po tym etapie nasz słownik powinien zawierać następujące dane:


Code:
<ResourceDictionary
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" 
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
<Style TargetType="charting:PieDataPoint">
        <Setter Property="Background" Value="Orange"/>
        <Setter Property="BorderBrush" Value="White"/>
        <Setter Property="BorderThickness" Value="1"/>
        <Setter Property="IsTabStop" Value="False"/>
        <Setter Property="RatioStringFormat" Value="{}{0:p2}"/>
        <Setter Property="Template">
            <Setter.Value>
                <ControlTemplate TargetType="charting:PieDataPoint">
                    <Grid
                        x:Name="Root"
                        Opacity="0">
                        <VisualStateManager.VisualStateGroups>
                            <VisualStateGroup x:Name="CommonStates">
                                <VisualStateGroup.Transitions>
                                    <VisualTransition GeneratedDuration="0:0:0.1"/>
                                </VisualStateGroup.Transitions>
                                <VisualState x:Name="Normal"/>
                                <VisualState x:Name="MouseOver">
                                    <Storyboard>
                                        <DoubleAnimation
                                            Storyboard.TargetName="MouseOverHighlight"
                                            Storyboard.TargetProperty="Opacity"
                                            To="0.6"
                                            Duration="0"/>
                                    </Storyboard>
                                </VisualState>
                            </VisualStateGroup>
                            <VisualStateGroup x:Name="SelectionStates">
                                <VisualStateGroup.Transitions>
                                    <VisualTransition GeneratedDuration="0:0:0.1"/>
                                </VisualStateGroup.Transitions>
                                <VisualState x:Name="Unselected"/>
                                <VisualState x:Name="Selected">
                                    <Storyboard>
                                        <DoubleAnimation
                                            Storyboard.TargetName="SelectionHighlight"
                                            Storyboard.TargetProperty="Opacity"
                                            To="0.6"
                                            Duration="0"/>
                                    </Storyboard>
                                </VisualState>
                            </VisualStateGroup>
                            <VisualStateGroup x:Name="RevealStates">
                                <VisualStateGroup.Transitions>
                                    <VisualTransition GeneratedDuration="0:0:0.5"/>
                                </VisualStateGroup.Transitions>
                                <VisualState x:Name="Shown">
                                    <Storyboard>
                                        <DoubleAnimation
                                            Storyboard.TargetName="Root"
                                            Storyboard.TargetProperty="Opacity"
                                            To="1"
                                            Duration="0"/>
                                    </Storyboard>
                                </VisualState>
                                <VisualState x:Name="Hidden">
                                    <Storyboard>
                                        <DoubleAnimation
                                            Storyboard.TargetName="Root"
                                            Storyboard.TargetProperty="Opacity"
                                            To="0"
                                            Duration="0"/>
                                    </Storyboard>
                                </VisualState>
                            </VisualStateGroup>
                        </VisualStateManager.VisualStateGroups>
                        <Path
                            x:Name="Slice"
                            Data="{TemplateBinding Geometry}"
                            Fill="{TemplateBinding Background}"
                            Stroke="{TemplateBinding BorderBrush}"
                            StrokeMiterLimit="1">
                            <ToolTipService.ToolTip>
                                <StackPanel>
                                    <ContentControl Content="{TemplateBinding FormattedDependentValue}"/>
                                    <ContentControl Content="{TemplateBinding FormattedRatio}"/>
                                </StackPanel>
                            </ToolTipService.ToolTip>
                        </Path>
                        <Path
                            x:Name="SelectionHighlight"
                            Data="{TemplateBinding GeometrySelection}"
                            Fill="Red"
                            StrokeMiterLimit="1"
                            IsHitTestVisible="False"
                            Opacity="0"/>
                        <Path
                            x:Name="MouseOverHighlight"
                            Data="{TemplateBinding GeometryHighlight}"
                            Fill="White"
                            StrokeMiterLimit="1"
                            IsHitTestVisible="False"
                            Opacity="0"/>
                    </Grid>
                </ControlTemplate>
            </Setter.Value>
        </Setter>
    </Style>
</ResourceDictionary>

Najważniejszą dla nas częścią jest ta na samym praktycznie końcu:


Code:
<ToolTipService.ToolTip>
                                <StackPanel>
                                    <ContentControl Content="{TemplateBinding FormattedDependentValue}"/>
                                    <ContentControl Content="{TemplateBinding FormattedRatio}"/>
                                </StackPanel>
                            </ToolTipService.ToolTip>

Przed przystąpieniem do operacji uruchommy aplikację i zobaczmy czy coś się nie zmieniło:





Niestety. Jak widać cały wykres korzysta tylko z jednego koloru - pomarańczowego. Należy w takim wypadku ręcznie ustawić kolory dla danych przychodzących.
Nad tym problemem powiem - posiedziałem trochę. Na szczęście google zna odpowiedź na praktycznie każde pytanie i udało się odnaleźć rozwiązanie. 
Zanim zagooglowałem, próbowałem różnych rozwiązań. Na początek jako ofiarę wybrałem właściwość Palette. Pierwsza więc próba wyglądała następująco:


Code:
<vis:ResourceDictionaryCollection x:Key="MyPaletteColors">
            <ResourceDictionary>
                <Style x:Key="DataPointStyle" TargetType="Control">
                    <Setter Property="Background" Value="Red"/>
                </Style>
            </ResourceDictionary>
            <ResourceDictionary>
                <Style x:Key="DataPointStyle" TargetType="Control">
                    <Setter Property="Background" Value="Green"/>
                </Style>
            </ResourceDictionary>
            <ResourceDictionary>
                <Style x:Key="DataPointStyle" TargetType="Control">
                    <Setter Property="Background" Value="Blue"/>
                </Style>
            </ResourceDictionary>
            <ResourceDictionary>
                <Style x:Key="DataPointStyle" TargetType="Control">
                    <Setter Property="Background" Value="Orange"/>
                </Style>
            </ResourceDictionary>
            <ResourceDictionary>
                <Style x:Key="DataPointStyle" TargetType="Control">
                    <Setter Property="Background" Value="Purple"/>
                </Style>
            </ResourceDictionary>
        </vis:ResourceDictionaryCollection>

Niestety po ustawieniu właściwości w wykresie nic się nie zmieniło:


Code:
<toolkit:Chart Name="chrtAverageSalaary" Title="Average Salary">
            <toolkit:PieSeries DataPointStyle="{StaticResource PieStyle}" IndependentValueBinding="{Binding ItemName}" DependentValueBinding="{Binding Value}" 
                               Palette="{StaticResource MyPalette}"/>
        </toolkit:Chart>

Wykres nadal pozostawał pomarańczowy. No nic następnie spróbowałem zbindować paletę do właściwości globalnej wykresu:


Code:
<toolkit:Chart Name="chrtAverageSalaary" Title="Average Salary"  Palette="{StaticResource MyPalette}">
            <toolkit:PieSeries DataPointStyle="{StaticResource PieStyle}" IndependentValueBinding="{Binding ItemName}" DependentValueBinding="{Binding Value}" 
                              />
        </toolkit:Chart>

Jednak i tu szczał okazał się nie trafny. Stwierdzając, że niestety na dalsze próby nie ma zbyt wiele czasu rozpocząłem poszukiwania rozwiązania mojego problemu. Po kilku minutach poszukiwać znalazłem rozwiązanie problemu, które okazało się prostsze niż myślałem.
Paleta kolorów nadal była potrzebna, jednak zamiast ustawiać styl dla DataPointStyle, należy ustawić jego wartość w palecie kolorów. Spójrzmy na przykład który obrazuje poszukiwane rozwiązanie:

Okno:


Code:
<UserControl x:Class="Charting.MainPage"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
    xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
    mc:Ignorable="d"
    d:DesignHeight="300" d:DesignWidth="400" 
    xmlns:toolkit="http://schemas.microsoft.com/winfx/2006/xaml/presentation/toolkit" Loaded="UserControl_Loaded"
    xmlns:vis="clr-namespace:System.Windows.Controls.DataVisualization;assembly=System.Windows.Controls.DataVisualization.Toolkit">
    <Grid x:Name="LayoutRoot" Background="White" >
        <toolkit:Chart Name="chrtAverageSalaary" Title="Average Salary">
            <toolkit:PieSeries Palette="{StaticResource MyPalette}" IndependentValueBinding="{Binding ItemName}" DependentValueBinding="{Binding Value}"   />
        </toolkit:Chart>
    </Grid>
</UserControl>

Styl:


Code:
<ResourceDictionary
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:charting="clr-namespace:System.Windows.Controls.DataVisualization.Charting;assembly=System.Windows.Controls.DataVisualization.Toolkit"
    xmlns:vis="clr-namespace:System.Windows.Controls.DataVisualization;assembly=System.Windows.Controls.DataVisualization.Toolkit"
    >

    <ControlTemplate TargetType="charting:PieDataPoint" x:Key="MyPieDataPointTemplate">
        <Grid
            x:Name="Root"
            Opacity="0">
            <VisualStateManager.VisualStateGroups>
                <VisualStateGroup x:Name="CommonStates">
                    <VisualStateGroup.Transitions>
                        <VisualTransition GeneratedDuration="0:0:0.1"/>
                    </VisualStateGroup.Transitions>
                    <VisualState x:Name="Normal"/>
                    <VisualState x:Name="MouseOver">
                        <Storyboard>
                            <DoubleAnimation
                                Storyboard.TargetName="MouseOverHighlight"
                                Storyboard.TargetProperty="Opacity"
                                To="0.6"
                                Duration="0"/>
                        </Storyboard>
                    </VisualState>
                </VisualStateGroup>
                <VisualStateGroup x:Name="SelectionStates">
                    <VisualStateGroup.Transitions>
                        <VisualTransition GeneratedDuration="0:0:0.1"/>
                    </VisualStateGroup.Transitions>
                    <VisualState x:Name="Unselected"/>
                    <VisualState x:Name="Selected">
                        <Storyboard>
                            <DoubleAnimation
                                Storyboard.TargetName="SelectionHighlight"
                                Storyboard.TargetProperty="Opacity"
                                To="0.6"
                                Duration="0"/>
                        </Storyboard>
                    </VisualState>
                </VisualStateGroup>
                <VisualStateGroup x:Name="RevealStates">
                    <VisualStateGroup.Transitions>
                        <VisualTransition GeneratedDuration="0:0:0.5"/>
                    </VisualStateGroup.Transitions>
                    <VisualState x:Name="Shown">
                        <Storyboard>
                            <DoubleAnimation
                                Storyboard.TargetName="Root"
                                Storyboard.TargetProperty="Opacity"
                                To="1"
                                Duration="0"/>
                        </Storyboard>
                    </VisualState>
                    <VisualState x:Name="Hidden">
                        <Storyboard>
                            <DoubleAnimation
                                Storyboard.TargetName="Root"
                                Storyboard.TargetProperty="Opacity"
                                To="0"
                                Duration="0"/>
                        </Storyboard>
                    </VisualState>
                </VisualStateGroup>
            </VisualStateManager.VisualStateGroups>
            <Path
                x:Name="Slice"
                Data="{TemplateBinding Geometry}"
                Fill="{TemplateBinding Background}"
                Stroke="{TemplateBinding BorderBrush}"
                StrokeMiterLimit="1">
                <ToolTipService.ToolTip>
                    <StackPanel>
                        <ContentControl Content="{TemplateBinding FormattedDependentValue}"/>
                        <ContentControl Content="{TemplateBinding FormattedRatio}"/>
                    </StackPanel>
                </ToolTipService.ToolTip>
            </Path>
            <Path
                x:Name="SelectionHighlight"
                Data="{TemplateBinding GeometrySelection}"
                Fill="Red"
                StrokeMiterLimit="1"
                IsHitTestVisible="False"
                Opacity="0"/>
            <Path
                x:Name="MouseOverHighlight"
                Data="{TemplateBinding GeometryHighlight}"
                Fill="White"
                StrokeMiterLimit="1"
                IsHitTestVisible="False"
                Opacity="0"/>
        </Grid>
    </ControlTemplate>
    
    <vis:ResourceDictionaryCollection x:Key="MyPalette">
        <!-- Blue -->
        <ResourceDictionary>
            <RadialGradientBrush x:Key="Background" GradientOrigin="-0.1,-0.1" Center="0.075,0.015" RadiusX="1.05" RadiusY="0.9">
                <GradientStop Color="#FFB9D6F7"/>
                <GradientStop Color="#FF284B70" Offset="1"/>
            </RadialGradientBrush>
            <Style x:Key="DataPointStyle" TargetType="Control">
                <Setter Property="Template" Value="{StaticResource MyPieDataPointTemplate}"/>
                <Setter Property="Background" Value="{StaticResource Background}"/>
            </Style>
        </ResourceDictionary>
        <!-- Red -->
        <ResourceDictionary>
            <RadialGradientBrush x:Key="Background" GradientOrigin="-0.1,-0.1" Center="0.075,0.015" RadiusX="1.05" RadiusY="0.9">
                <GradientStop Color="#FFFBB7B5"/>
                <GradientStop Color="#FF702828" Offset="1"/>
            </RadialGradientBrush>
            <Style x:Key="DataPointStyle" TargetType="Control">
                <Setter Property="Template" Value="{StaticResource MyPieDataPointTemplate}"/>
                <Setter Property="Background" Value="{StaticResource Background}"/>
            </Style>
        </ResourceDictionary>
        <!-- Light Green -->
        <ResourceDictionary>
            <RadialGradientBrush x:Key="Background" GradientOrigin="-0.1,-0.1" Center="0.075,0.015" RadiusX="1.05" RadiusY="0.9">
                <GradientStop Color="#FFB8C0AC"/>
                <GradientStop Color="#FF5F7143" Offset="1"/>
            </RadialGradientBrush>
            <Style x:Key="DataPointStyle" TargetType="Control">
                <Setter Property="Template" Value="{StaticResource MyPieDataPointTemplate}"/>
                <Setter Property="Background" Value="{StaticResource Background}"/>
            </Style>
        </ResourceDictionary>
    </vis:ResourceDictionaryCollection>
</ResourceDictionary>

Tak więc pierwszy etap - przywrócenie kolorów mamy za sobą. Czas zabrać się za ToolTip. Aby dodać informację na temat tego na czym aktualnie stoimy wystarczy dopisać jedną linijkę:


Code:
<ToolTipService.ToolTip>
                    <ToolTip>
                        <StackPanel>
                            <ContentControl Content="{TemplateBinding FormattedDependentValue}"/>
                            <ContentControl Content="{TemplateBinding FormattedRatio}"/>
                            <ContentControl Content="{TemplateBinding FormattedIndependentValue}"/>
                        </StackPanel>
                    </ToolTip>
                </ToolTipService.ToolTip>

Uzyskany efekt będzie następujący:

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.