Share via


WPF Data, Item and Control Templates - Minimum Code, Maximum Awesomeness

Introduction

This article is going to highlight some of the awesomeness of WPF (and Silverlight) through XAML, INotifyPropertyChanged, Item Templates, Data Templates and Control Templates.

The Sample Project

To demonstrate these concepts, I have uploaded a sample project to MSDN that you can download and play with yourself.

A Brief Explanation

Firstly, to produce a list of buttons with menus, I am using a ListBox:

<Grid>
    <ListBox ItemsSource="{Binding Applications}"
                ItemTemplate="{StaticResource ApplicationsTemplate}"
                ItemContainerStyle="{StaticResource NoHighlightStyle}"
                ItemsPanel="{DynamicResource HorizontalItemsPanel}" />
</Grid>

 

 

To list the buttons horizontally, I change the ItemsPanel: 
<ItemsPanelTemplate x:Key="HorizontalItemsPanel">    <VirtualizingStackPanel Orientation="Horizontal" IsItemsHost="True" VerticalAlignment="Top"/></ItemsPanelTemplate>

 

To prevent the items of the ListBox being highlighted, I change the ItemContainerStyle:
 

<Style x:Key="NoHighlightStyle" TargetType="{x:Type ListBoxItem}">
    <Setter Property="Template">
        <Setter.Value>
            <ControlTemplate TargetType="{x:Type ListBoxItem}">
                <Grid Background="{TemplateBinding Background}">
                    <ContentPresenter x:Name="contentPresenter" ContentTemplate="{TemplateBinding ContentTemplate}" Content="{TemplateBinding Content}" HorizontalAlignment="{TemplateBinding HorizontalContentAlignment}" Margin="{TemplateBinding Padding}"/>
                </Grid>
            </ControlTemplate>
        </Setter.Value>
    </Setter>
</Style>

 
To generate the actual buttons, I change the ItemTemplate:
 

<DataTemplate x:Key="ApplicationsTemplate">
    <Grid>
        <RadioButton x:Name="tb" Content="{Binding Name}" Style="{DynamicResource MouseOverPopupRadio}" GroupName="GrpApp" />
        <Popup IsOpen="{Binding IsChecked, ElementName=tb}">
            <ListBox ItemsSource="{Binding Options}"
                    ItemTemplate="{StaticResource OptionsTemplate}"
                    ItemContainerStyle="{StaticResource NoHighlightStyle}" />
        </Popup>
    </Grid>
</DataTemplate>

  

Note the Popup IsOpen is bound to the IsChecked of the RadioButton.

To ensure only one popup is open at any one time, I use grouped RadioButtons.

However, I don't want it to look like RadioButtons, so I restyle them to look like Yellow backed TextBlocks:

 

<Style x:Key="MouseOverPopupRadio" TargetType="{x:Type RadioButton}">
    <Setter Property="Template">
        <Setter.Value>
            <ControlTemplate TargetType="{x:Type RadioButton}">
                <TextBlock Text="{TemplateBinding Content}" Margin="5" Padding="5" Background="Yellow"/>
            </ControlTemplate>
        </Setter.Value>
    </Setter>
</Style>

 

 
Also note the popup contains a ListBox for the options, like a Context Menu:
 

<DataTemplate x:Key="OptionsTemplate">
    <Button IsEnabled="{Binding IsEnabled}" Content="{Binding Name}" Margin="5" Click="OptionButton_Click"/>
</DataTemplate>

 

IsEnabled and Content are bound to specific data for that button (application)

 

Below is the class for the options. Because we want to control the IsEnabled property of each option, we use INotifyPropertyChanged to notify the Ui that IsEnabled has changed:
  

public class  Option : INotifyPropertyChanged
{
    public string  Name  { get; set; }
    public int ParentIndex  { get; set; }
 
    bool _IsEnabled;
    public bool IsEnabled
    {
        get
        {
            return _IsEnabled;
        }
        set
        {
            if (_IsEnabled != value)
            {
                _IsEnabled = value;
                RaisePropertyChanged("IsEnabled");
            }
        }
    }
 
    void RaisePropertyChanged(string prop)
    {
        if (PropertyChanged != null) { PropertyChanged(this, new  PropertyChangedEventArgs(prop)); }
    }
    public event  PropertyChangedEventHandler PropertyChanged;
 
}

 

When an option button is clicked, we can extrapolate back to find the actual option, its parent application and therefore change the IsEnabled property of the other options, and of course act upon the application depending on the option.

 

private void  OptionButton_Click(object sender, RoutedEventArgs e)
{
    var button = sender as  Button;
    var option = button.DataContext as  Option;
    var application = Applications[option.ParentIndex];
    switch (option.Name)
    {
        case "Share":
            foreach (var opt in  application.Options)
                opt.IsEnabled = opt.Name == "Share"? false : true;
            break;
        default:
            foreach (var opt in  application.Options)
                opt.IsEnabled = opt.Name == "Share"? true : false;
            break;
    }
 
}

This is a silly function that simply changed the IsEnabled property, and hence updates the UI, but it shows how I can reference the option, and it's parent class, which allows me to do whatever I need to do.
http://i1.code.msdn.s-msft.com/responsive-listbox-items-1963df20/image/file/93344/1/screencapture_31_07_2013_11.gif
 

 

I hope you find this useful!

 

See Also

Another important place to find a huge amount of WPF related articles is the TechNet Wiki itself. The best entry point is WPF Resources on the TechNet Wiki