Silverlight and WPF ComboBox with TreeView inside

The TreeView is useful control, but it has one shortcoming: it occupies too much space in the application. That’s why I’ve decided to create the custom control which looks like a ComboBox but displays the TreeView instead of the list.

Here is the final result:

Although this control looks quite simple, the actual implementation isn’t clear and takes long time.
Here is the sequence of steps:

1) Custom TreeView and TreeViewItem. They provide the following functionality:

  • Allow to expand and  select an item from a view model (It isn’t possible to select an item of the TreeView using other ways)
  • The event which is fired when a user clicks a TreeViewItem (so it will be possible to close the ComboBox)

2) Interface for an item of the ItemsSource collection

public interface ITreeViewItemModel
{
	string SelectedValuePath { get; }
	string DisplayValuePath { get; }

	bool IsExpanded { get; set; }
	bool IsSelected { get; set; }

	IEnumerable<ITreeViewItemModel> GetHierarchy();
	IEnumerable<ITreeViewItemModel> GetChildren();
}

Members of this interface:

  • IsExpanded – allows to expand the TreeViewItem from the bound view model. Must be implemented as the INotifyPropertyChanged property.
  • IsSelected – allows to select the TreeViewItem from the bound view model. Must be implemented as the INotifyPropertyChanged property.
  • SelectedValuePath – the unique string (unique item id) that will be used to select and expand the treeview control
  • DisplayValuePath – the content that will be displayed at the header when the combobox is closed
  • GetHierarchy – returns the item and its parents
  • GetChildren – returns child items of the current item

3. Create the Combobox

It is the most difficult part of the implementation. I was forced to create many methods to provide the connection between Combobox and TreeView. But although there is many private methods, there is only two public properties:

  • SelectedItem – now it is possible to get or set this item and it will be selected in the treeview.
  • SelectedHierarchy – it wasn’t necessary to create this property, but it wasn’t difficult so I’ve decided to implement it. Use list of strings instead of the actual item.

4. Add it to a view and bind with a view model:

<UserControl.Resources>
    <Windows:HierarchicalDataTemplate x:Key="treeViewDataTemplate" 
                   ItemsSource="{Binding Children}">
        <TextBlock Text="{Binding Title}" />
    </Windows:HierarchicalDataTemplate>
</UserControl.Resources>
<Grid x:Name="LayoutRoot" Background="White">
        <local:ComboBoxTreeView ItemsSource="{Binding Items}" 
             SelectedItem="{Binding SelectedItem}" 
             ItemTemplate="{StaticResource treeViewDataTemplate}"
             HorizontalAlignment="Center" VerticalAlignment="Top" />
</Grid>

The ItemTemplate property is obligatory property and must be of the HierarchicalDataTemplate type.

Silverlight version: ComboBoxTreeView.zip
WPF version: WpfComboboxTreeview.zip
The updated source code which supports the ‘SelectionChanged’ event of the combobox: ComboBoxTreeViewEventsSupport.zip

Silverlight TimeLine Chart

In this article I will create the chart which looks like the google timeline control. Here is the screenshot of the google chart which is displayed on this page:

My implementation isn’t exactly the same, but is very close:

  • it allows to move left and right;
  • it displays a line on the mouse over event and set a line if a user clicks somewhere;
  • this chart binds a selected value or a mouse over value to the closest item of the chart;
  • the chart is scrolled automatically if a user clicks at some distance from the middle of the chart.

In my implementation I’ve decided to use combination of the user control and the custom control.
The custom control has the following responsibilities:

  • displays a line if the mouse is over the chart
  • displays another line if a user clicks on the chart
  • provides two properties: MouseOverValue and ClickValue

The user control and its View Model provide operations with data, such as changing the range and binding the selected point to the nearest data value.

The code files are too large to paste them here, so I will explain only the User Control:

<Button Content="&lt;" Width="{StaticResource ButtonSize}"
		Height="{StaticResource ButtonSize}"
		Command="{Binding MoveLeftCommand}" Margin="5" Grid.Row="1" />
<local:ExtendedChart x:Name="chart" Grid.Column="1" Grid.Row="1"
		MouseOverValue="{Binding MouseOverDate, Mode=TwoWay}"
		ClickValue="{Binding SelectedDate, Mode=TwoWay}">
  <local:ExtendedChart.Series>
    <local:ExtendedColumnSeries ItemsSource="{Binding Items}"
			DependentValuePath="Value" IndependentValuePath="Date"
			DataPointStyle="{StaticResource SimpleColumnStyle}" />
  </local:ExtendedChart.Series>
  <local:ExtendedChart.Axes>
    <Charting:DateTimeAxis Orientation="X" IntervalType="Hours"
			Interval="6" Minimum="{Binding StartDate}"
			Maximum="{Binding EndDate}" />
  </local:ExtendedChart.Axes>
</local:ExtendedChart>
<Button Content="&gt;" Grid.Column="2"
		Width="{StaticResource ButtonSize}"
		Height="{StaticResource ButtonSize}"
		Command="{Binding MoveRightCommand}" Margin="5" Grid.Row="1" />

There are two buttons and one chart.

