Click here to Skip to main content
15,868,164 members
Articles / Desktop Programming / WPF

View-View Model based WPF and XAML Implementational Patterns. (WPF and XAML Patterns of Code Reuse in Easy Samples. Part 2)

Rate me:
Please Sign up or sign in to vote.
4.94/5 (27 votes)
19 Aug 2014CPOL26 min read 65.8K   801   77   21
View-View Model based implementational patterns

Introduction

As was mentioned in WPF Control Patterns. (WPF and XAML Patterns of Code Reuse in Easy Samples. Part 1) - WPF framework gave rise to completely new programming concepts which can also be used outside of WPF for purely non-visual programming. From my point of view, these concepts form a step above the usual OOP at least of the same magnitude as the OOP was a step above the procedural programming.

There are various WPF/XAML patterns that can be built around these new concepts. Together these patterns allow to achieve enormous code reuse and separation of concerns within the application's functionality.

In these series of articles I am trying to present and clarify these patterns by using simple WPF examples.

In the first article from the series - WPF Control Patterns. (WPF and XAML Patterns of Code Reuse in Easy Samples. Part 1) - I presented a number of patterns that deal with WPF Controls and ControlTemplates. The patterns described in that article were completely agnostic of a very special DataContext property of the WPF Controls and were not dealing with the capability of ContentControls or ItemControls to turn a non-visual object into a visual object. This article is going to fill that gap.

I call the Patterns described in this article "Implementational" since they are applied for implementing certain visual behaviors. More complex patterns (I call them "Architectural") are left for the third installment of the series and I hope to publish an article describing them soon.

Using the XAML/WPF paradigms described here and in the previous article, I managed to program with great speed at the same time producing high quality and high density code (code with large reuse and large amount of functionality per unit of code).

Very often as the code base expands the developer productivity falls - the developers become a bit confused about their own code and it takes longer for them to figure out the best way of reusing the old functionalty. Applying the principles described in these series of articles, in fact makes the productivity and the code reuse rise as the code base expands since the developers, in fact, are  building a reusable framework with the same patterns and ideas throughout it.

This article talks primarily about the patterns based on the relationship between the Views and the View Models - about making visual objects mimic completely non-visual ones.

This article is not for the beginners - I assume that the readers are familiar with basic WPF concepts - dependency and attached properties, bindings, styles, templates, data templates, triggers.

Visual and Non-Visual Objects

Visual and Non-Visual Objects

In the WPF Control Patterns. (WPF and XAML Patterns of Code Reuse in Easy Samples. Part 1) we talked about Skeleton-Meat paradigm. Lookless controls played role of a Skeleton and ControlTemplates were the Meat. Note, that the lookless controls still had to derive from an object of WPF's Control type (or one of its subclasses).

In this article we are going to talk mostly about visual objects providing Meat for completely non-visual and even non-WPF Skeletons. This is the View-View Model part of the MVVM paradigm. Here are not going to be concerned about the Model part of the MVVM - it can be easily merged into or replicated by the View Model.

This section is mainly a refresher - but it is still highly recommended that you read it carefully since it discusses the known WPF controls from a different angle than most WPF text books.

DataContext Property

Every FrameworkElement object has DataContext dependency property. Unless overridden, this property is inherited from the object's parents within the logical tree. DataContext object is the default source object for the WPF Binding in a sense that if neither RelativeSource, nor Source, nor ElementName is specified for a binding, the binding assumes that the its Source object is provided by the DataContext of the binding's target. This was done on purpose by the Microsoft people who implemented WPF, in order to make it easier for a visual object to bind its properties to a non-visual one provided by its DataContext property.

ContentControl

Some WPF text books state that you can place any XAML code within WPF ContentControl's Content property. E.g. you can have a Button (which is a descendant of a ContentControl) containing a StackPanel object which in turn contains several Image objects:

XAML
<Button>
  <StackPanel>
    <Image Source="Image1">
    <Image Source="Image2">
  /<StackPanel>
/<Button>

While this is true, this is not the best way to use the Content property of a ContentControl and it should be avoided as much as possible.

A more useful definition of the ContentControl should be - a control that allows to 'marry' a non-visual object pointed by its Content property and a DataTemplate pointed by its ContentTemplate property into a visual.

Image 1

Project SimpleContentControlSample illustrates such use of the ContentControls.

Non-visual object is represented by the class TextContainer. It has just one property TheText and a method ChangeText() that toggles TheText property between "Hello World" and "Bye World":

public class TextContainer : INotifyPropertyChanged
{
    #region INotifyPropertyChanged Members
    public event PropertyChangedEventHandler PropertyChanged;
    #endregion

    protected void OnPropertyChanged(string propertyName)
    {
        if (this.PropertyChanged != null)
        {
            this.PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
        }
    }

    #region TheText Property
    private string _text = "Hello World";
    public string TheText
    {
        get
        {
            return this._text;
        }
        set
        {
            if (this._text == value)
            {
                return;
            }

            this._text = value;
            this.OnPropertyChanged("TheText");
        }
    }
    #endregion TheText Property


    bool _showOriginalText = true;
    public void ChangeText()
    {
        _showOriginalText = !_showOriginalText;

        if (_showOriginalText)
        {
            TheText = "Hello World";
        }
        else
        {
            TheText = "Bye World";
        }
    }
}  

Note, that OnPropertyChanged function fires PropertyChanged event declared within INotifyPropertyChanged interface, to inform the WPF Bindings that the property changed.

In the Resources section of the Window within MainWindow.xaml file, we define the non-visual object and the DataTemplate:

XAML
<!-- non-visual object -->
<this:TextContainer x:Key="TheTextContainer" />

<!-- data template built around the TextContainer -->
<DataTemplate x:Key="TextContainerDataTemplate">
    <StackPanel x:Name="TopLevelDataTemplatePanel"
                Orientation="Horizontal">
        <TextBlock Text="{Binding Path=TheText}" 
                   VerticalAlignment="Center"/>
        <Button x:Name="ChangeTextButton"
                Width="120"
                Height="25"
                Content="Change Text"
                Margin="20,5" 
                VerticalAlignment="Center">
            <i:Interaction.Triggers>
                <i:EventTrigger EventName="Click">
                    <ei:CallMethodAction MethodName="ChangeText" 
                                         TargetObject="{Binding }"/>
                </i:EventTrigger>
            </i:Interaction.Triggers>
        </Button>
    </StackPanel>
</DataTemplate>  

Text property of the TextBlock is bound to the TheText property of the TextContainer object. Click on ChangeTextButton (via MS Expression Blend SDK plumbing) will result in method ChangeText() being called.

Note: As was stated in WPF Control Patterns. (WPF and XAML Patterns of Code Reuse in Easy Samples. Part 1) you do not have to install MS Expression Blend in order to use its SDK. It can be downloaded from MS Expression Blend SDK and used for free. On top of that, we only need two dll files from the SDK: Microsoft.Expression.Interactions.dll and System.Windows.Interactivity.dll and we provide these files together with our code under MSExpressionBlendSDKDlls folder.

Since you download these files from the internet, you'll probably have to unblock them. In order to do it, right click on each of the files, choose Properties menu item and click "Unblock" button.

 

Finally, within the Window we create the ContentControl whose Content property is set to the TextContainer object and whose ContentTemplate property is set to the TextContainerDataTemplate:

XAML
<ContentControl HorizontalAlignment="Center"
                VerticalAlignment="Center" 
                Content="{StaticResource TheTextContainer}"
                ContentTemplate="{StaticResource TextContainerDataTemplate}"/>

Here is what we get when we run the project:

