Saturday, 23 March 2013

Part - 5: Applying style to ListBox, Continued

This post is the fifth in the series of ListBox control. Here is the reference of all the posts in this series for quick reference.

Part - 1: ListBox basics - Adding Items manually to ListBox and understanding properties of ListBox

Part - 2: Data Binding in ListBox (Binding ListBox to data source)

Part - 3: Binding ListBox to XML

Part - 4: Applying style to ListBox

Part - 5: Applying style to ListBox, Continued

Part - 6: ListBox custom ControlTemplate

 

In last post, we learned basic structure of ListBox control and a sample of how our ListBox control would look after applying basic style. In post 3, we have see how we can DataBind ListBox control. In this post we continue using same sample and apply style to different layers of ListBox control step by step.

ListBox DataBinding

A simple class “Fruit” to hold fruit information.

public class Fruit
{
public string Name { get; set; }
public string ImagePath { get; set; }
public Int32 Calories { get; set; }
public string Vitamins { get; set; }
}


Add simple ListBox control to XAML

<ListBox Name="lstFruits" DisplayMemberPath="Name"></ListBox>


In code behind (constructor), Create a list of Fruits and bind to ListBox control.

List<Fruit> myFruits = new List<Fruit>()
{
new Fruit() { Name = "Apple", ImagePath = "Images\\Apple.png", Calories = 61, Vitamins = "A,C" },
new Fruit() { Name = "Orange", ImagePath = "Images\\Orange.png", Calories = 51, Vitamins = "A,B1,C" },
new Fruit() { Name = "Grape", ImagePath = "Images\\Grapes.png", Calories = 40, Vitamins = "C" },
new Fruit() { Name = "Mango", ImagePath = "Images\\Mango.png", Calories = 80, Vitamins = "A,B1,C" }
};

lstFruits.ItemsSource = myFruits;


ItemSource property can be bound to any data source that implements IEnumerable. Generic List is one of them that implements IEnumerable.



Output:



 


1. Apply style to ListBox control

<Window x:Class="ListboxSample.ListBoxBasicStyle"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="ListBoxBasicStyle" Height="400" Width="400" FontFamily="Verdana" FontSize="13">
<Window.Resources>
<Style x:Key="ListBoxBlueStyle" TargetType="{x:Type ListBox}">
<Setter Property="Background" Value="#94B4D0"></Setter>
<Setter Property="Foreground" Value="#253E53"></Setter>
<Setter Property="BorderBrush" Value="#497CA5"></Setter>
<Setter Property="BorderThickness" Value="1"></Setter>
<Setter Property="Margin" Value="10"></Setter>
<Setter Property="Padding" Value="5"></Setter>
</Style>
</Window.Resources>
<Grid>
<ListBox Name="lstFruits" DisplayMemberPath="Name" Style="{StaticResource ResourceKey=ListBoxBlueStyle}">
</ListBox>
</Grid>
</Window>

Output:



Understanding XAML



We have added a ListBox control with list of static items.



We have added a style in window resources section, provided unique key to style that will be used to refer style later and set target type to ListBox control.


We have applied some properties directly on ListBox control.


2. Apply style to each ListBoxItem (ItemContainer)


In above style, If we see individual items in ListBox control still remain same and needs some formatting to display better. So we use ItemContainer to apply style to each ListBoxItem. One point to note here is that we are applying style to ItemContainer of each individual item and not the items itself.