The buttons scroll the visible content left or right, they are square with some constant size and are bound to the commands of the View Model.

The chart has two bound properties (and these properties have two way binding because they will be updated in the View Model), one column series and one custom Axis.
I have extended the default column series because it has spaces between columns, also I’ve applied custom data point style to the series in which I’ve removed borders and have left only blue rectangle.
The axis displays ticks and labels each 6 hours and is restricted by the range which is set in the View Model.

Here is the complete source code: TimeLineChart.zip

Silverlight Status Indicator Control

In this article I’m going to create the control similar to the status indicator control which is used for displaying KPIs in the Business Intelligence Studio.

Here is the screenshot of the ComboBox in BI Studio:

The concept of KPI is rather complex, but all that you need to know is that it is displayed differently depending on the state of the value:

  • Good state (green)
  • Moderate state (yellow)
  • Bad state (red)

So let’s open Visual Studio, create new Silverlight Application, make right mouse button click on the project, select Add New Item and then Silverlight Templated Control:

Very often it is difficult to choose between User Control and Templated Control, but in this example I use the second control because it allows to change styles and control templates.

After adding new item you have two files: the code file and the file Generic.xaml which contains default styles of all templated controls:

Now I start to implement behavior of the control.

Read more of this post

Silverlight TabControl with data binding

The TabControl is commonly used control, but the team which develops the Silverlight Toolkit hasn’t added the data binding support. Although it is promised that the TabControl will be fixed in Silverlight 5, there is long time to wait.

That’s why I decided to extend the current control.

I added the following public properties:

  • ItemsSource
  • SelectedItem
  • ItemTemplate
  • ContentTemplate

These properties are the same which the WPF TabControl has, and now it is possible to write almost the same code:

        <local:ExtendedTabControl ItemsSource="{Binding Items}" SelectedItem="{Binding SelectedItem, Mode=TwoWay}">
            <local:ExtendedTabControl.ItemTemplate>
                <DataTemplate>
                    <TextBlock Text="{Binding Header}" Foreground="Red" />
                </DataTemplate>
            </local:ExtendedTabControl.ItemTemplate>
            <local:ExtendedTabControl.ContentTemplate>
                <DataTemplate>
                    <TextBlock Text="{Binding Content}" FontWeight="Bold"/>
                </DataTemplate>
            </local:ExtendedTabControl.ContentTemplate>
        </local:ExtendedTabControl>

Here is the completed solution: BindableTabControl.zip

In this post I updated the control so that it supports TabItems as templates.
Also I implemented the version with scrollbale items.

Silverlight ComboBox Prompt text if an item isn’t selected

If you work with Silverlight you can notice that default ComboBox displays empty text if it doesn’t have a selected item.

This behaviour is far from the expected, and one of the workaround is to select the first item from the list. It is good, but it can’t be used in every situation.

So I extended the default control and now it has the following features:

1. Displays a prompt if a user hasn’t selected an item.

2. Displays a prompt if the combobox doesn’t have items at all.

To achieve this I handle only two events:

  • SelectionChanged – to add or remove the “Select Item…” prompt if it is necessary.
  • OnItemsChanged – to add or remove the “No Items…” promptif it is necessary.

Here is complete source code with comments:

    [TemplateVisualState(Name = ExtendedComboBox.StateNormal, GroupName = ExtendedComboBox.GroupItemsSource)]
    [TemplateVisualState(Name = ExtendedComboBox.StateNotSelected, GroupName = ExtendedComboBox.GroupItemsSource)]
    [TemplateVisualState(Name = ExtendedComboBox.StateEmpty, GroupName = ExtendedComboBox.GroupItemsSource)]
    public class ExtendedComboBox : ComboBox
    {
        public const string GroupItemsSource = "ItemsSourceStates";
        public const string StateNormal = "Normal";
        public const string StateNotSelected = "NotSelected";
        public const string StateEmpty = "Empty";

        private ContentPresenter selectedContent;

        public ExtendedComboBox()
        {
            this.DefaultStyleKey = typeof(ComboBox);
        }

        public override void OnApplyTemplate()
        {
            base.OnApplyTemplate();
            this.selectedContent = this.GetTemplateChild("ContentPresenter") as ContentPresenter;

            //this event can change the NotSelected state
            this.SelectionChanged += (s, e) => this.SetTextIfEmpty();

            //Set a state at start
            this.SetTextIfEmpty();
        }

        //this method can change the Empty state
        protected override void OnItemsChanged(System.Collections.Specialized.NotifyCollectionChangedEventArgs e)
        {
            base.OnItemsChanged(e);
            this.SetTextIfEmpty();
        }

        /// <summary>
        /// Text if the SelectedItem is null
        /// </summary>
        public string NotSelectedText
        {
            get { return (string)GetValue(NotSelectedTextProperty); }
            set { SetValue(NotSelectedTextProperty, value); }
        }

        public static readonly DependencyProperty NotSelectedTextProperty =
            DependencyProperty.Register("NotSelectedText", typeof(string), typeof(ExtendedComboBox), new PropertyMetadata(" "));

        /// <summary>
        /// Text if there is no items in the ComboBox at all
        /// </summary>
        public string EmptyText
        {
            get { return (string)GetValue(EmptyTextProperty); }
            set { SetValue(EmptyTextProperty, value); }
        }

        public static readonly DependencyProperty EmptyTextProperty =
            DependencyProperty.Register("EmptyText", typeof(string), typeof(ExtendedComboBox), new PropertyMetadata(null));

        /// <summary>
        /// Change the state of this control and updates displayed text
        /// </summary>
        protected void SetTextIfEmpty()
        {
            if (this.selectedContent == null || !(this.selectedContent.Content is TextBlock))
                return;
            var text = this.selectedContent.Content as TextBlock;

            if (this.SelectedItem == null && this.Items != null && this.Items.Count > 0)
            {
                text.Text = this.NotSelectedText;
                VisualStateManager.GoToState(this, ExtendedComboBox.StateNotSelected, true);
            }
            else if (this.SelectedItem == null)
            {
                text.Text = this.EmptyText ?? this.NotSelectedText;
                VisualStateManager.GoToState(this, ExtendedComboBox.StateEmpty, true);
            }
            else VisualStateManager.GoToState(this, ExtendedComboBox.StateNormal, true);
        }
    }