Image 2

Pressing "Change Text" button will toggle the text between "Hello World" and "Bye World".

Note that all the meaningful functionality to display the text and to address switching between the "Hello World" and "Bye World" strings is defined within TextContainer object, which plays role of a View Model for our simple application. The View, defined in MainWindow.xaml file by TextContainerDataTemplate only accounts for Visual aspects of the application and provides a wrapper or a shell around the logic defined within the TextContainer class.

Important Note:

On top of the Content property, the ContentControl has the usual DataContext property, whose value is propagated from parent to child within the logical tree as was mentioned above. The Content property is not automatically set to the DataContext property - if you want it to be set to the DataContext property - you need to provide a binding: Content={Binding}. Note, that you do not have to specify the binding path - by default {Binding} will bind to the DataContext property of the same object.

The Content object of the ContentControl becomes the DataContext for the top level object within the data template - in our case "TopLevelDataTemplatePanel" StackPanel gets its DataContext property set to the TextContainer View Model object. Correspondingly the same object gets passed as the DataContext down the logical tree within the DataTemplate to the TextBlock and the Button elements.

ItemsControl

While ContentControl is great for displaying a non-visual object, ItemsControl is used for displaying a collection of non-visual objects. ItemsSource property of ItemsControl is set (usually via binding) to a collection of non-visual objects. Its ItemTemplate property is set to a DataTemplate that provides visual representation for every item within the collection:

Image 3

Note that ItemsSource can be set to any collection, but only if the ItemsSource collection implements INotifyCollectionChanged interface (or implements ObservableCollection<T>) the visuals will update when items are added or removed from the ItemsSource collection. Otherwise the visuals will only change when ItemsSource itself is reset.

Project SimpleItemsControlSample demonstrates how to use ItemsControl.

Its TextContainer class represents a single non-visual (View Model) object while TextContainerTestCollection class represents a collection of such non-visual objects.

TextContainer class is different from the one in the previous sample. Instead of hardcoding the the strings to show before and after the button is clicked, we allow to pass those strings within the TextContainer's constructor:

string _originalText = null;
string _alternateText = null;

public TextContainer(string originalText, string alternateText)
{
    _originalText = originalText;
    _alternateText = alternateText;
}  

TheText property implementation also slightly changed simply to demonstrate a more elegant way of implementing it. It no longer has a Setter and it returns either _originalText or _alternativeText string depending on the value of _showOriginalString flag:

#region TheText Property
public string TheText
{
    get
    {
        if (_showOriginalText)
        {
            return _originalText;
        }
        else
        {
            return _alternateText;
        }
    }
}
#endregion TheText Property  

Method ChangeText() toggles the _showOriginalString flag and calls OnPropertyChanged("TheText") in order to notify the UI that TheText property has changed:

bool _showOriginalText = true;
public void ChangeText()
{
    _showOriginalText = !_showOriginalText;

    OnPropertyChanged("TheText");
}  

TextContainerTestCollection class is derived from ObservableCollection<TextContainer>. Its constructor adds 3 TextContainer items to itself:

public TextContainerTestCollection()
{
    Add(new TextContainer("Hi World", "Bye World"));
    Add(new TextContainer("Hi Friend", "Bye Friend"));
    Add(new TextContainer("Hi WPF", "Bye WPF"));
}

A TextContainerTestCollection object is defined in the Window.Resources section of the XAML. We also define there TextContainerDataTemplate in exactly the same way as we did in the previous sample:

XAML
<Window.Resources>
    <!-- non-visual object -->
    <this:TextContainerTestCollection x:Key="TheTextContainerTestCollection" />

    <!-- data template built around the TextContainer -->
    <DataTemplate x:Key="TextContainerDataTemplate">
        <StackPanel x:Name="TopLevelDataTemplatePanel"
                    Orientation="Horizontal">
            <TextBlock Text="{Binding Path=TheText}"
                       VerticalAlignment="Center" />
            <Button Width="120"
                    Height="25"
                    Content="Change Text"
                    Margin="20,5"
                    VerticalAlignment="Center">
                <i:Interaction.Triggers>
                    <i:EventTrigger EventName="Click">
                        <ei:CallMethodAction MethodName="ChangeText"
                                             TargetObject="{Binding }" />
                    </i:EventTrigger>
                </i:Interaction.Triggers>
            </Button>
        </StackPanel>
    </DataTemplate>
</Window.Resources>  

Finally, we define the ItemsControl setting its ItemsSource to the non-visual collection and its ItemTemplate to the data template:

XAML
<!-- Resulting ItemsControl -->
<ItemsControl HorizontalAlignment="Center"
              VerticalAlignment="Center"
              ItemsSource="{StaticResource TheTextContainerTestCollection}"
              ItemTemplate="{StaticResource TextContainerDataTemplate}"/> 

Here is how the application looks when we run it:

Image 4

Pressing the button at each item will change the item's text to its alternative text. Pressing it again will change it back.

Using ControlTemplate for ContentControl

The final topic, I'd like to discuss in this section is how to use ControlTemplate for changing the look of a content control.

The solution for this sub-section is called CustomToggleControl. It consist of the main project (also of the same name) and it also reference a project CustomControls.

The purpose of the project is to define a ToggleButton as a ContentControl and to show how to provide several different ControlTemplates for it.

The MyToggleButton class represents the Lookless Skeleton for the Toggle button. It derives from ContentControl class. It defines one Boolean dependency property IsChecked:

#region IsChecked Dependency Property
public bool IsChecked
{
    get { return (bool)GetValue(IsCheckedProperty); }
    set { SetValue(IsCheckedProperty, value); }
}

public static readonly DependencyProperty IsCheckedProperty =
DependencyProperty.Register
(
    "IsChecked",
    typeof(bool),
    typeof(MyToggleButton),
    new PropertyMetadata(false)
);
#endregion IsChecked Dependency Property  

It also sets an event handler for MouseUp event to toggle the IsChecked property on MouseUp:

public MyToggleButton()
{
    this.MouseUp += MyToggleButton_MouseUp;
}

void MyToggleButton_MouseUp(object sendr, System.Windows.Input.MouseButtonEventArgs e)
{
    this.IsChecked = !this.IsChecked;
}  

In CustomControls.xaml file, we define two control templates for the same MyToggleButton control within two different styles - "MyToggleButtonCheckBoxStyle" that displays the toggle button as a check box and "ToggleButton_ButtonStyle" that display the toggle button as a button.

Before describing the templates, let us take a look at the main file for the application. It contains two instances of the MyToggleButton control within a vertical StackPanel:

XAML
<StackPanel HorizontalAlignment="Center"
            VerticalAlignment="Center">
    <customControls:MyToggleButton Content="My Toggle Button - Check Box Style:"
                                   Style="{StaticResource MyToggleButtonCheckBoxStyle}"
                                   Foreground="White"
                                   Background="Black"
                                   Margin="10,10" />
    <customControls:MyToggleButton Content="My Toggle Button - Button Style:"
                                   Foreground="White"
                                   Background="Black"
                                   Style="{StaticResource ToggleButton_ButtonStyle}"
                                   Margin="10,10" />
</StackPanel>  

When we run the application, we get the following:

Image 5

The check box style button displays a check mark within the square on its right hand side when its IsChecked property is true, while the button style one changes the background to darker.

Now let us take a look at the corresponding ControlTemplates.

The check box template has a horizontal StackPanel that contains a ContentPresenter and a Border. The border represents the square with the checkbox. ContentPresenter is a special object that controls where the DataTemplate of the ContentControl is going to be rendered. It adopts the shape and form defined by the DataTemplate of the ContentControl. If the DataTemplate is not defined, it will simply render the ContentPresenter as a TextBlock displaying the string representation of the Content property of the ContentControl:

XAML
<ControlTemplate TargetType="this:MyToggleButton">
    <Grid Background="Transparent">
        <StackPanel Orientation="Horizontal"
                    Background="{TemplateBinding Background}">
            <ContentPresenter Content="{TemplateBinding Content}"
                              ContentTemplate="{TemplateBinding ContentTemplate}"
                              HorizontalAlignment="{TemplateBinding HorizontalContentAlignment}"
                              VerticalAlignment="{TemplateBinding VerticalContentAlignment}" />

            <!-- the square on the right hand side-->
            <Border Width="20"
                    Height="20"
                    BorderThickness="1"
                    BorderBrush="{TemplateBinding Foreground}"
                    Background="{TemplateBinding Background}"
                    Margin="10, 1, 0, 1">

                <!-- this the the check mark. It is visible when 
                     IsChecked property of the control is true and 
                     invisible otherwise -->
                <TextBlock Text="V"
                           HorizontalAlignment="Center"
                           VerticalAlignment="Center"
                           Foreground="{TemplateBinding Foreground}"
                           Visibility="{Binding Path=IsChecked,
                                                Converter={StaticResource TheBoolToVisConverter},
                                                RelativeSource={RelativeSource AncestorType=this:MyToggleButton}}" />
            </Border>
        </StackPanel>
    </Grid>
</ControlTemplate>  

The button style toggle button has Grid panel containing the border with a ContentPresenter. It also has an opaque Border object that overlays the original border. By default the opaque border has opacity 0.5. Using triggers we reduce this opacity on MouseOver to 0.3 and when the control is checked, the opacity is further reduced to 0:

XAML
<ControlTemplate TargetType="this:MyToggleButton">
    <Grid Background="Transparent"
          x:Name="ItemPanel">
        <Border x:Name="TheItemBorder"
                Background="Black"
                BorderBrush="White"
                BorderThickness="1">
            <ContentPresenter Content="{TemplateBinding Content}"
                              ContentTemplate="{TemplateBinding ContentTemplate}"
                              HorizontalAlignment="Center"
                              VerticalAlignment="Center"
                              Margin="10, 5" />
        </Border>

        <!-- provide some opacity for the control -->
        <Border x:Name="TheOpacityBorder"
                Background="White"
                Opacity="0.5" />
    </Grid>
    <ControlTemplate.Triggers>

        <!-- reduce the opacity of the opacity border
             to 0.3 on Mouse Over -->
        <Trigger Property="IsMouseOver"
                 Value="True">
            <Setter TargetName="TheOpacityBorder"
                    Property="Opacity"
                    Value="0.3" />
        </Trigger>


        <!-- reduce the opacity of the opacity border
             to 0 when the button is checked -->
        <Trigger Property="IsChecked"
                 Value="True">
            <Setter TargetName="TheOpacityBorder"
                    Property="Opacity"
                    Value="0" />
        </Trigger>
    </ControlTemplate.Triggers>
</ControlTemplate>  

The main idea that I wanted to convey within this sub-section is that the ControlTemplate for a ContentControl can define a ContentPresenter object whose visual will be determined by the DataTemplate of the ContentControl.

MVVM based Patterns

Now we are finally going to discuss the MVVM based patterns.

Single Selection View Model Pattern

I like the basic WPF controls e.g. ContentControl and ItemsControl. I like the derived WPF controls (e.g. RadioButton or ListView and ListBox) considerably less - from my point of view they were created before the WPF best practices became known and are not taking full advantage of them. 

In this sub-section we are going to show how to use the View Model concepts for creating an ItemsControl with single selection, mimicking the RadioButton functionality - i.e. allowing to select at most one item out of many at a time: if an item is already selected and the user selects another item, the first selected items becomes unselected.

Later we shall show how to create a more general functionality for keeping 2 or more last items selected.

Note that ListView and ListBox already have the functionality for single item selection, but they cannot be easily generalized to keep 2 or more items selected.

Note also that since the item selection functionality is defined in a non-visual View Model, we can easily unit test it as will be shown below.

The solution containing the code is called "UniqueSelectionPatternSample". Its main project has the same name and it references two other projects within the same solution: ViewModels, TestViewModels. The solution also contains MS Unit Test project SelectionTests.

ViewModels project defines the non-visual objects for this and several other samples. In this sample we'll be looking at interface ISelectable and classes SelectableItemBase, SelectableItemWithDisplayName and CollectionWithUniqueItemSelection defined within ViewModels project.

TestViewModels project contains a number of classes representing the collections defined in ViewModels project populated with some test data. In this sample we'll be using MyTestButtonsWithSelectionVM class from it.

SelectionTests project provides the unit tests for non-visual classes defined under ViewModels project.

Let us start describing the sample by showing what it does. Make UniqueSelectionPatternSample project the start-up project of the solution. Then run the solution. You will see a row of four buttons. Try clicking the buttons - you'll see that when a button is selected, button that had been selected before is unselected:

Image 6

The functionality that controls selection/unselection is located within a purely non-visual class CollectionWithUniqueItemSelection<T> defined under ViewModels project. This collection extends ObservableCollection<T> and its generic argument T has to implement ISelectable interface.

ISelectable declares IsItemSelected property and IsItemSelectedChangedEvent event:

public interface ISelectable
{
    bool IsItemSelected { get; set; }

    event Action<iselectable> ItemSelectedChangedEvent;
}
</iselectable>

The implementations of this interface have to fire the ItemSelectedChangedEvent after IsItemSelected property changes.

SelectableItemBase is a basic implementation of this interface. It fires the ItemSelectionChangedEvent when the IsItemSelected property changes; it also fires the PropertyChanged event so that the UI could detect the property change:

public class SelectableItemBase : ISelectable, INotifyPropertyChanged
{
    public event PropertyChangedEventHandler PropertyChanged;

    protected void OnPropChanged(string propName)
    {
        if (PropertyChanged != null)
            PropertyChanged(this, new PropertyChangedEventArgs(propName));
    }

    public event Action<ISelectable> ItemSelectedChangedEvent;

    bool _isItemSelected = false;
    public bool IsItemSelected
    {
        get
        {
            return _isItemSelected;
        }
        set
        {
            if (_isItemSelected == value)
                return;

            _isItemSelected = value;

            if (ItemSelectedChangedEvent != null)
                ItemSelectedChangedEvent(this);

            OnPropChanged("IsItemSelected");
        }
    }
}

Class SelectableItemWithDisplayName extends SelectableItemBase by adding a property DisplayName to it and a method ToggleSelection(). This method simply toggles the selection state of the item:

public class SelectableItemWithDisplayName : SelectableItemBase
{
    public SelectableItemWithDisplayName(string displayName)
    {
        DisplayName = displayName;
    }

    public string DisplayName 
    {
        get; 
        private set; 
    }

    public void ToggleSelection()
    {
        this.IsItemSelected = !this.IsItemSelected;
    }
}  

Now let us take a close look at CollectionWithUniqueItemSelection<T> class. It defines SelectedItem property that should contain item which is currently selected. If the collection contains no selected item, this property should be null. When SelectedItem is set to some item within the collection, this item's IsItemSelected property is set to true, while the previous selected item's IsItemSelected property is set to false:

T _selectedItem = null;
public T SelectedItem
{
    get
    {
        return _selectedItem;
    }

    set
    {
        if (_selectedItem == value)
            return;

        if (_selectedItem != null) // unselect old item
        {
            _selectedItem.IsItemSelected = false;
        }

        _selectedItem = value;

        if (_selectedItem != null) // select the new item
        {
            _selectedItem.IsItemSelected = true;
        }

        OnPropertyChanged(new PropertyChangedEventArgs("SelectedItem"));
    }
}  

Whenever item is added to the collection, we add the method item_ItemSelectedChagnedEvent(ISelectable item) to be the handler for the item's ItemSelectedChangedEvent. Whenever the item is removed from the collection, we remove the corresponding handler. Adding the handler is achieved by ConnectItems method:

void ConnectItems(IEnumerable items)
{
    if (items == null)
        return;

    foreach (T item in items)
    {
        item.ItemSelectedChangedEvent += item_ItemSelectedChangedEvent;
    }
}  

This method is called by the Init() method (for the original items within the collection):

void Init()
{
    ConnectItems(this);
    this.CollectionChanged += CollectionWithUniqueItemSelection_CollectionChanged;
}  

It is also called in the handler to the CollectionChanged event for the new items within the collection:

void CollectionWithUniqueItemSelection_CollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
{
    DisconnectItems(e.OldItems);

    ConnectItems(e.NewItems);
}  

DisconnectItems(IEnumerable items) method is called to disconnect the handlers for the items removed from the collection:

void DisconnectItems(IEnumerable items)
{
    if (items == null)
        return;

    foreach (T item in items)
    {
        item.ItemSelectedChangedEvent -= item_ItemSelectedChangedEvent;
    }
}  

As was shown above it is called by CollectionWithUniqueItemSelection_CollectionChanged method (which is a handler for the CollectionChanged event) to disconnect the items that are removed from the collection.

The method item_ItemSelectedChangedEvent(ISelectable item) (which is assigned to be a IsItemSelectedChangedEvent handler for every item within the collection) takes care of setting SelectedItem property for the currently selected item:

void item_ItemSelectedChangedEvent(ISelectable item)
{
    if (item.IsItemSelected)
    {
        this.SelectedItem = (T)item;
    }
    else
    {
        this.SelectedItem = null;
    }
}  

Thus, there are two equivalent ways of choosing selected item within the CollectionWithUniqueItemSelection class:

  1. By setting the corresponding item's IsItemSelected property
  2. By setting the collection's SelectedItem property to the corresponding item.

Besides, the two methods are completely equivalent - if you select (or unselect) an item using one of the above methods, the other method's condition will also be satisfied. To verify that it is true, I've created a unit test UniqueSelectionTests class within SelectionTests project.

 

In the constructor of the class, we create _testCollection as a CollectionWithUniqueItemSelection<SelectableItemWithDisplayName> and populated it with three items with DisplayNames "1", "2" and "3":

public UniqueSelectionTests()
{
    _testCollection = new CollectionWithUniqueItemSelection<SelectableItemWithDisplayName>();

    _testCollection.Add(new SelectableItemWithDisplayName("1"));
    _testCollection.Add(new SelectableItemWithDisplayName("2"));
    _testCollection.Add(new SelectableItemWithDisplayName("3"));
}  

Method TestAllUnselected verifies that there is no selected item within the collection - it checks that SelectedItem property is set to null and all the individual items within the collection have IsItemSelected property set to false:

// test that no item is selected
void TestAllUnselected()
{
    // testing for no items selected
    Assert.IsNull(_testCollection.SelectedItem);

    // test that all items is the collected have IsItemSelected property
    // set to false.
    foreach (SelectableItemWithDisplayName selectableItem in _testCollection)
    {
        Assert.IsFalse(selectableItem.IsItemSelected);
    }

    return;
}  

Method TestSelected(string selectedItemDisplayName) tests that an item with the corresponding display name is selected and only that item is selected - it checks that SelecteItem has the required display name and also that only the item with that display name has IsItemSelected property set to true while the rest of the items have it set to false:

// test that item with the specified display name is selected
void TestSelected(string selectedItemDisplayName)
{
    Assert.IsNotNull(_testCollection.SelectedItem);
    Assert.AreEqual<string>(_testCollection.SelectedItem.DisplayName, selectedItemDisplayName);
    Assert.IsTrue(_testCollection.SelectedItem.IsItemSelected);

    foreach (SelectableItemWithDisplayName selectableItem in _testCollection)
    {
        if (selectedItemDisplayName.Equals(selectableItem.DisplayName))
        {
            Assert.IsTrue(selectableItem.IsItemSelected);
        }
        else
        {
            Assert.IsFalse(selectableItem.IsItemSelected);
        }
    }
}
</string>

There are two methods in charge of selecting items and two methods in change of unselecting items - one of each pair corresponds to selecting (or unselecting) via SelectedItem property while the other - via the IsItemSelected flag:

// select the specified item by setting the testCollection's SelectedItem
// property to that item.
void SelectItemViaSettingSelectedItem(string itemDisplayName)
{
    SelectableItemWithDisplayName itemToSelect = FindItemByDisplayName(itemDisplayName);

    _testCollection.SelectedItem = itemToSelect;
}

// unselect the currently selected  item by 
// setting the test collection's selected item to null
void UnselectItemViaUnsettingSelectedItem()
{
    _testCollection.SelectedItem = null;
}

// select the specified item by setting its IsItemSelected property to true.
void SelectItemViaFlag(string itemDisplayName)
{
    SelectOrUnselectItemViaFlag(itemDisplayName, true);
}

// unselect the specified item by setting its IsItemSelected property to false.
void UnselectItemViaFlag(string itemDisplayName)
{
    SelectOrUnselectItemViaFlag(itemDisplayName, false);
}

The IsItemSelected flag changing functions call SelectOrUnselectItemViaFlag utility method that in turns calls FindItemByDisplayName utility method:

SelectableItemWithDisplayName FindItemByDisplayName(string itemDisplayName)
{
    SelectableItemWithDisplayName itemToSelect =
        _testCollection.
            Where((item) => (item as SelectableItemWithDisplayName).DisplayName.Equals(itemDisplayName)).First();

    return itemToSelect;
}

// select or unselect the specified item by setting its IsItemSelected 
// property to true or false.
void SelectOrUnselectItemViaFlag(string itemDisplayName, bool selectOrUnselect)
{
    SelectableItemWithDisplayName itemToSelect = FindItemByDisplayName(itemDisplayName);

    itemToSelect.IsItemSelected = selectOrUnselect;
}

The "main" test method is TestSelectingItem() it calls various selection/unselection methods followed by method that test that the corresponding item is indeed selected (or unselected):

[TestMethod]
public void TestSelectingItem()
{
    TestAllUnselected();

    SelectItemViaFlag("2");

    TestSelected("2");

    SelectItemViaFlag("1");

    TestSelected("1");

    UnselectItemViaFlag("1");

    TestAllUnselected();

    SelectItemViaSettingSelectedItem("2");

    TestSelected("2");

    SelectItemViaSettingSelectedItem("3");

    TestSelected("3");

    UnselectItemViaUnsettingSelectedItem();

    TestAllUnselected();
}  

You can run this method by right mouse clicking on its declaration and choosing "Run Tests" or "Debug Tests" option.

Finally let us talk about "main" UniqueSelectionPatternSample project. All its code is located within MainWindow.xaml file. It uses MyTestButtonsWithSelectionVM object (defined in TestViewModels) project as its View Model:

XAML
<testVMs:MyTestButtonsWithSelectionVM x:Key="TheTestButtonsWithSelectionVM" />

MyTestButtonsWithSelectionVM is simply a CollectionWithUniqueItemSelection<SelectableItemWithDisplayName> that populates itself with four items:

