in

vbCity Blogs

New (temp) place for vbCity Blogs

Ged Mead's Blog

November 2009 - Posts

  • WPF: How To Display Master-Detail Data In a TreeView (Part 2)

     

     

    Introduction
    In this earlier blog I used an XML file containing nested data items as the data source of a TreeView. In this version I will use a collection of objects as the data source.
    The details of the data are much the same as I used in the previous blog, with a couple of tiny changes to avoid any possible confusion caused by the use of Visual Basic keywords as field names:

    These classes are saved in a VB file named SalesData.vb. Because it is fairly lengthy, filling about three screen display lengths, I've made it available from this link. (To a large extent, how the the data is created isn't really the topic of this blog - I'm more concerned with the Binding to a source and the templates used for displaying the data.)

    Mapping to the local Assembly
    In order to be able to access those SalesPerson, SalesOrder, etc classes and objects in the XAML file, it is necessary to create a mapping. The syntax for this is as follows:  

      xmlns:local="clr-namespace:HierarchicalDataTemplate" 

    It isn't mandatory to use 'local' as the mapping alias, but it's a fairly traditional approach. In this case, of course 'HierarchicalDataTemplate' is the name of the project I am working on.

    With the namespace mapping in place, any of the classes that currently exist in the code-behind files of the project become visible to the XAML file. If you view the code listing for the SalesData.vb file, you will see that the class which creates the demo data is called 'SalesPersonList'. It's now possible to create a new instance of that class in the XAML file : 

        <local:SalesPersonList x:Key="SalesPersonList"/> 

    I generally place this in the Window.Resources collection.

    Creating a TreeView and Binding its Data Source
    In the markup for the Window itself, I'll create a TreeView which contains a single TreeViewItem.  

        <TreeView>

          <TreeViewItem ItemsSource="{Binding Source={StaticResource SalesPersonList}}"

              Header="Sales Figures" />

        </TreeView> 

    The ItemsSource is the crucial property here. It identifies exactly where the TreeView should look for its data. In this case it looks into the SalesPersonList instance that I created a few moments ago. For the avoidance of doubt, the exact 'SalesPersonList' that is used is the StaticResource created earlier and identified by the Key. (I possibly should have used a different name for the Key and the underlying Class to avoid any confusion).

    Because of the use of the Binding and the HierarchicalDataTemplates, it is only necessary to create this single TreeViewItem. The Binding engine will trawl through all the data and create as many TreeViewItem nodes as it needs to in order to display everything correctly. 

    HierarchicalDataTemplates
    This example uses three HierarchicalDataTemplates, plus a standard DataTemplate for the SalesItems. Here is the first one (which again I've placed in the Window.Resources collection):  

        <HierarchicalDataTemplate DataType="{x:Type local:SalesPerson}"

              ItemsSource="{Binding Path=Periods}">

          <TextBlock Text="{Binding Path=Name}"/>

        </HierarchicalDataTemplate> 

    The DataType property identifies which type of object will be dealt with by this template. In the case of this first template, this will be the SalesPerson type. In other words, when the Binding that I placed on the TreeViewItem finds any instance of a SalesPerson inside that SalesPersonList, it will look at this template to discover how it should display SalesPerson details.
    In this case, the Name property of the current SalesPerson will be shown in a TextBlock.
    The ItemsSource property also uses a Binding, but it is only interested in knowing what needs to be shown as the child data of SalesPersons. As we know from the diagram above, this will be the Periods data.

    If you are finding the DataType and the Bindings' Paths a bit tricky to grasp, it may help if you run the project as it currently stands. Here's the markup for the Window so far: 

    <Window x:Class="OrdersListsDisplay"

       xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"

       xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"

       xmlns:local="clr-namespace:HierarchicalDataTemplate"

       Title="Sales List Display" Height="300" Width="300">

      <Window.Resources>

        <local:SalesPersonList x:Key="SalesPersonList"/>

     

        <!--  Data Templates -->

        <HierarchicalDataTemplate DataType="{x:Type local:SalesPerson}"

              ItemsSource="{Binding Path=Periods}">

          <TextBlock Text="{Binding Path=Name}"/>

        </HierarchicalDataTemplate>

     

      </Window.Resources>

        <Grid>

        <TreeView>

          <TreeViewItem ItemsSource="{Binding Source={StaticResource SalesPersonList}}"

              Header="Sales Figures" />

        </TreeView>

      </Grid>

    </Window> 

    When you run this, you will first see:

    Then, when you expand the first node, you will have:

    Finally, clicking on either or both the SalesPerson nodes, you will see that the application has tried to display the children of the SalesPersons for you. In the absence of any instruction about formatting the Periods, it simply reverts to displaying the default ToString rendering of the class.

    Hopefully though you can now see why the template points to SalesPerson as its DataType, but identifies the next level down the tree as the ItemsSource. (You may have noticed that this wasn't necessary with the XML data example in the earlier blog, where simply assigning a Binding without a Path will work.)

    Displaying Periods and SalesOrders
    Exactly the same approach is used for the next two templates: 

        <HierarchicalDataTemplate DataType="{x:Type local:Period}"

              ItemsSource="{Binding Path=SalesOrders}">

          <TextBlock Text="{Binding Path=Name}"/>

        </HierarchicalDataTemplate>

     

        <HierarchicalDataTemplate DataType="{x:Type local:SalesOrder}"

             ItemsSource="{Binding Path=SalesItems}">

          <TextBlock Text="{Binding Path=Name}"/>

        </HierarchicalDataTemplate> 

    The three HierarchicalDataTemplates in place so far will produce this result:

    which should come as no surprise, based on what I explained earlier.

    ItemDetail DataTemplate
    So that just leaves the template for the SalesItems and ItemDetail data. This time a standard DataTemplate will do, because we know there is no further data below ItemDetail.  

        <DataTemplate DataType="{x:Type local:SalesItem}">

          <TextBlock Text="{Binding Path=ItemDetail}"/>

        </DataTemplate> 

    Just for the record, a HierarchicalDataTemplate would work but isn't necessary. With the final DataTemplate added, every level of the Master-Detail data can be accessed:

    Formatting
    You have many options for formatting the final presentation of the data. In this example, you can change the color, font size and weight, indentation, etc via the individual TextBlock properties. If you wanted to insert additional graphical detail (such as an icon), you simply wrap the TextBlock in a StackPanel with its Orientation set to Horizontal. You can then insert the icon image as an additional child of this StackPanel.

    You can build on this basic example shown here to create a much more complex display.

  • WPF HierarchicalDataTemplates and Master-Detail Data Display

     Introduction
    For some reason, I struggled with HierarchicalDataTemplates when I first tried to use them. Although I understood the overall theory that they walk the data tree and can create formatted, nested output, I always seemed to spend an inordinately long time trying to get things to work just as I want them (or sometimes, to be truthful, to work at all!)  

    So I thought I would work through a few samples over the space of a few blogs, building up the complexity as I go. Hopefully, if you too have come unstuck using these kind of templates, this step by step approach might be helpful to you.

    Some Data
    I'll start by using some XML data. This has the advantage that you can physically see the tree-like composition of the data. Here is a schematic of the overall structure of the data:

     

        

     

      

    As you can see from the diagram, each Salesperson can have several children named 'Period', each Period can have several children named ''Order' and each Order can have several children named 'Item'.

    To keep things short and simple, I have only included two SalesPerson instances in the demo file. Here is the content of the XML data file:  

    <SalesFigures xmlns="">

      <SalesPerson Name="Jean Price">

     

        <Period PeriodName="January">

          <Order OrderNumber="Order # JAN001">

            <Item>500 Widgets</Item>

            <Item>120 Gizmos</Item>

          </Order>

          <Order OrderNumber="Order # JAN002">

            <Item>200 Wiggles</Item>

            <Item>20 Schmoos</Item>

            <Item>312 Linguines</Item>

          </Order>

        </Period>

     

        <Period PeriodName="February">

          <Order OrderNumber="Order # FEB001">

            <Item>1000 Widgets</Item>

            <Item>76 Schmoos</Item>

          </Order>

        </Period>

      </SalesPerson>

     

    <!-- Second SalesPerson -->

      <SalesPerson Name="John P Grant">

        <Period PeriodName="January">

          <Order OrderNumber="Order # JAN001">

            <Item>200 Widgets</Item>

            <Item>120 Woggles</Item>

          </Order>

          <Order OrderNumber="Order # JAN002">

            <Item>100 Wiggles</Item>

            <Item>222 Linguines</Item>

          </Order>

        </Period>

     

        <Period PeriodName="February">

          <Order OrderNumber="Order # FEB001">

            <Item>75 Smashies</Item>

            <Item>176 Widgets</Item>

            <Item>750 Small Blingshies</Item>

            <Item>110 Scoobs</Item>

          </Order>

        </Period>

      </SalesPerson>

     

    </SalesFigures> 

    Accessing the XML data
    When using XML data as the source, you create an XMLDataProvider in the markup. This usually consists of three elements:

    • A Key, which you can use later to refer to it.
    • A Source, which points to the location of the data (the XML file)
    • An XPath, which identifies the starting point of the data tree.  

    The XMLDataProvider can be placed in the Resources collection of the Window: 

      <Window.Resources>

        <XmlDataProvider x:Key="SalesInfo"

           Source="SalesStats.xml"

           XPath="/SalesFigures"></XmlDataProvider>

      </Window.Resources> 

    The Templates
    The key pieces of HierarchicalDataTemplates are the DataType property and the ItemsSource property. The following template is also stored in the Window.Resources collection:  

        <HierarchicalDataTemplate DataType="SalesPerson"

            ItemsSource ="{Binding}">

          <TextBlock Text="{Binding XPath=@Name}" />

        </HierarchicalDataTemplate> 

    The DataType identifies the Type to which this template will be applied. Thanks to the magic of WPF DataBinding, this is done almost automatically - that is to say that whenever an instance of the target DataType is found as the binding engine does its work, this template will be used to display it. Our XML data file contains a type named SalesPerson and each time one is found, the template will be used. The ItemsSource property is easy to use in this case and requires only the inclusion of the 'Binding' extension as shown.

    Specifically in this case the template will employ a TextBlock to display the Name of the particular Salesperson instance. Because the data is XML based, XPath is used in place of the usual Path syntax and the @ symbol is used to signify that the binding needs to look for an attribute rather than an element.

    First Test Run
    In order to see the result so far, we'll add a TreeView to the WPF Window. 

        <TreeView >

          <TreeViewItem

            ItemsSource="{Binding Source={StaticResource SalesInfo}, XPath=*}"

           Header="SalesFigures"></TreeViewItem>

        </TreeView> 

    Note the use of a single TreeViewItem and the Binding Source being set to that XMLDataProvider. The XPath setting uses the asterisk notation, which means that the search will begin from the start of the data source (Or from the top of the tree, if you prefer to think of it in those terms).

    If you run this project as-is, it starts out as you would expect:

    Both the Salesperson instances are shown, each on its own node. However, if you expand either of those SalesPersons you will be less pleased with the result:

    The reason for this result is that the data template is targeted specifically at the Name attribute of SalesPerson instances and the TreeView doesn't really know how to display anything else it subsequently finds as it traverses deeper into the data. As you can see, it ignores any Attributes, but concatenates all the Elements - in this case the Items elements - it finds.

    The answer of course is to create templates for each level and in this example this requires two more HierarchicalDataTemplates. Again, these are placed in the Window.Resources. 

       <HierarchicalDataTemplate DataType="Period"

          ItemsSource ="{Binding}">

          <TextBlock Text="{Binding XPath=@PeriodName}" />

        </HierarchicalDataTemplate>

     

        <HierarchicalDataTemplate DataType="Order"

          ItemsSource ="{Binding}" >

          <TextBlock Text="{Binding XPath=@OrderNumber}" />

        </HierarchicalDataTemplate> 

    The approach used is exactly the same as for the first template - identify the DataType, set the ItemsSource to start searching from the top of the bound data tree and use a TextBlock for the display.

    Of course, you can format the text - e.g. changing the font weight or color and so on - just by setting properties on the individual TextBlocks in the Data Templates:

     

    I'll look a bit deeper into this in future blogs, including looking at other kinds of data sources.   You can read the one that creates a List collection of SalesPerson objects and displays the Master-Detail data here. 

     

  • A Better WPF TreeViewItem Grouping Sample

     In previous blogs I was looking at ways of grouping TreeViewItems. Those blogs included other features, such as changing the visibility of individual nodes based on the data content of each field. The blog that dealt with TreeViewItem visibility is here and the one that expanded the idea to include grouping is here. Although it worked to an extent, I wasn't really happy with the final result and you know how that can niggle at you - especially if you have gone public with it! So I've done some more research and now have a version that looks right.

    In this blog I'm going to leave out the ValueConverter/Visibility feature, as it tends to get in the way of the core layout steps. I will post up a second version later that does include this.

    I'm indebted to Bea Costa for laying the groundwork on this in a blog she wrote back in the days when WPF was still at the Orcas Beta stage. I've tweaked the original a little and converted the C# code to Visual Basic.

    Here is the finished result we are looking for:

    The Data
    As with the earlier blogs, I'm using a Person class which has three properties - FullName, Status and Category. The plan is to have FullName on one line of a TreeView node with Status immediately below it on a second line. Each of these two line sets will be grouped according to their Category property value.

    Here's the code for the Person class:

     

    Imports System.ComponentModel

     

    Public Class Person

        Implements INotifyPropertyChanged

     

        Sub New(ByVal personname As String, ByVal personstatus As String, ByVal personcategory As String)

            Me.FullName = personname

            Me.Status = personstatus

            Me.Category = personcategory

        End Sub

     

        Private _name As String

        Public Property FullName() As String

            Get

                Return _name

            End Get

            Set(ByVal value As String)

                _name = value

                OnPropertyChanged(New PropertyChangedEventArgs("FullName"))

            End Set

        End Property

     

        Private _status As String

        Public Property Status() As String

            Get

                Return _status

            End Get

            Set(ByVal value As String)

                _status = value

                OnPropertyChanged(New PropertyChangedEventArgs("Status"))

            End Set

        End Property

     

        Private _Category As String

        Public Property Category() As String

            Get

                Return _Category

            End Get

            Set(ByVal value As String)

                _Category = value

                OnPropertyChanged(New PropertyChangedEventArgs("Category"))

            End Set

        End Property

     

        Public Sub OnPropertyChanged(ByVal e As PropertyChangedEventArgs)

            If Not PropertyChangedEvent Is Nothing Then

                RaiseEvent PropertyChanged(Me, e)

            End If

        End Sub

     

        Public Event PropertyChanged(ByVal sender As Object, ByVal e As System.ComponentModel.PropertyChangedEventArgs) Implements System.ComponentModel.INotifyPropertyChanged.PropertyChanged

     

    End Class

     

    In tis version, I introduce a Persons class. This replaces the Shared method to generate some dummy data that I was using in previous versions. The end result is the same - a List (Of Person) that can be used as the data source for the TreeView.

    Here's the code for that class:

     

    Public Class Persons

     

        Private _personlist As List(Of Person)

     

        Public ReadOnly Property PersonList() As IEnumerable(Of Person)

            Get

                Return _personlist

            End Get

        End Property

     

     

        Public Sub New()

            _personlist = New List(Of Person)()

            _personlist.Add(New Person("Ged Mead", "Busy", "VB City"))

            _personlist.Add(New Person("Joe Brown", "On Site", "Work Colleagues"))

            _personlist.Add(New Person("Serge Baliansky", "North Office", "VB City"))

            _personlist.Add(New Person("Fran Pickman", "East Centre", "Family"))

            _personlist.Add(New Person("Elaine Jones", "Not Available", "Work Colleagues"))

            _personlist.Add(New Person("Matt Bianca", "South Office", "VB City"))

        End Sub

    End Class

     

    TreeView UserControl
    To remain consistent with the original blogs, which created a WPF UserControl that can be hosted in a Windows Forms project, I'll build a UserControl here too. Of course, if you want to have this grouped TreeView in an all-WPF project then you can simply build it inside a Window.

    As before, it is necessary to map the assembly to an XML namespace in the XAML pane so that you can access the Persons data.

     

       xmlns:local="clr-namespace:GroupingTest"

     

    With that in place, an instance of the Persons class can be created in the XAML markup. This is stored in the UserControl.Resources collection:

     

      <UserControl.Resources>

        <local:Persons x:Key="People" />

     

    (There are more items to go into that Resources collection yet, so I haven't closed it off).

    CollectionView
    In the earlier blogs, I created the CollectionView and added a new PropertyGroupDescription in the code-behind. There is an example and a description of how CollectionView works here. This time I'm going to use a XAML version of the same thing. It makes things slightly easier because the Persons instance has already been created in XAML in the Resources collection too.

     

        <CollectionViewSource x:Key="cvs" Source="{Binding Source={StaticResource People}, Path=PersonList}">

          <CollectionViewSource.GroupDescriptions>

            <PropertyGroupDescription PropertyName="Category"/>

          </CollectionViewSource.GroupDescriptions>

        </CollectionViewSource>

     

    If you compare this to the code-behind VB version, the mapping is very clear. The Binding in the first line of the markup may look a bit complex at first, but essentially it :

    • Finds the Persons instance via the key that was assigned to it in the line above.
    • Then within that Persons instance it points the Path to the list of Person objects that are stored in the PersonList collection.

     

    DataTemplates
    Now that the data exists and is accessible, the next step is to create the DataTemplates. There will be two of them and the order in which you place them in the XAML file is important.

    The first template will be for the child items - that is, the two lines that contain the FullName and the Status property values. Here is the markup which creates it (again this can be stored in the UserControl.Resources collection):

     

        <DataTemplate x:Key="PersonandStatusTemplate">

          <StackPanel>

            <TextBlock Text="{Binding Path=FullName}"/>

            <TextBlock Text="{Binding Path=Status}"

             Padding="0,0,0,3" Foreground="DarkGreen"/>

          </StackPanel>

        </DataTemplate>

     

    This is pretty much the same template as used in the previous blogs. I've just tweaked a bit of the formatting.

    The key change to making the grouping work properly is the next template, a HierarchicalDataTemplate:

     

        <HierarchicalDataTemplate x:Key="categoryTemplate"

               ItemsSource="{Binding Path=Items}"

               ItemTemplate="{StaticResource PersonandStatusTemplate}">

          <TextBlock Text="{Binding Path=Name}" FontWeight="Bold"

                    Margin="0,5,0,0"/>

        </HierarchicalDataTemplate>

     

    The ItemsSource points to the specific data to be shown via this template and in this case it is the collection of items stored in the PersonList.
    The ItemTemplate is used for the display of the children (the FullName and Status values) and points to the DataTemplate named PersonandStatusTemplate. This is why the order of elements in the XAML file is important. PersonandStatusTemplate must pre-exist in order for the HierarchicalDataTemplate to find it.

    The Binding Path for the TextBlock Text has the potential to mightily confuse. As you see, it points to 'Name'. As there is no 'Name' field in the data source this may seem strange. 'Name' in this context points to the name assigned to the PropertyGroupDescription in the CollectionViewSource earlier in the XAML file. (If you want a more detailed explanation of what is going on, you can read this earlier blog - the relevant part being somewhere towards the end.)

    So, the logic of this setup is that

    • An instance of the Persons class is created
    • This instance contains a collection of Person objects.
    • A PropertyGroupDescription in a CollectionViewSource identifies the 'Category' property as the value on which it will group items.
    • A DataTemplate will format the look of the FullName and Status fields of each item.
    • A HierarchicalDataTemplate recurses through the items, groups them according to its PropertyGroupDescription (Category, in this case) and displays the children using the PersonandStatusTemplate.

     

    Using the UserControl
    If you use this UserControl in a WPF project, you need to map the assembly to an XML namespace - something you will be coming quite familiar with if you have tried any of my earlier blogs! You can then create a new instance of the UserControl that I have named GTV.

     

    <Window x:Class="Window3"

       xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"

       xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"

       xmlns:local="clr-namespace:GroupingTest"

       Title="TreeView UserControl In Window" Height="400" Width="330">

        <Grid>

        <local:GTV Width="160" Margin="0,15" />

     

      </Grid>

    </Window>

     

    As I mentioned earlier, this TreeView isn't so complicated that it deserves UserControl status within a WPF project and I have only taken this route because it was originally created in order to be housed in a WinForms project. If you need to use this in a WinForms project, you can follow the steps shown in the earlier blogs and replace the UserControl with this one.

    You can download the sample WPF project from here. It contains two Windows: one uses the UserControl, the other builds the TreeView inside the Window itself.

    So I'm happy now with the look and functionality of the grouped TreeView. It has taken more work and research than I expected, mostly because I had previously found it quite hard to get to grips with HierarchicalDataTemplates. I hope that this example and description will help anyone else who has also struggled with it.

  • Grouping the Two Line TreeView Items

     In this earlier blog I described how to change the visibility of a line in a TreeView which has TreeViewItems that consist of two lines each. The original requirement also included grouping the entries under the different Categories. I purposely didn't include that at the time, because I thought it would obscure the main topic - i.e. changing the visibility. So in this item I will just expand the original project to include grouping.

    The first step is to create a new View using ICollectionView. This is in the System.ComponentModel namespace, so I will use an Imports statement for this.
    The easiest way to get the view is to use the GetDefaultView method of CollectionViewSource. This can be found in the System.Windows.Data namespace.
    Here is the code:  

    Imports System.ComponentModel

    Imports System.Windows.Data

     

    Partial Public Class TwoLineTreeView

        Dim Contacts As New List(Of Person)

     

     

        Private Sub TwoLineTreeView_Loaded(ByVal sender As Object, ByVal e As System.Windows.RoutedEventArgs) Handles Me.Loaded

            Contacts = Person.GetPersons

            Me.DataContext = Contacts

     

     

            Dim currentView As ICollectionView

            currentView = CollectionViewSource.GetDefaultView(Contacts)

     

            '  Group by category

            currentView.GroupDescriptions.Add(New PropertyGroupDescription("Category"))

     

        End Sub

     

    End Class 

    If you want more detail of how CollectionView works, you might want to read my blog here. The key point is that a CollectionView is created automatically behind the scenes when you set up the Binding.

    Once you make this view available, you can use it in the GroupStyle of the TreeView. Specifically, you create a new DataTemplate for the HeaderTemplate. Here is the markup for this part of the TreeView:  

          <TreeView.GroupStyle>

            <GroupStyle>

              <GroupStyle.HeaderTemplate>

                <DataTemplate>

                  <TreeViewItem   IsExpanded="True"> 

                  <TextBlock Text="{Binding Path=Name}"

                    Margin="2"   FontWeight="Bold"/>

                    </TreeViewItem>

                </DataTemplate>

              </GroupStyle.HeaderTemplate>

            </GroupStyle>

          </TreeView.GroupStyle> 

    The only part of this markup that might confuse you is the use of a Path value of 'Name' in the TextBlock.Text property. This isn't Name, as in the name of any of the Person instances, but represents the name given to the PropertyGroupDescription when it was added to the current view's GroupDescriptions.

    When you run the project, each of the Person instances is grouped under the heading of their Status value:

    I'm not entirely happy with the blank line between each TreeViewItem.  I suspect this is caused because there is no value being directly assigned to the TreeViewItem's Header property.    This causes an empty header to be displayed.      I'm looking into this and will post a follow up when I find the answer.

    You can access the demo project used for this blog from here.

     

  • Controlling TreeView Item Visibility

    An interesting question came up recently, where someone wanted to have a TreeView in which:

    • Each TreeViewItem consisted of two separate lines of content
    • but the second line may be hidden, depending on its content.  

    This was actually a Windows Forms question, but I wondered if this would be a good candidate for harnessing the graphical power of WPF to solve a tough WinForms problem. My idea was to create a WPF UserControl that had the features described above and then host this User Control in an ElementHost in the Windows Form.

    It seemed like a reasonable proposition and here's how I went about it.

    Data Source
    First of all I need some data, so will use a simple Person class.  

     

    Imports System.ComponentModel

    Imports System.Collections.ObjectModel

     

    Public Class Person

        Implements INotifyPropertyChanged

     

        Sub New(ByVal personname As String, ByVal personstatus As String, ByVal personcategory As String)

            Me.FullName = personname

            Me.Status = personstatus

            Me.Category = personcategory

        End Sub

     

        Private _name As String

        Public Property FullName() As String

            Get

                Return _name

            End Get

            Set(ByVal value As String)

                _name = value

                OnPropertyChanged(New PropertyChangedEventArgs("FullName"))

            End Set

        End Property

     

     

        Private _status As String

        Public Property Status() As String

            Get

                Return _status

            End Get

            Set(ByVal value As String)

                _status = value

                OnPropertyChanged(New PropertyChangedEventArgs("Status"))

            End Set

        End Property

     

     

        Private _Category As String

        Public Property Category() As String

            Get

                Return _Category

            End Get

            Set(ByVal value As String)

                _Category = value

                OnPropertyChanged(New PropertyChangedEventArgs("Category"))

            End Set

        End Property

     

     

        Public Sub OnPropertyChanged(ByVal e As PropertyChangedEventArgs)

            If Not PropertyChangedEvent Is Nothing Then

                RaiseEvent PropertyChanged(Me, e)

            End If

        End Sub

     

     

        Public Event PropertyChanged(ByVal sender As Object, ByVal e As System.ComponentModel.PropertyChangedEventArgs) Implements System.ComponentModel.INotifyPropertyChanged.PropertyChanged

     

     

        Public Shared Function GetPersons() As List(Of Person)

            Dim GP As New List(Of Person)

            GP.Add(New Person("Ged Mead", "Busy", "VB City"))

            GP.Add(New Person("Joe Brown", "On Site", "Work Colleagues"))

            GP.Add(New Person("Serge Baliansky", "North Office", "VB City"))

            GP.Add(New Person("Fran Pickman", "East Centre", "Family"))

            GP.Add(New Person("Elaine Jones", "Not Available", "Work Colleagues"))

            GP.Add(New Person("Matt Bianca", "South Office", "VB City"))

            Return GP

        End Function

     

    End Class

     

    As you see, this class implements INotifyPropertyChanged, so that changes to instance properties can be caught and any appropriate action taken as a result. I also included a basic method to generate some dummy data.

    The Basic TreeView
    The TreeView will be a WPF UserControl, so it is necessary to create this inside the Windows Forms project. Use the Project > Add New Item menu for this, then select WPF from the selection window that appears and name the WPF UserControl:

       

    In the code-behind, you create a DataContext which will be the source of the data that is used to populate the TreeView:

    Partial Public Class TwoLineTreeView

        Dim Contacts As New List(Of Person)

     

     

        Private Sub TwoLineTreeView_Loaded(ByVal sender As Object, ByVal e As System.Windows.RoutedEventArgs) Handles Me.Loaded

            Contacts = Person.GetPersons

            Me.DataContext = Contacts

        End Sub

     

    End Class 

    An easy way to package up the two line content display is to use two TextBlocks inside a StackPanel. These would all be stored in a DataTemplate that the TreeView will use for its ItemTemplate property. Here's the first pass at the DataTemplate which I have placed in the Resources section of the UserControl named TwoLineTreeView. 

      <UserControl.Resources>

         <DataTemplate x:Key="TVVis" >

          <TreeViewItem IsExpanded="True">

     

            <StackPanel>

              <TextBlock Text="{Binding Path=FullName}"

                      Margin="2,0,0,1"></TextBlock>

              <TextBlock Text="{Binding Path=Status}"

                      Margin="8,3,2,2"></TextBlock>

            </StackPanel>

          </TreeViewItem>

        </DataTemplate>

      </UserControl.Resources> 

    The first TextBlock binds to the Person's name and the second one binds to their Status.

    We can test this early stage by adding a TreeView control to the UserControl: 

      <Grid>

        <TreeView ItemsSource="{Binding}"

             ItemTemplate="{StaticResource TVVis}"   >

        </TreeView>

     

      </Grid> 

    The Grid is inserted in the UserControl by default and although we don't really need it in this example there's no great value in deleting it.

    If you now move to the Windows Form, you can add an ElementHost and populate it with this data bound user control. You can either find the ElementHost control in the Toolbox and drag an instance of it on to the surface of the form. If it hasn't been added to the Toolbox (and as it is a Windows Forms project it doesn't always appear by default) you can simply find the UserControl itself in the Toolbox and drag this on to the Form.

       

     

    When you do so, an ElementHost will be created for you on the Form and will automatically offer you the TwoLineTreeView user control as the Child of that ElementHost. Run the project and you will see:

       

    Enabling and Disabling Visibility of the Second TextBlock

    The easiest way of hiding or showing the second item of data (the status of the Person) is to use a ValueConverter. I've written several blogs describing the use of these and you can check them out if you need more detail on how they work. An introduction to a simple ValueConverter is covered here and a more complex verion here and here.

    Here is the Converter class used in this project: 

    Imports System.Windows.Data

     

    Public Class StatusToVisibilityConverter

        Implements IValueConverter

     

        Public Function Convert(ByVal value As Object, ByVal targetType As System.Type, ByVal parameter As Object, ByVal culture As System.Globalization.CultureInfo) As Object Implements System.Windows.Data.IValueConverter.Convert

            Select Case value.ToString

                Case "Busy", "Not Available", "En Route"

                    Return "Collapsed"

               

                Case Else

                    Return "Visible"

            End Select

        End Function

     

        Public Function ConvertBack(ByVal value As Object, ByVal targetType As System.Type, ByVal parameter As Object, ByVal culture As System.Globalization.CultureInfo) As Object Implements System.Windows.Data.IValueConverter.ConvertBack

            Throw New NotImplementedException()

        End Function

    End Class 

    It tests the value of the Status property and if it finds it is set to Busy, Not Available or En Route it will not display the line. It will display it in all other cases.

    The UserControl requires a namespace mapping before we can use this class to create and use a converter in the XAML pane.

       xmlns:local="clr-namespace:HostedTreeView"

    With that in place, a converter is created inside the UserControl's Resources collection: 

        <local:StatusToVisibilityConverter x:Key="StatusConverter" />

     

    The final step is to use the converter in the markup for the second TextBlock, specifically to hide or show it based on the content. Here is the amended TextBlock markup: 

             <TextBlock Text="{Binding Path=Status}"

               Visibility="{Binding Path=Status, Converter={StaticResource StatusConverter}}" 

               Margin="8,3,2,2"></TextBlock> 

    Now when the project is run, the two Person instances that contain Busy or Not Available as their Status will not display the second line.

     

       

    You can of course take this basic approach and use alternatives or weave a much more complex set of interconnections - perhaps replacing the Status text with another message altogether or totally hiding a Person and their Status if their Status has a particular value. Or whatever other variation meets your needs.  They key task was to create a two line node in which you could control the visibility of the lines.

    You can download the project used in this blog from here.

  • Create and Use a WPF Custom Command - Follow-Up

     

     

    In this earlier blog, I explained how you can create and use a Command to carry out a task based on a variety of user actions. These actions included key presses, mouse actions and key/mouse combinations, as well as clicking on WPF controls. At the time I said that if you didn't want to have the Image move unless the right mouse button was clicked directly on the Image itself (as opposed to clicking anywhere in the Window or on its other children) then you could use Bubbling and Tunneling. When I came to test this theory, however, I couldn't find an obvious way of achieving this. I could stop a right click on the Image working, but that was the exact opposite of what I was trying to do!

    Whether this failure is due to the way the Command infrastructure works or (more likely) my inability to grasp the finer nuances of routing, I've had to admit defeat - at least temporarily. So I thought I should at least post up a workaround for anyone who read that blog item and did want the right click to work only on the Image itself.

    The 'fix', such as it is, is to ditch the InputGesture for MouseAction.RightClick in the InputGestureCollection and use a standard event handler purely for the Image MouseDown event.

    So, the MoveItCommand code becomes:

     

    Public Class MoveItCommand2

     

        Private Shared _moveit As RoutedUICommand

        Public Shared ReadOnly Property MoveIt() As RoutedUICommand

            Get

                Return _moveit

            End Get

        End Property

     

        Shared Sub New()

            '  Add keyboard and mouse gestures

            Dim UserInputs As New InputGestureCollection()

            UserInputs.Add(New KeyGesture(Key.M, ModifierKeys.Alt))

            UserInputs.Add(New KeyGesture(Key.F12, ModifierKeys.None))

            ' UserInputs.Add(New MouseGesture(MouseAction.RightClick, ModifierKeys.None))

            UserInputs.Add(New MouseGesture(MouseAction.LeftClick, ModifierKeys.Shift))

            '  Assign these gestures to the _moveit field (and thereby to the MoveIt property)

            _moveit = New RoutedUICommand("Move Element", "Move", GetType(MoveItCommand), UserInputs)

        End Sub

     

    End Class

     

    You'll notice that I've renamed the class to MoveItCommand2.

    The code behind for the Window now takes an event handler for the Image MouseDown:

     

        Private Sub MoveableImage_MouseDown(ByVal sender As Object, ByVal e As System.Windows.Input.MouseButtonEventArgs) Handles MoveableImage.MouseDown

            If e.ChangedButton = MouseButton.Right Then

                CommandBinding_Executed(MoveableImage, Nothing)

            End If

        End Sub

     

    The code in that event handler simply calls the execution code for the original Command (so at least we retain some semblance of coordination).

    Finally, the markup for the Image in the XAML Pane includes a pointer to the new MouseDown event handler:

     

          <Image x:Name="MoveableImage" Width="55" Source="questionmark2.jpg"

           Canvas.Left="0" Canvas.Top="0"

           MouseDown="MoveableImage_MouseDown"

             />

     

    To avoid confusion, I'll show all the revised markup below, as all references to the Command now have '2' appended to them, and they are easy to miss!

     

    <Window x:Class="Window4"

      xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"

       xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"

       xmlns:local="clr-namespace:GetValue"

       Title="Right Mouse Test" Height="300" Width="300">

     

      <Window.CommandBindings>

        <CommandBinding Command="local:MoveItCommand2.MoveIt"

                       Executed="CommandBinding_Executed"

                       CanExecute="CommandBinding_CanExecute"/>

      </Window.CommandBindings>

     

      <Grid x:Name="MainGrid">

        <Grid.RowDefinitions>

          <RowDefinition Height="Auto" />

          <RowDefinition Height="218*" />

          <RowDefinition Height="Auto" />

        </Grid.RowDefinitions>

     

        <!--  Canvas in the main middle section -->

        <Canvas x:Name="MainCanvas" Grid.Row="1"

            >

          <Image x:Name="MoveableImage" Width="55"

           Source="questionmark2.jpg"

           Canvas.Left="0" Canvas.Top="0"

           MouseDown="MoveableImage_MouseDown" />

        </Canvas>

     

        <!-- Menu at the top -->

        <Menu Grid.Row="0" Margin="3">

          <MenuItem Header="Move It" Margin="5"

            Command="local:MoveItCommand2.MoveIt" />

        </Menu>

     

        <ToolBar Grid.Row="2" >

          <Button Content="Move It"

           Command="local:MoveItCommand2.MoveIt"  />

        </ToolBar>

     

        <Button Grid.Row="1" VerticalAlignment="Bottom"

                 HorizontalAlignment="Right" Width="100"

                 Height="33" Content="Move It" Margin="0,0,4,2"

                 Command="local:MoveItCommand2.MoveIt" />

      </Grid>

    </Window>

     

     

  • How To Create and Use a Custom Command in WPF

      I've got to be honest and admit up front that initially I wasn't completely sold on the idea that WPF Commands are the great leap forward that they are sometimes billed as. OK, so I get that they can reduce the repetition of event handling code, but even their most ardent supporters aren't going to be able to claim that the required code is particularly intuitive. I will agree though that the availability of CanExecute is useful sometimes when multiple controls are bound to a Command. As a WinForms developer moving to WPF, I still find that in many cases I'm happy to use event handlers and, where necessary, a utility method or two.

    So, having got that caveat out of the way, let's look at a basic situation where we want a range of different user actions to trigger the same task.

    I've decided to use the scenario that I covered in this blog. It's maybe not the most realistic scenario, but it does give an opportunity to use a wide range of user inputs and gestures to move the image across the screen. It also allows the use of the CanExecute event.

    In the earlier blog, the user had to click on an image to make it move from left to right across a Canvas. This can soon become tiresome, so by means of a Command we'll give her several other options. In order to make the use of CanExecute realistic, we will say that once the image reaches the far right edge of the Canvas, it must stay there. So the image move will be allowed to happen (i.e. CanExecute) as long as it hasn't reached the right hand edge of the canvas.

    Creating the User Interface
    The following XAML will create an updated version of the Canvas-and-Image UI used previously. This version includes various controls that will be bound to the Command. 

    <Window x:Class="Window3"

       xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"

       xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"

     

       Title="Using A Command" Height="300" Width="300">

     

     

        <Grid x:Name="MainGrid">

        <Grid.RowDefinitions>

          <RowDefinition Height="Auto" />

          <RowDefinition Height="218*" />

          <RowDefinition Height="Auto" />

        </Grid.RowDefinitions>

     

     

          <!--  Canvas in the main middle section -->

        <Canvas x:Name="MainCanvas" Grid.Row="1">

          <Image x:Name="MoveableImage" Width="55" Source="questionmark2.jpg"

     

            Canvas.Left="0" Canvas.Top="0" />

        </Canvas>

     

        <!-- Menu at the top -->

        <Menu Grid.Row="0" Margin="3">

          <MenuItem Header="Move It" Margin="5"

     

                   ></MenuItem>

        </Menu>

     

        <ToolBar Grid.Row="2" >

          <Button Content="Move It"

                  />

        </ToolBar>

     

          <Button Grid.Row="1" VerticalAlignment="Bottom"

                 HorizontalAlignment="Right" Width="100"

                 Height="33" Content="Move It" Margin="0,0,4,2"

                  />

      </Grid>

    </Window>

     (If you've looked at the markup closely and think that the order of elements is haphazard, you would be wrong.   Try moving the MenuItem to what seems a more logical position above the Canvas and you will get an Error when the code is completed.  As you will see, the code behind for the MenuItem will eventually have a link to the Image.   So therefore the Image must be created first in the top down XAML file, before the MenuItem can know about it .  I often think that it's these little potential Gotchas that make the WinForms-to-WPF learning curve so difficult.)

    This is what it should look like:

      

    Creating the Command
    Let's start by creating a new Command Class called MoveItCommand. It will have a single property named MoveIt, a backing field named _move it and a parameterless constructor. The property and field are of type RoutedUICommand, but apart from that are standard and really don't need any additional explanation: 

        Private Shared _moveit As RoutedUICommand

        Public Shared ReadOnly Property MoveIt() As RoutedUICommand

            Get

                Return _moveit

            End Get

        End Property

     The constructor is used to create the various keyboard and mouse gestures that can be used to move the image. For the purposes of demonstration, I've gone totally overboard on these and have included four variations in the final version. But for clarity, I am only showing one in the code snippet below:

         Shared Sub New()

            '  Add keyboard and mouse gestures

            Dim UserInputs As New InputGestureCollection()

            UserInputs.Add(New KeyGesture(Key.M, ModifierKeys.Alt))

     

             '  Assign these gestures to the _moveit field (and thereby to the MoveIt property)

            _moveit = New RoutedUICommand("Move Element", "Move", GetType(MoveItCommand), UserInputs)

     

        End Sub 

    • The constructor begins by creating a new empty collection of mouse gestures.
    • The second line creates and stores a gesture which takes the Alt and M keys as the combination to be used to fire this command. Notice the order of the keys used as arguments - effectively it is "M & Alt" which isn't the way we usually think of the combination. If you switch the order and place the Modifier key first, you will get a runtime error. Trust me, I've been there, done that!
    • Finally, a new RoutedCommand is created, containing descriptive text, its name, owner type (which is this custom command class - MoveItCommand), and the collection of Gestures that will work with this command.

     Note that all the members of this class are Shared, ensuring that only instance of the Command will be in use in the application when it runs.

    Additional Gestures
    If you are happy with how the Class and its Constructor works, I'll now add in those other gestures I mentioned. These include F12 on its own, a mouse click on its own and finally a combination of mouse click and key press. Here's the revised code:  

        Shared Sub New()

            '  Add keyboard and mouse gestures

            Dim UserInputs As New InputGestureCollection()

            UserInputs.Add(New KeyGesture(Key.M, ModifierKeys.Alt))

            UserInputs.Add(New KeyGesture(Key.F12, ModifierKeys.None))

            UserInputs.Add(New MouseGesture(MouseAction.RightClick, ModifierKeys.None))

            UserInputs.Add(New MouseGesture(MouseAction.LeftClick, ModifierKeys.Shift))

             '  Assign these gestures to the _moveit field (and thereby to the MoveIt property)

            _moveit = New RoutedUICommand("Move Element", "Move", GetType(MoveItCommand), UserInputs)

        End Sub

     

    • The first gesture is Alt and M, which I've already covered.
    • The second gesture is the F12 key on its own. Note that you must include Modifiers.None. if you don't, you will get a runtime error.
    • The third gesture will fire the command when the mouse is right clicked anywhere in the Window. Because of the way that WPF and RoutedCommands work, this effectively means that you can still click on the Image to make it move. If you wanted to limit the mouse click to the Image only, you could harness the power of Bubbling and Tunneling events to trap the mouse button press at the Image level. I haven't done that in this example.
    • The final gesture takes a combination of both the mouse left button click together with holding down the Shift key. Quite awkward to use and only included to demonstrate the range of available gestures.

     

    Command Bindings
    Having created the custom Command Class, if we want to use it in the XAML - and we do - then it is necessary to create an XML namespace mapping. Without this link, the XAML markup would have no idea about the existence of the class and its property. The syntax is fairly simple: 

       xmlns:local="clr-namespace:GetValue" 

    The alias of 'local' is used as the key which is mapped to the namespace in which the MoveItCommand class resides. The namespace is the same as the name of the project and in this case the project is named 'GetValue'.

    Given this namespace mapping, we can now set up the CommandBinding in the XAML pane:

     

      <Window.CommandBindings>

        <CommandBinding Command="local:MoveItCommand.MoveIt"

                       Executed="CommandBinding_Executed"

                       CanExecute="CommandBinding_CanExecute"/>

      </Window.CommandBindings>

     

    As you see from the snippet above, you create a new CommandBinding inside a CommandBindings collection. The Command property of the CommandBinding points to that MoveIt property created in the MoveItCommand class.

    The Executed and CanExecute properties point to two methods which we will create next in the code-behind.

    Executing The CommandBinding The task of the CommandBinding_Executed method is to carry out whatever tasks we want to have actioned when the command is invoked. In this demonstration project, this task is simply to shunt the image a few units to the right. So the code is as follows:  

        Private Sub CommandBinding_Executed(ByVal sender As System.Object, ByVal e As System.Windows.Input.ExecutedRoutedEventArgs)

            '  Move the image 5 units to the right

            Dim LeftPos As Double = MoveableImage.GetValue(Canvas.LeftProperty)

            MoveableImage.SetValue(Canvas.LeftProperty, LeftPos + 5)

     

        End Sub 

    (If you are not sure about how the GetValue and SetValue functions work, you can read up on it in my blog here).

    CanExecute
    CanExecute is an optional feature which can be quite useful. In this demonstration, we will only allow the Command to work (and therefore only allow the Image to continue moving to the right) if it hasn't reached the right hand edge of the Canvas. More subtly, if CanExecute becomes set to False, any controls that are bound to the Command will automatically be disabled. I will add Command Bindings to the buttons, etc shortly.

    First, here is the code for the CanExecute method: 

        Private Sub CommandBinding_CanExecute(ByVal sender As System.Object, ByVal e As System.Windows.Input.CanExecuteRoutedEventArgs)

            '  Only allow execution of the command if the image

            '  has not yet reached the right hand edge.

            e.CanExecute = MoveableImage.GetValue(Canvas.LeftProperty) < (MainCanvas.ActualWidth - MoveableImage.Width)

     

        End Sub 

    It tests the current position of the Image to see if it has reached the right hand edge of the Canvas. CanExecute will be set to True if there is still space to the right side of the Image, otherwise it will be set to False.

    The complete code behind for the Window and the Custom Command class together now looks like this: 

    Partial Public Class Window3

     

        Private Sub CommandBinding_Executed(ByVal sender As System.Object, ByVal e As System.Windows.Input.ExecutedRoutedEventArgs)

            '  Move the image 5 units to the right

            Dim LeftPos As Double = MoveableImage.GetValue(Canvas.LeftProperty)

            MoveableImage.SetValue(Canvas.LeftProperty, LeftPos + 5)

     

        End Sub

     

        Private Sub CommandBinding_CanExecute(ByVal sender As System.Object, ByVal e As System.Windows.Input.CanExecuteRoutedEventArgs)

            '  Only allow execution of the command if the image

            '  has not yet reached the right hand edge.

            e.CanExecute = MoveableImage.GetValue(Canvas.LeftProperty) < (MainCanvas.ActualWidth - MoveableImage.Width)

     

        End Sub

    End Class

     

    Public Class MoveItCommand

     

        Private Shared _moveit As RoutedUICommand

        Public Shared ReadOnly Property MoveIt() As RoutedUICommand

            Get

                Return _moveit

            End Get

        End Property

     

        Shared Sub New()

            '  Add keyboard and mouse gestures

            Dim UserInputs As New InputGestureCollection()

            UserInputs.Add(New KeyGesture(Key.M, ModifierKeys.Alt))

            UserInputs.Add(New KeyGesture(Key.F12, ModifierKeys.None))

            UserInputs.Add(New MouseGesture(MouseAction.RightClick, ModifierKeys.None))

            UserInputs.Add(New MouseGesture(MouseAction.LeftClick, ModifierKeys.Shift))

             '  Assign these gestures to the _moveit field (and thereby to the MoveIt property)

            _moveit = New RoutedUICommand("Move Element", "Move", GetType(MoveItCommand), UserInputs)

        End Sub

     

    End Class 

    At this point, you can run the project and use any of those four gestures to move the Image. Once the Image hits the right hand edge, it will stop moving. The menu item, the two buttons don't play any part in the action yet - that is, clicking them will have no effect and they won't become disabled when the Image reaches the right hand limit.

    Binding the Command to the Controls
    All that remains to do is to bind this custom command to the menu item and the two buttons. Now things really do become simple and you can begin to see the potential benefit of using a Command. The following piece of markup is added to the MenuItem, the Button in the Toolbar and the Button in the Canvas:  

            Command="local:MoveItCommand.MoveIt" 

    With those bindings in place, you can move the Image by any of the seven methods included - the four input gestures and by clicking on any of the three controls. Note now that once the Image hits the right hand edge, those three controls all become disabled.

    So, there is a fairly simple example of using your own Command. You can see the final version of the XAML markup here, and also the code behind here.

Copyright 1998-2009 vbCity.com LLC
Powered by Community Server (Non-Commercial Edition), by Telligent Systems