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

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 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.

 

 
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
Sort by: Published Date | Most Recent | Most Useful
Comments
Page 1 of 1 (1 items)