public class MyTestButtonsWithSelectionVM : CollectionWithUniqueItemSelection<SelectableItemWithDisplayName>
{
    public MyTestButtonsWithSelectionVM()
    {
        Add(new SelectableItemWithDisplayName("Button 1"));
        Add(new SelectableItemWithDisplayName("Button 2"));
        Add(new SelectableItemWithDisplayName("Button 3"));
        Add(new SelectableItemWithDisplayName("Button 4"));
    }
}  

Coming back to MainWindow.xaml file - let us take a look at the ItemsControl that represents the selectable buttons:

XAML
<ItemsControl x:Name="SingleButtonSelectionControl"
              ItemsSource="{StaticResource TheTestButtonsWithSelectionVM}"
              ItemTemplate="{StaticResource ItemButtonDataTemplate}"
              VerticalAlignment="Center"
              HorizontalAlignment="Center">
    <ItemsControl.ItemsPanel>
        <ItemsPanelTemplate>
            <!-- arrange the items horizontally -->
            <StackPanel Orientation="Horizontal" />
        </ItemsPanelTemplate>
    </ItemsControl.ItemsPanel>
    <ItemsControl.ItemContainerStyle>
        <!-- make sure that each item has margin 5 pixels to the left and right of it-->
        <Style TargetType="FrameworkElement">
            <Setter Property="Margin"
                    Value="5,0" />
        </Style>
    </ItemsControl.ItemContainerStyle>
</ItemsControl>

The ItemControl's ItemsSource property is set to the TheTestButtonsWithSelectionVM resource object (that contains the TestButtonsWithSelectionVM collection), its ItemTemplate property is pointing to ItemButtonDataTemplate resource (to be discussed shortly).

Here is the DataTemplate for individual items within the ItemsControl:

XAML
<DataTemplate x:Key="ItemButtonDataTemplate">
    <Grid Background="Transparent"
          x:Name="ItemPanel">
        <Border x:Name="TheItemBorder"
                Background="Black"
                BorderBrush="White"
                BorderThickness="1">
            <TextBlock HorizontalAlignment="Center"
                       VerticalAlignment="Center"
                       Foreground="White"
                       Text="{Binding Path=DisplayName}"
                       Margin="10, 5" />
        </Border>
        <Border x:Name="TheOpacityBorder"
                Background="White"
                Opacity="0.5" />
        <i:Interaction.Triggers>
            <!-- Call ToggleSelection method when when MouseDown event 
                 is fired on the item -->
            <i:EventTrigger EventName="MouseDown">
                <ei:CallMethodAction MethodName="ToggleSelection"
                                     TargetObject="{Binding Path=DataContext, ElementName=ItemPanel}" />
            </i:EventTrigger>
        </i:Interaction.Triggers>
    </Grid>
    <DataTemplate.Triggers>
        <!-- make TheOpacityBorder less opaque when the mouse is over-->
        <Trigger Property="IsMouseOver"
                 Value="True">
            <Setter TargetName="TheOpacityBorder"
                    Property="Opacity"
                    Value="0.3" />
        </Trigger>
        <DataTrigger Binding="{Binding Path=IsItemSelected}"
                     Value="True">
            <!-- make TheOpacityBorder completely transparent when the 
                 item is selected-->
            <Setter TargetName="TheOpacityBorder"
                    Property="Opacity"
                    Value="0" />

            <!-- make the ItemPanel non-responsive to the event when
                 the item is selected (this is to prevent unselection
                 when clicking on the same item again) -->
            <Setter TargetName="ItemPanel"
                    Property="IsHitTestVisible"
                    Value="False" />
        </DataTrigger>
    </DataTemplate.Triggers>
</DataTemplate>  

The visual item itself consist of a Border (named TheItemBorder) object with TextBlock object within it. The Text property of the TextBlock is bound to DisplayName property of its own DataContext (which contains the corresponding item within the collection). There is also TheOpacityBorder Border that overlays TheItemBorder object. By default its opacity is 0.5. The DataTemplate's triggers ensure that when the mouse is over the visual item, its opacity is reduced to 0.3 and then the item is selected, its opacity is further reduced to 0. Using MS Expression Blend SDK plumbing we ensure that every time the item is clicked, method ToggleSelection() is called on the corresponding item's View Model object.

There is also a trigger to set IsHitTestVisible value to False when the item is selected - this is done in order to prevent item unselection on click - to mimic the RadioButton functionality.

Single Selection Implementation with Custom Controls

The above sample implements single selection with the ItemTemplate created from scratch. This might create some problems for reuse. For example if we want to reuse the above DataTemplate for different View Model, the new View Model will also have to have the same property and method names, i.e. whatever text we want to display within the item, should be provided by DisplayName property, the selection state should be given by IsItemSelected property and it will need a method ToggleSelection() in order to change the selection state.

Instead of creating such templates from scratch, it is better to use a custom control that has properties that reflect the text to show and the selection state. Even if we use a View Model with similar properties named differently all we need to do is to change the bindings on such control for the properties to be consumed.

We showed above how to create MyToggleButton custom control. This control can be directly used for our single selection implementation. Its Content property can be bound to DisplayName property of the View Model in order to display text contained in it, while its IsChecked property can be bound to IsItemSelected property of the View Model.

The solution that shows how to use MyToggleButton custom control for single selection is called "UniqueSelectionWithCustomControl". Its main project (of the same name) differs from the one described above only in how TheItemButtonDataTemplate is constructed within MainWindow.xaml file's Window.Resources section:

XAML
<DataTemplate x:Key="ItemButtonDataTemplate">
    <customControls:MyToggleButton x:Name="TheToggleButton"
                                   Content="{Binding Path=DisplayName}"
                                   Foreground="White"
                                   Background="Black"
                                   IsChecked="{Binding Path=IsItemSelected, Mode=TwoWay}"
                                   Style="{StaticResource ToggleButton_ButtonStyle}"/>
    <DataTemplate.Triggers>
        <Trigger SourceName="TheToggleButton"
                 Property="IsChecked"
                 Value="True">
            <Setter Property="IsHitTestVisible"
                    Value="False" />
        </Trigger>
    </DataTemplate.Triggers>
</DataTemplate>  

MyToggleButton class provides an adequate implementation for the selectable buttons with its Content property bound to DisplayName and its IsChecked property bound to IsItemSelected property of the View Model object.

When you run this project you'll get exactly the same visuals and behavior as in the previous sample.

Now change the MyToggleButton's style to MyToggleButtonCheckBoxStyle. You will see check boxes instead of buttons with the same single selection principle - when a checkbox is checked, the previously selected check box is unselected:

Image 7

Note that replacing the selectable buttons by the check boxes was achieved by changing a single word within the XAML file.

Non-Visual Behaviors Pattern

In WPF Control Patterns. (WPF and XAML Patterns of Code Reuse in Easy Samples. Part 1) we gave an example of a behavior pattern for the WPF controls as a non-invasive way of modifying the control's behavior. Here we are going to present the non-visual behaviors as an (almost) non-invasive way of modifying the non-visual object's behavior.

Note that CollectionWithUniqueItemSelection<T> class that we used in the previous samples to create the non-visual single selection, has a lot of code dedicated specifically to single selection functionality. In fact almost all of its code (about 90 lines) is dealing with single selection. Behaviors would allow to move all of this code outside of the class into the behavior class. Then we can reuse the same collection class with different behaviors resulting in completely different selection algorithms.

Single Selection Behavior