<Style x:Key="ListBoxBlueStyle" TargetType="{x:Type ListBox}">
<Style.Resources>
<SolidColorBrush x:Key="{x:Static SystemColors.HighlightBrushKey}" Color="#B5E37D" />
<SolidColorBrush x:Key="{x:Static SystemColors.HighlightTextBrushKey}" Color="#253E53" />
</Style.Resources>
<Setter Property="Background" Value="#94B4D0"></Setter>
<Setter Property="Foreground" Value="#253E53"></Setter>
<Setter Property="BorderBrush" Value="#497CA5"></Setter>
<Setter Property="BorderThickness" Value="1"></Setter>
<Setter Property="Margin" Value="10"></Setter>
<Setter Property="Padding" Value="5"></Setter>
<Setter Property="ItemContainerStyle">
<Setter.Value>
<Style TargetType="{x:Type ListBoxItem}">
<Setter Property="Background" Value="#F5F5F5"></Setter>
<Setter Property="BorderBrush" Value="#497CA5"></Setter>
<Setter Property="Margin" Value="4"></Setter>
<Setter Property="Padding" Value="8"></Setter>
<Setter Property="BorderThickness" Value="1"></Setter>
<Style.Triggers>
<Trigger Property="IsMouseOver" Value="True">
<Setter Property="FontWeight" Value="Bold"></Setter>
</Trigger>
<Trigger Property="IsSelected" Value="True">
<Setter Property="FontWeight" Value="Normal"></Setter>
</Trigger>
</Style.Triggers>
</Style>
</Setter.Value>
</Setter>
</Style>

Output


ListBox custom style


Understanding XAML



ItemContainerStyle property is used to set style for each individual ListBoxItem. Here we create a new style with target type as ListBoxItem. In style we apply some basic properties on each item.


Triggers: Triggers is very handy feature in WPF to provide conditional formatting. Here, If mouse is moved over individual item then we set font weight to bold to provide a visual feedback. If item is in normal state or selected state then font weight is normal.


Small Hack


If we see the sample image at the start of post, it can be observed that selected item in ListBox has Green background. If we set Background property in “IsSelected” Trigger of ListBoxItem style then this will not work. Because ListBox control default template has Background of each ListBoxItem set to system color and can not be changed. So here is the small hack. We override system color for this particular ListBox style in resources section. Hence it will not have any effect on other controls and styles except this one


3. Changing default ItemsPanel


By default, ItemsPanel to hold items in ListBox control is VirtualizingStackPanel which arranges controls vertically. We can change the default panel to any control that derives from Panel like StackPanel, DockPanel, WrapPanel and so on.


In this example, we change the default panel to WrapPanel.

<Style x:Key="ListBoxBlueStyle" TargetType="{x:Type ListBox}">
<Style.Resources>
<SolidColorBrush x:Key="{x:Static SystemColors.HighlightBrushKey}" Color="#B5E37D" />
<SolidColorBrush x:Key="{x:Static SystemColors.HighlightTextBrushKey}" Color="#253E53" />
</Style.Resources>
<Setter Property="Background" Value="#94B4D0"></Setter>
<Setter Property="Foreground" Value="#253E53"></Setter>
<Setter Property="BorderBrush" Value="#497CA5"></Setter>
<Setter Property="BorderThickness" Value="1"></Setter>
<Setter Property="Margin" Value="10"></Setter>
<Setter Property="Padding" Value="5"></Setter>
<Setter Property="ScrollViewer.HorizontalScrollBarVisibility" Value="Disabled"></Setter>
<Setter Property="ItemContainerStyle">
<Setter.Value>
<Style TargetType="{x:Type ListBoxItem}">
<Setter Property="Width" Value="100"></Setter>
<Setter Property="Height" Value="100"></Setter>
<Setter Property="HorizontalContentAlignment" Value="Center"></Setter>
<Setter Property="Background" Value="#F5F5F5"></Setter>
<Setter Property="BorderBrush" Value="#497CA5"></Setter>
<Setter Property="Margin" Value="4"></Setter>
<Setter Property="Padding" Value="8"></Setter>
<Setter Property="BorderThickness" Value="1"></Setter>
<Style.Triggers>
<Trigger Property="IsMouseOver" Value="True">
<Setter Property="FontWeight" Value="Bold"></Setter>
</Trigger>
<Trigger Property="IsSelected" Value="True">
<Setter Property="FontWeight" Value="Normal"></Setter>
</Trigger>
</Style.Triggers>
</Style>
</Setter.Value>
</Setter>
<Setter Property="ItemsPanel">
<Setter.Value>
<ItemsPanelTemplate>
<WrapPanel></WrapPanel>
</ItemsPanelTemplate>
</Setter.Value>
</Setter>
</Style>

