A ListBox displays a list of items, and one (or more) item(s) can be selected. With WPF it’s possible to define a complete different look for ListBox elements. With templates items can be customized with a complete different look, and the ListBox itself can be customized with a control template. This way a ListBox can look like a bar chart. This article demonstrates changing the look of a ListBox to a bar chart.
Data Source
First data are needed. I’ve created a simple class that returns a list of numbers with the type IEnumerable<int>:
public class DataFactory { public IEnumerable<int> GetData() { return new int[] { 3, 5, 8, 6, 11, 7 }; } }
The class is instantiated by the ObjectDataProvider which invokes the method GetData to return the list of numbers.
<ObjectDataProvider x:Key="someData" ObjectType="{x:Type local:DataFactory}" MethodName="GetData" />
Binding the ListBox
A ListBox is bound to the data by setting the ItemsSource property, and defining the DataContext of a parent element from the ListBox.
<Grid DataContext="{Binding Source={StaticResource someData}}"> <ListBox ItemsSource="{Binding}" /> </Grid>
Running the application a default representation of the ListBox and its items is shown.
Item Template
To display the values as bars, a DataTemplate can be created. The template contains a red rectangle where the Width property is bound to the value returned from the collection item.
<DataTemplate x:Key="BarChartItemTemplate"> <Border Width="200" Height="50"> <Grid> <Rectangle Fill="Red" StrokeThickness="0" Height="40" Width="{Binding}" Margin="3" HorizontalAlignment="Right" VerticalAlignment="Bottom" /> </Grid> </Border> </DataTemplate>
With the ListBox, the ItemTemplate references the data template defined earlier.
<ListBox ItemsSource="{Binding}" ItemTemplate="{DynamicResource BarChartItemTemplate}" />
Running the application with its current state the rectangles are already visible as shown in the following figure. However, as the values returned from the collection are small, the rectangle has a small width.
To change the width of the rectangle based on the existing values returned from the collection it would be possible to use a converter. A converter class implements the interface IValueConverter and is configured with the binding. Another option – this one is used here – is to create a transformation. The rectangle is scaled by using a ScaleTransform setting ScaleX to 20 to scale the rectangle in x direction by 20.
<Rectangle Fill="Red" StrokeThickness="0" Height="40" Width="{Binding}" Margin="3" HorizontalAlignment="Right" VerticalAlignment="Bottom"> <Rectangle.LayoutTransform> <ScaleTransform ScaleX="20" /> </Rectangle.LayoutTransform> </Rectangle>
With this transformation the rectangles look better. With the configuration as it is now, the first values are on top of the ListBox, and the last items on bottom. A behavior that is usually expected with ListBox controls. However, using a bar chart display usually it looks better to arrange the values in a horizontal way, starting with the first value on the left. This is done next.
Rotating the Panel
To display the values horizontally within a bar chart the complete panel from the ListBox can be rotated. An ItemsPanelTemplate defines the panel for the items, and this is rotated by 90 degrees and scaled by –1 in the x direction to have the first items on the left side.
<ItemsPanelTemplate x:Key="BarChartItemsPanel"> <VirtualizingStackPanel IsItemsHost="True"> <VirtualizingStackPanel.LayoutTransform> <TransformGroup> <RotateTransform Angle="90" /> <ScaleTransform ScaleX="-1" ScaleY="1" /> </TransformGroup> </VirtualizingStackPanel.LayoutTransform> </VirtualizingStackPanel> </ItemsPanelTemplate>
With the ListBox control the ItemsPanelTemplate is assigned to the ItemsPanel property.
<ListBox ItemsSource="{Binding}" ItemTemplate="{DynamicResource BarChartItemTemplate}" ItemsPanel="{DynamicResource BarChartItemsPanel}" />
Running the application now, the items are displayed horizontally and the first items in the list are shown left.
Adding Values to the Display
If text values should be displayed in the ListBox as well beside the red rectangles, the ItemTemplate can be changed. Here, a TextBlock is added to the DataTemplate that binds the Text property to the same value as the Width property of the rectangle. As the TextBlock is in the same position of the grid as the rectangle it is just shown on top of it.
<DataTemplate x:Key="BarChartItemTemplate"> <Border Width="200" Height="50"> <Grid> <Rectangle Fill="Red" StrokeThickness="0" Height="40" Width="{Binding}" Margin="3" HorizontalAlignment="Right" VerticalAlignment="Bottom"> <Rectangle.LayoutTransform> <ScaleTransform ScaleX="20" /> </Rectangle.LayoutTransform> </Rectangle> <TextBlock Margin="20" FontWeight="Bold" HorizontalAlignment="Right" VerticalAlignment="Center" Text="{Binding}"> <TextBlock.LayoutTransform> <ScaleTransform ScaleX="-1" ScaleY="1" /> </TextBlock.LayoutTransform> </TextBlock> </Grid> </Border> </DataTemplate>
Now both the rectangles and the values are shown when running the application.
Does this look like a ListBox? It has the same functionality as a list box, just different look. Using XAML it’s easy to change the look in any way.
More information on WPF and XAML in my workshops and in the book Professional C# 4 and .NET 4.
Christian
Seriously helped me a lot for my similar problem.. Thanks for this wonderful article
Posted by: Indhy | 05/06/2013 at 11:58 AM