NonVisualBehaviorsSample solution demonstrates creating a behavior for single selection. Instead of using CollectionWithUniqueSelection<T> for our View Model collection, we use SelectableItemCollection<T>. It is much simpler than CollectionWithUniqueSelection<T>. Just like CollectionWithUniqueSelection<T>, it extends ObservableCollection<T> but its functionality consists of only one property TheBehavior of the type IBehavior. This property serves for attaching the behavior to the collection:

public class SelectableItemCollection<T> : ObservableCollection<T>
    where T : class, ISelectable
{

    IBehavior _behavior = null;
    public IBehavior TheBehavior 
    {
        get
        {
            return _behavior;
        }

        set
        {
            if (_behavior == value)
                return;

            if (_behavior != null) // if not null detach old selection behavior
                _behavior.OnDetach();

            _behavior = value;

            if (_behavior != null) // if new selection behavior is not null, attach it
                _behavior.OnAttach(this);
        }
    }
}  

IBehavior is a very simple interface defined in the same project ViewModels:

public interface IBehavior
{
    void OnAttach(IEnumerable collectionToAttachTo);

    void OnDetach();
}  

Most of the selection behavior functionality is located within SelectionBehaviorBase<T> class. The two selection behaviors that we present in this article are derived from it.

This class ensures that every ISelectable item within the collection to which the behavior is attached, gets its ItemSelectedChangedEvent handled by OnItemSelectionChanged(ISelectable item) function. It also takes care of removing the event handler if the corresponding item is removed from the collection.

Similar to CollectionWithUniqueSelection<T>, the functionality for removing and adding the even handler is provided by the methods DisconnectItems(IEnumerable items) and ConnectItems(IEnumerable items):

void DisconnectItems(IEnumerable items)
{
    if (items == null)
        return;

    foreach (ISelectable item in items)
    {
        item.ItemSelectedChangedEvent -= OnItemSelectionChanged;
    }
}

void ConnectItems(IEnumerable items)
{
    if (items == null)
        return;

    foreach (ISelectable item in items)
    {
        item.ItemSelectedChangedEvent += OnItemSelectionChanged;
    }
}  

These methods are called when the TheCollection property of the behavior is reset:

ObservableCollection<t> _collection = null;
ObservableCollection<t> TheCollection
{
    get
    {
        return _collection;
    }

    set
    {
        if (_collection == value)
            return;

        if (_collection != null)
        {
            _collection.CollectionChanged -= _collection_CollectionChanged;
        }

        // disconnect event handlers from the items in the old collection
        DisconnectItems(_collection);

        _collection = value;

        // connect the event handlers to the items in the new collection
        ConnectItems(_collection);

        if (_collection != null)
        {
            _collection.CollectionChanged += _collection_CollectionChanged;
        }
    }
}  
</t></t>

Also they are called for the items removed or added to the collection:

void _collection_CollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
{
    // disconnect the event handlers from the items removed from the collection
    DisconnectItems(e.OldItems);

    // connect the event handlers to the items added to the collection
    ConnectItems(e.NewItems);
}

IBehavior methods OnAttach(IEnumerable selectableItemCollectionToAttachTo) and OnDetach() simply set the TheCollection propert of the behavior:

public void OnAttach(IEnumerable selectableItemCollectionToAttachTo)
{
    if (TheCollection != null)
        throw new Exception("Programming Error: Selection Behavior cannot attach to more than one collection");

    TheCollection = selectableItemCollectionToAttachTo as ObservableCollection<t>;
}

public void OnDetach()
{
    TheCollection = null;
}  
</t>

The selection event handler provided by OnItemSelectionChanged(ISelectableItem item) function is not implemented within the SelectionBehaviorBase<T> class - it is declared as an abstract method to be overridden within the subclasses.

UniqueSelectionBehavior<T> class extends SelectionBehaviorBase<T> providing the functionality for single item selection. Its SelectedItem property allows selection by choosing an item (just like CollectionWithUniqueSelection<T>) and its implementation of OnItemSelectionChanged(ISelectable item) event handler method allows to select or deselect an item by changing its IsItemSelected property:

public class UniqueSelectionBehavior<T> : SelectionBehaviorBase<T>
    where T : class, ISelectable
{
    T _selectedItem = null;
    public T SelectedItem 
    {
        get
        {
            return _selectedItem;
        }

        set
        {
            if (_selectedItem == value)
                return;

            if (_selectedItem != null)
            {
                _selectedItem.IsItemSelected = false;
            }

            _selectedItem = value;

            if (_selectedItem != null)
            {
                _selectedItem.IsItemSelected = true;
            }

            OnPropChanged("SelectedItem");
        }
    }

    protected override void OnItemSelectionChanged(ISelectable item)
    {
        if (item.IsItemSelected)
        {
            this.SelectedItem = (T)item;
        }
        else
        {
            this.SelectedItem = null;
        }
    }
}  

As a view model for the collection of items we use MyTestButtonsWithSelectionAndBehavior class defined under TestViewModels project. It extends SelectableItemCollection<SelectableItemWithDisplayName> collection and adds four items to it:

public class MyTestButtonsWithSelectionAndBehavior : SelectableItemCollection<SelectableItemWithDisplayName>
{
    public MyTestButtonsWithSelectionAndBehavior()
    {
        Add(new SelectableItemWithDisplayName("Button 1"));
        Add(new SelectableItemWithDisplayName("Button 2"));
        Add(new SelectableItemWithDisplayName("Button 3"));
        Add(new SelectableItemWithDisplayName("Button 4"));
    }
}  

And here is how the behavior and the collection is defined in MainWindow.xaml file of the main project:

XAML
<!-- define the selection behavior -->
<viewModels:UniqueSelectionBehaviorOnSelectableWithDisplayName x:Key="TheUniqueSelectionBehavior" />

<!-- define the collection and assign the behavior to it -->
<testViewModels:MyTestButtonsWithSelectionAndBehavior x:Key="TheButtonsWithSelectionBehaviorVM"
                                                      TheBehavior="{StaticResource TheUniqueSelectionBehavior}"/>

The rest of the MainWindow.xaml file is almost identical to the one of the previous example aside from the fact that we use "TheButtonsWithSelectionBehaviorVM" collection as the ItemsSource for the ItemsControl.

As a result, we obtain a radio button behavior identical to the one of the previous sample:

Image 8

Two Last Items Selection Behavior

This sample implements a different behavior on the same collection. According to this behavior, we allow to have at most two items selected at the same time within the collection. When a user selects a third item, the item that has been selected longest gets unselected.

This sample is located in "TwoLastItemsSelectionBehaviorSample" solution. Its code is almost identical to the code of the previous sample aside from the behavior. Instead of UniqueSelectionBehaviorOnSelectableWithDisplayName, we use TwoLastItemSelectionBehaviorOnSelectableWithDisplayName, which is derived from TwoLastItemsSelectionBehavior<SelectableItemWithDisplayName> class.

All of the behavior's code is located in the superclass TwoLastItemsSelectionBehavior<T>. Just like UniqueSelectionBehavior<T> it is derived from SelectionBehaviorBase<T> and overrides the OnItemSelectionChanged(ISelectable item) method (as a reminder - this method is called whenever the IsItemSelected property changes on any item within the collection).

TwoLastItemsSelectionBehavior<T> has a List<T> field _selectedItems which controls selection and unselection. It contains the currently selected items with items that have been selected longer being closer to the end of the collection. The method that controls adding a new item to the _selectedItems collection is AddSelectedItem(T itemToAdd):

// collection of selected items
List<T> _selectedItems = new List<T>();

// returns the number of selected items
int NumberSelectedItems
{
    get
    {
        return _selectedItems.Count;
    }
}