I’m not sure whether this control works with a tricky ItemTemplate, but at least it works in 99% of cases.

Complete example: ComboBoxWithPrompt.zip

WPF and Silverlight Chart: multiple series databinding

Recently I needed a chart with multiple series (legend items) which would be able to display data from an OLAP database, like this:

The standard Chart requires to define each legend item in XAML, which doesn’t satisfy me, because I don’t know the actual number of returned items from a data source. So I decided to extend this control. Fortunately, there was a little work: only 3 properties and 1 method.

And now I can write xaml in this way:

        <local:ExtendedChart ItemsSource="{Binding Series}">
            <local:ExtendedChart.ItemTemplate>
                <DataTemplate>
                    <chart:ColumnSeries ItemsSource="{Binding Items}" DependentValuePath="Value" IndependentValuePath="Month" Title="{Binding Title}" />
                </DataTemplate>
            </local:ExtendedChart.ItemTemplate>
        </local:ExtendedChart>

The ExtendedChart control doesn’t require to redefine its template and contains few lines of code:

    public class ExtendedChart : Chart
    {
        ///
<summary>
        /// List of CLR-objects which represent series of the chart
        /// </summary>

        public IEnumerable ItemsSource
        {
            get { return (IEnumerable)GetValue(ItemsSourceProperty); }
            set { SetValue(ItemsSourceProperty, value); }
        }

        public static readonly DependencyProperty ItemsSourceProperty =
            DependencyProperty.Register("ItemsSource", typeof(IEnumerable), typeof(ExtendedChart), new PropertyMetadata(null, (s, e) => ((ExtendedChart)s).InitSeries()));

        ///
<summary>
        /// Template for an item, transforms a CLR-object to an ISeries object
        /// </summary>

        public DataTemplate ItemTemplate
        {
            get { return (DataTemplate)GetValue(ItemTemplateProperty); }
            set { SetValue(ItemTemplateProperty, value); }
        }

        public static readonly DependencyProperty ItemTemplateProperty =
            DependencyProperty.Register("ItemTemplate", typeof(DataTemplate), typeof(ExtendedChart), new PropertyMetadata(null, (s, e) => ((ExtendedChart)s).InitSeries()));

        ///
<summary>
        /// This property is necessary for stacked charts
        /// </summary>

        public DataTemplate ItemsHostTemplate
        {
            get { return (DataTemplate)GetValue(ItemsHostTemplateProperty); }
            set { SetValue(ItemsHostTemplateProperty, value); }
        }

        public static readonly DependencyProperty ItemsHostTemplateProperty =
            DependencyProperty.Register("ItemsHostTemplate", typeof(DataTemplate), typeof(ExtendedChart), new PropertyMetadata(null, (s, e) => ((ExtendedChart)s).InitSeries()));

        private void InitSeries()
        {
            this.Series.Clear();
            if (this.ItemsSource == null || this.ItemTemplate == null)
                return;

            //From items to series
            var series = from item in this.ItemsSource.OfType<object>()
                        let seriesItem = this.ItemTemplate.LoadContent() as ISeries
                        where seriesItem != null && seriesItem is FrameworkElement
                        let dummy = ((FrameworkElement)seriesItem).DataContext = item
                        select seriesItem;

            //Generic series and stacked series are different, that's why I use this if-else
            var hostSeries = this.ItemsHostTemplate != null ? this.ItemsHostTemplate.LoadContent() as DefinitionSeries : null;
            if (hostSeries != null)
            {
                this.Series.Add(hostSeries);
                series.OfType<SeriesDefinition>().ToList().ForEach(hostSeries.SeriesDefinitions.Add);
            }
            else series.ToList().ForEach(this.Series.Add);
        }
    }

The code above will work in WPF as well if you use the new version of WPF Toolkit for WPF4.

Here is the completed solution for Silverlight: BindableSilverlightChart.zip | Download from Google Docs
For WPF use this link: WpfBindableChart.zip