Output


ListBox custom style


Understanding XAML



We set ItemsPanel property to new template which has WrapPanel.


We have set one additional property to ListBox control ScrollViewer.HorizonalScrollBarVisibility to Hidden so that content will wrap automatically when there is no space left in horizontal direction.


We also set few other properties to ListBoxitem style like Width, Height and HorizontalContentAlignment to get the desired look and feel


4. Applying DataTemplate to each item


We have not yet touched on layer which is the actual data/content of each item in ListBox control. We can create a new DataTemplate and apply to data items in ListBox. DataTemplate is powerful feature of WPF that defines what and how to display data in control.

<Style x:Key="ListBoxBlueStyle" TargetType="{x:Type ListBox}">
<Style.Resources>
<SolidColorBrush x:Key="{x:Static SystemColors.HighlightBrushKey}" Color="#B5E37D" />
<SolidColorBrush x:Key="{x:Static SystemColors.HighlightTextBrushKey}" Color="#253E53" />
</Style.Resources>
<Setter Property="Background" Value="#94B4D0"></Setter>
<Setter Property="Foreground" Value="#253E53"></Setter>
<Setter Property="BorderBrush" Value="#497CA5"></Setter>
<Setter Property="BorderThickness" Value="1"></Setter>
<Setter Property="Margin" Value="10"></Setter>
<Setter Property="Padding" Value="5"></Setter>
<Setter Property="ScrollViewer.HorizontalScrollBarVisibility" Value="Disabled"></Setter>
<Setter Property="ItemContainerStyle">
<Setter.Value>
<Style TargetType="{x:Type ListBoxItem}">
<Setter Property="Width" Value="100"></Setter>
<Setter Property="Height" Value="100"></Setter>
<Setter Property="HorizontalContentAlignment" Value="Center"></Setter>
<Setter Property="Background" Value="#F5F5F5"></Setter>
<Setter Property="BorderBrush" Value="#497CA5"></Setter>
<Setter Property="Margin" Value="4"></Setter>
<Setter Property="Padding" Value="8"></Setter>
<Setter Property="BorderThickness" Value="1"></Setter>
<Style.Triggers>
<Trigger Property="IsMouseOver" Value="True">
<Setter Property="FontWeight" Value="Bold"></Setter>
</Trigger>
<Trigger Property="IsSelected" Value="True">
<Setter Property="FontWeight" Value="Normal"></Setter>
</Trigger>
</Style.Triggers>
</Style>
</Setter.Value>
</Setter>
<Setter Property="ItemsPanel">
<Setter.Value>
<ItemsPanelTemplate>
<WrapPanel></WrapPanel>
</ItemsPanelTemplate>
</Setter.Value>
</Setter>
<Setter Property="ItemTemplate">
<Setter.Value>
<DataTemplate>
<DockPanel LastChildFill="True">
<TextBlock Text="{Binding Path=Name}" DockPanel.Dock="Bottom" HorizontalAlignment="Center" Margin="5"></TextBlock>
<Image Source="{Binding Path=ImagePath}"></Image>
</DockPanel>
</DataTemplate>
</Setter.Value>
</Setter>
</Style>
<ListBox Name="lstFruits" Style="{StaticResource ResourceKey=ListBoxBlueStyle}"></ListBox>

Output


ListBox custom style


Understanding XAML


ItemTemplate property of ListBox control can be used to apply new DataTemplate. Inside setter we created a new DataTemplate that would be applied to each data item in ListBox. Till now we have used DisplayMemberPath property in ListBox control to specify what data (from Fruit class) would be displayed in ListBox items. In DisplayMemberPath, we could specify only single property of fruit class to be displayed. But DataTemplate provides flexibility to define what and how data items would be displayed. Here, We choose to display 2 properties Name and Image of fruit class. Moreover we have DockPanel that defines how this data would be displayed. Image is displayed at the top and Name at bottom.

No comments:

Post a Comment