// if the number of selected items within _selectedItems
// collection is 2, unselect the item that has been selected the longest
// (last item within the collection)
// add a newly selected item to beginning of _selectedItems collection.
void AddSelectedItem(T itemToAdd)
{   
    if (NumberSelectedItems >= 2) 
    {
        // if the current number of selected item is 2 (or greater - which cannot happen)
        // remove the last item within the _selecteItems collection
        T itemToRemove = _selectedItems.Last();
        _selectedItems.Remove(itemToRemove); // remove last item

        // set the IsItemSelected property on the 
        // removed item to false
        itemToRemove.IsItemSelected = false;
    }

    // insert the newly selected item 
    // at the beginning of the collection
    _selectedItems.Insert(0, itemToAdd);
}  

AddSelectedItem(T itemToAdd) is called by OnItemSelectionChanged(ISelectable item) method:

protected override void OnItemSelectionChanged(ISelectable item)
{
    if (!item.IsItemSelected)
    {
        // if item is no longer selected, remove it from _selected items collection
        _selectedItems.Remove((T)item);
    }
    else
    {
        // if the item is selected, add it to the _selectedItems collection
        AddSelectedItem((T)item);
    }
    OnPropChanged("SelectedItems");
    OnPropChanged("NumberSelectedItems");
}  

Now, the only minor changes to the MainWindow.xaml file (in comparison to the previous sample) is defining TwoLastItemSelectionBehaviorOnSelectableWithDisplayName object and using it as the behavior on the MyTestButtonsWithSelectionAndBehavior object instead of UniqueSelectionBehaviorOnSelectableWithDisplayName:

XAML
<viewModels:TwoLastItemSelectionBehaviorOnSelectableWithDisplayName x:Key="TheTwoLastItemsSelectionBehavior" />

<testViewModels:MyTestButtonsWithSelectionAndBehavior x:Key="TheButtonsWithSelectionBehaviorVM"
                                                      TheBehavior="{StaticResource TheTwoLastItemsSelectionBehavior}" />

When we run the sample, we'll get the needed behavior - if two buttons are selected and you click another one, the button that had been selected the longest will get unselected:

Image 9

Note that we changed the behavior without changing either the View or the View Model, only behavior attached to the View Model was changed. Thus we showed how to modify the behavior of a collection of items with minimal invasiveness.

Note, also, that simply by changing a style/template for the toggle buttons, we can produce the same behavior with check boxes:

Image 10

The above was achieved by replacing a reference to ToggleButton_ButtonStyle by a reference to MyToggleButtonCheckBoxStyle in ItemButtonDataTemplate defined within MainWindow.xaml file.

We can easily generalize 2 last items selection to N last items selection just by replacing the number 2 within the behavior by an integer parameter. Also we can easily create considerably more complex selection behaviors based on the same principle.

I used 2 last items selection behavior for displaying mulitple charts within the same Dev Express'es ChartControl object corresponding to two different Y axes - one axis on the left and one on the right. If you have more than 2 charts, you can allow the users to choose any two of them to display, by using the 2 last items selection principle.

Recursive Data Templates

A lot of concepts whether in real world or in software development have a recursive tree structure where items can be represented by tree nodes - each node might have multiple child nodes and one parent node. The nodes without child nodes are called the leaves of the tree. There can be only one node without a parent and it is called the root of the tree.

Example of such trees would be a file system, a reporting structure within an organization, WPF's logical and visual trees.

It turns out that such trees within the View can be easily represented by WPF's DataTemplates that refer to themselves - i.e. recursive data templates.

Recursive data template sample is located within RecursiveDatateTemplatesSample solution with the main project that has the same name. If you run the solution and expand all its nodes, here is what you are going to see:

Image 11

You can see that this is a tree representation of some mocked up file system (which does not have any to do with the file system on your machine). It has folder Root that contains two folders Documents and Pictures and file MySystemFile. Documents folder contains two files Document1 and Document2, while Picture folder contains Picture1 and Picture2.

As you will see below, we are not using WPF's TreeView control to display this file system.

First, let us explain the View Model that represents this tree structure.

Class TreeNodeBase under ViewModels project is the basic class for a tree node. It has Parent and Children properties to represent the parent and children of the tree node correspondingly. It also has a property HasChildren that is false if Children collection is null or empty and true otherwise:

public class TreeNodeBase : INotifyPropertyChanged
{
    #region INotifyPropertyChanged Members
    public event PropertyChangedEventHandler PropertyChanged;
    #endregion

    protected void OnPropertyChanged(string propertyName)
    {
        if (this.PropertyChanged != null)
        {
            this.PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
        }
    }

    #region Parent Property
    private TreeNodeBase _parent;
    public TreeNodeBase Parent
    {
        get
        {
            return this._parent;
        }
        set
        {
            if (this._parent == value)
            {
                return;
            }

            this._parent = value;
            this.OnPropertyChanged("Parent");
        }
    }
    #endregion Parent Property

    #region Children Property
    private ObservableCollection<treenodebase> _children;
    public ObservableCollection<treenodebase> Children
    {
        get
        {
            return this._children;
        }
        set
        {
            if (this._children == value)
            {
                return;
            }

            if (this._children != null)
            {
                this._children.CollectionChanged -= _children_CollectionChanged;
            }

            this._children = value;

            if (this._children != null)
            {
                this._children.CollectionChanged += _children_CollectionChanged;
            }

            this.OnPropertyChanged("Children");
            this.OnPropertyChanged("HasChildren");
        }
    }

    void _children_CollectionChanged(object sender, System.Collections.Specialized.NotifyCollectionChangedEventArgs e)
    {
        // collection changed, so fire the PropertyChanged event for HasChildren property.
        this.OnPropertyChanged("HasChildren");
    }
    #endregion Children Property


    public bool HasChildren
    {
        get
        {
            return (Children != null) && (Children.Count > 0);
        }
    }
}  
</treenodebase></treenodebase>

Class ViewModels.FileTreeNode is derived from TreeNodeBase:

public class FileTreeNode : TreeNodeBase
{
    // true for a file, false for a folder
    public bool FileOrFolder { get; set; }

    // name of the file or folder
    public string Name { get; set; }

    // adds a new child and returns it
    public FileTreeNode AddChild(bool fileOrFolder, string name)
    {
        if (this.FileOrFolder)
            throw new Exception("Cannot add a child to a file");

        if (this.Children == null)
        {
            this.Children = new ObservableCollection<treenodebase>();
        }

        FileTreeNode newNode = new FileTreeNode
        {
            FileOrFolder = fileOrFolder,
            Name = name,
            Parent = this
        };

        Children.Add(newNode);

        return newNode;
    }


    #region IsExpanded Property
    private bool _isExpanded = false;
    public bool IsExpanded
    {
        get
        {
            return this._isExpanded;
        }
        set
        {
            if (this._isExpanded == value)
            {
                return;
            }

            this._isExpanded = value;
            this.OnPropertyChanged("IsExpanded");
        }
    }
    #endregion IsExpanded Property
}  
</treenodebase>

It has FileOrFolder Boolean property that is true when the node is a file and false if it is a folder. Also it has Name property corresponding to the file or folder name. Finally it defines IsExpanded property which we use to specify whether the corresponding node is expanded (and you can see its children) or not.

There is also public FileTreeNode AddChild(bool fileOrFolder, string name) method that allows adding a child to the current node, specifying the child's FileOrFolder and Name properties. It returns the child itself so that one can add the children to the newly created child.

The View Model for this sample is built by MySimpleFileSystem class defined under TestViewModels project. It derives from FileTreeNode class. MySimpleFileSystem creates the file system tree in its own constructor. It sets its own name to be "Root" and marks itself as a folder. Then it adds folders "Documents" and "Pictures" to it and a file "MySystemFile". Finally it populates each one of its subfolders:

public MySimpleFileSystem()
{
    this.Name = "Root";
    this.FileOrFolder = false; // root folder;

    // create the docFolder object
    FileTreeNode docFolder = this.AddChild(false, "Documents");

    // create the pictureFolder object
    FileTreeNode pictureFolder = this.AddChild(false, "Pictures");

    // create the systemFile object
    FileTreeNode systemFile = this.AddChild(true, "MySystemFile");

    // add two child files Document1 and Document2 to the docFolder object
    docFolder.AddChild(true, "Document1");
    docFolder.AddChild(true, "Document2");

    // add two child files Picture1 and Picture2 to the pictureFolder object
    pictureFolder.AddChild(true, "Picture1");
    pictureFolder.AddChild(true, "Picture2");
}  

Visuals for this sample are defined within MainWindow.xaml file of the RecursiveDataTemplateSample main project. We define the tree view model as the Window's resource within that file:

XAML
<!-- The tree-like View Model-->
<testViewModels:MySimpleFileSystem x:Key="TheFileSystem" />

The recursive data template is defined in the same file and is called TheFileRepresentationDataTemplate:

XAML
<DataTemplate x:Key="TheFileTreeRepresentationDataTemplate"
              DataType="viewModels:FileTreeNode">
    <StackPanel Orientation="Vertical">
        <StackPanel Orientation="Horizontal"
                    HorizontalAlignment="Left">
            <!-- Expander toggle button, it only visible for the items that have children -->
            <customControl:MyToggleButton Style="{StaticResource ToggleButtonExpanderStyle}"
                                          IsChecked="{Binding Path=IsExpanded, Mode=TwoWay}"
                                          Visibility="{Binding Path=HasChildren,
                                                           Converter={StaticResource TheBooleanToVisibilityConverter}}" />
            <!--name of the file or folder-->
            <TextBlock Margin="10,0,0,0"
                       Text="{Binding Name}" />
        </StackPanel>

        <!-- This is the ItemsControl that represents the children of the current node
              It is only visible if the current node is expanded-->
        <!-- Note that ItemTemplate of the ItemsControl is set to the 
             DataTemplate we are currently in: TheFileTreeRepresentation.
             This is a recursive template. 
        -->
        <ItemsControl Margin="30,0,0,0"
                      ItemsSource="{Binding Children}"
                      ItemTemplate="{DynamicResource TheFileTreeRepresentationDataTemplate}"
                      Visibility="{Binding Path=IsExpanded, Converter={StaticResource TheBooleanToVisibilityConverter}}" />
    </StackPanel>
</DataTemplate>  

Note that the ItemsControl for the Children of the current node is referring to the same data template - TheFileTreeRepresentationDataTemplate: ItemTemplate="{DynamicResource TheFileTreeRepresentationDataTemplate}". We are forced to use DynamicResource here since the DataTemplate references itself.

Finally we "marry" the DataTemplate and the View Model by using the ContentControl:

XAML
<ContentControl ContentTemplate="{StaticResource TheFileTreeRepresentationDataTemplate}"
                Content="{StaticResource TheFileSystem}" 
                HorizontalAlignment="Left"
                VerticalAlignment="Top"
                Margin="20,20,0,0"/>

Note, that the same pattern of recursive DataTemplates can be used for much more complex controls than the one shown in the sample. E.g. I used it to display a full organization chart for a large organization.

Conclusion

In this article we presented the patterns that can be used for creating visuals that mimic behaviors defined by non-visual objects - Views mimicking the behavior of View Models. Next article will talk about more complex (architectural) patterns.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
Architect AWebPros
United States United States
I am a software architect and a developer with great passion for new engineering solutions and finding and applying design patterns.

I am passionate about learning new ways of building software and sharing my knowledge with others.

I worked with many various languages including C#, Java and C++.

I fell in love with WPF (and later Silverlight) at first sight. After Microsoft killed Silverlight, I was distraught until I found Avalonia - a great multiplatform package for building UI on Windows, Linux, Mac as well as within browsers (using WASM) and for mobile platforms.

I have my Ph.D. from RPI.

here is my linkedin profile

Comments and Discussions

 
Questionvote 5 Pin
Beginner Luck7-Aug-16 21:16
professionalBeginner Luck7-Aug-16 21:16 
AnswerRe: vote 5 Pin
Nick Polyak9-Aug-16 8:16
mvaNick Polyak9-Aug-16 8:16 
GeneralMy vote of 3 Pin
MahBulgaria22-Aug-14 1:39
MahBulgaria22-Aug-14 1:39 
GeneralRe: My vote of 3 Pin
Nick Polyak22-Aug-14 5:03
mvaNick Polyak22-Aug-14 5:03 
GeneralRe: My vote of 3 Pin
MahBulgaria26-Aug-14 4:05
MahBulgaria26-Aug-14 4:05 
GeneralRe: My vote of 3 Pin
Nick Polyak26-Aug-14 7:15
mvaNick Polyak26-Aug-14 7:15 
GeneralRe: My vote of 3 Pin
MahBulgaria1-Sep-14 4:22
MahBulgaria1-Sep-14 4:22 
GeneralRe: My vote of 3 Pin
Nick Polyak7-Sep-14 16:29
mvaNick Polyak7-Sep-14 16:29 
GeneralMy vote of 5 Pin
Volynsky Alex10-Aug-14 11:48
professionalVolynsky Alex10-Aug-14 11:48 
GeneralRe: My vote of 5 Pin
Nick Polyak10-Aug-14 12:49
mvaNick Polyak10-Aug-14 12:49 
GeneralRe: My vote of 5 Pin
Volynsky Alex11-Aug-14 9:50
professionalVolynsky Alex11-Aug-14 9:50 
Thumbs Up | :thumbsup: Thumbs Up | :thumbsup: Thumbs Up | :thumbsup:
Всегда пожалуйста! Smile | :)
GeneralMy vote of 5 Pin
Diana Lucia10-Aug-14 2:25
Diana Lucia10-Aug-14 2:25 
GeneralRe: My vote of 5 Pin
Nick Polyak10-Aug-14 4:08
mvaNick Polyak10-Aug-14 4:08 
GeneralMy vote of 5 Pin
vperezquilez5-Aug-14 20:16
vperezquilez5-Aug-14 20:16 
GeneralRe: My vote of 5 Pin
Nick Polyak6-Aug-14 2:11
mvaNick Polyak6-Aug-14 2:11 
GeneralGreat Article Pin
Abhishek Kumar Goswami4-Aug-14 3:57
professionalAbhishek Kumar Goswami4-Aug-14 3:57 
GeneralRe: Great Article Pin
Nick Polyak4-Aug-14 6:02
mvaNick Polyak4-Aug-14 6:02 
GeneralMy Vote of 50,000 Pin
Austin Mullins31-Jul-14 12:44
Austin Mullins31-Jul-14 12:44 
GeneralRe: My Vote of 50,000 Pin
Nick Polyak31-Jul-14 16:17
mvaNick Polyak31-Jul-14 16:17 
GeneralMy vote of 5 Pin
Member 1052531729-Jul-14 20:42
Member 1052531729-Jul-14 20:42 
GeneralRe: My vote of 5 Pin
Nick Polyak30-Jul-14 3:41
mvaNick Polyak30-Jul-14 3:41 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.