none
Looking for a walkthrough RRS feed

  • Question

  • I am trying to move from WinForms to WPF.

    I am stymied by the treeview control with context menus and drag/drop functionality.  I can't get a grip on RelayCommand, Placement Target, FindAncestor, etc.

    I have read dozens of posts that have fragments of samples and explanation, but I can't fit it together.

    Is there a start-to-finish walkthrough anywhere, that will take me through the process of putting together a simple WPF application with a treeview that includes context menus bound to a ICommand in a view model?  Perhaps as a bonus, this will explain how to implement drag/drop functionality within the treeview or maybe from treeview to datagrid.

    These are the things that are so easy in WinForms, but reportedly so much more robust in WPF.

    If I could just find even a sample that demonstrates how these things work, I can figure it out from there. 

    There are numerous posts with bits and pieces of code which must be helpful to those who already have a good handle on this, but I can't find anything that starts from the beginning.

    Thanks!

    Bill


    bill

    Saturday, November 28, 2015 5:14 PM

Answers

  • Josh Smith wrote a MVVM article about using the treeview, but IMO Josh is some sort of super genius.

    The problem I find when I read an article by someone like that is my brain starts melting.


    The treeview uses something called a headereditemscontrol for each treeviewitem.

    https://msdn.microsoft.com/en-us/library/system.windows.controls.headereditemscontrol(v=vs.110).aspx

    With a default treeviewitem the Header is the thing you see as the label.

    Then it has an itemscontrol inside it. This is for the next level down. So that will be bound using an itemssource, because that's how you connect up itemscontrols to their collection.

    Each node has a collection in it.

    This is important to understand because it's why selecteditem is strange.

    .

    The way you put stuff into a node so it has a collection of items is using a hierarchicaldatemplate. That's a template defines what goes in that header and what collection will be presented to the itemcontrol within it for the next level down.

    This is pretty well illustrated here:

    https://msdn.microsoft.com/en-us/library/system.windows.hierarchicaldatatemplate(v=vs.110).aspx

    Notice the use of datatemplates with the datatype deciding what it's going to be applied to.

          <HierarchicalDataTemplate DataType    = "{x:Type src:Division}"
                                    ItemsSource = "{Binding Path=Teams}">
            <TextBlock Text="{Binding Path=Name}"/>
          </HierarchicalDataTemplate>
    

    The leaf nodes of your tree structure won't have a collection in them and they are covered with a datatemplate

          <DataTemplate DataType="{x:Type src:Team}">
            <TextBlock Text="{Binding Path=Name}"/>
          </DataTemplate>


    I recommend using mvvmlight and relaycommand because icommand is a pita if you don't use some sort of framework.

    Placementtarget lets you get a reference to the specific treeviewitem.

    Relativesource lets you go look somewhere else.

    Let's dodge some of that by defining the contextmenu as a resource.

    Assuming you're ok with the same contextmenu for everything.

    You can do:

        <Window.DataContext>
            <local:MainWindowViewModel/>
        </Window.DataContext>
        <Window.Resources>
            <ContextMenu x:Key="TVContextMenu">
                <Style TargetType="MenuItem">
                      <Setter Property="CommandParameter" Value="{Binding PlacementTarget.DataContext,
                                    RelativeSource={RelativeSource Self}}"/>
                </Style>
                <MenuItem Header="Send" Command="{Binding SendCommand}"/>
            </ContextMenu>
        </Window.Resources>
        <Grid>
            <TreeView Name="trvFamilies" ItemsSource="{Binding Families}"
                      ContextMenu="{StaticResource TVContextMenu}"
                      >
                <HierarchicalDataTemplate DataType="{x:Type local:Family}" ItemsSource="{Binding Members}">
                        <StackPanel Orientation="Horizontal">
                            <TextBlock Text="{Binding Name}" />
                            <TextBlock Text=" [" Foreground="Blue" />
                            <TextBlock Text="{Binding Members.Count}" Foreground="Blue" />
                            <TextBlock Text="]" Foreground="Blue" />
                        </StackPanel>
                    </HierarchicalDataTemplate>
                    <DataTemplate DataType="{x:Type local:FamilyMember}">
                        <StackPanel Orientation="Horizontal">
                            <TextBlock Text="{Binding Name}" />
                            <TextBlock Text=" (" Foreground="Green" />
                            <TextBlock Text="{Binding Age}" Foreground="Green" />
                            <TextBlock Text=" years)" Foreground="Green" />
                        </StackPanel>
                    </DataTemplate>
                <TreeView.ItemContainerStyle>
                    <Style TargetType="{x:Type TreeViewItem}">
                        <Setter Property="ContextMenu" Value="{StaticResource TVContextMenu}"/>
                    </Style>
                </TreeView.ItemContainerStyle>
            </TreeView>


    Notice that each of your commands will have a commandparameter and it's expecting an object which will be whatever that item you right clicked is bound to.

    And here's the viewmodel

        public class MainWindowViewModel
        {
            public ObservableCollection<TVCommand> TVCommands
            { get; set;} = new ObservableCollection<TVCommand>
            {
                new TVCommand {Name="Command One" },
                new TVCommand {Name="Command Two" }
            };
    
            public ObservableCollection<Family> Families { get; set; }
            public MainWindowViewModel()
            { 
    
            Families = new ObservableCollection<Family>();
    
            Family family1 = new Family() { Name = "The Doe's" };
            family1.Members.Add(new FamilyMember() { Name = "John Doe", Age = 42 });
                            family1.Members.Add(new FamilyMember() { Name = "Jane Doe", Age = 39 });
                            family1.Members.Add(new FamilyMember() { Name = "Sammy Doe", Age = 13 });
                            Families.Add(family1);
    
                            Family family2 = new Family() { Name = "The Moe's" };
            family2.Members.Add(new FamilyMember() { Name = "Mark Moe", Age = 31 });
                            family2.Members.Add(new FamilyMember() { Name = "Norma Moe", Age = 28 });
                            Families.Add(family2);
            }
        }
        public class Family
        {
            public Family()
            {
                this.Members = new ObservableCollection<FamilyMember>();
            }
            public string Name { get; set; }
            public ObservableCollection<FamilyMember> Members { get; set; }
        }
    
        public class FamilyMember
        {
            public string Name { get; set; }
            public int Age { get; set; }
        }

    I've not defined that command. It's easy to find examples of relaycommand though.

    https://msdn.microsoft.com/en-gb/magazine/dn237302.aspx


    Hope that helps.

    Technet articles: WPF: MVVM Step 1; All my Technet Articles

    Saturday, November 28, 2015 6:03 PM
    Moderator

All replies

  • Josh Smith wrote a MVVM article about using the treeview, but IMO Josh is some sort of super genius.

    The problem I find when I read an article by someone like that is my brain starts melting.


    The treeview uses something called a headereditemscontrol for each treeviewitem.

    https://msdn.microsoft.com/en-us/library/system.windows.controls.headereditemscontrol(v=vs.110).aspx

    With a default treeviewitem the Header is the thing you see as the label.

    Then it has an itemscontrol inside it. This is for the next level down. So that will be bound using an itemssource, because that's how you connect up itemscontrols to their collection.

    Each node has a collection in it.

    This is important to understand because it's why selecteditem is strange.

    .

    The way you put stuff into a node so it has a collection of items is using a hierarchicaldatemplate. That's a template defines what goes in that header and what collection will be presented to the itemcontrol within it for the next level down.

    This is pretty well illustrated here:

    https://msdn.microsoft.com/en-us/library/system.windows.hierarchicaldatatemplate(v=vs.110).aspx

    Notice the use of datatemplates with the datatype deciding what it's going to be applied to.

          <HierarchicalDataTemplate DataType    = "{x:Type src:Division}"
                                    ItemsSource = "{Binding Path=Teams}">
            <TextBlock Text="{Binding Path=Name}"/>
          </HierarchicalDataTemplate>
    

    The leaf nodes of your tree structure won't have a collection in them and they are covered with a datatemplate

          <DataTemplate DataType="{x:Type src:Team}">
            <TextBlock Text="{Binding Path=Name}"/>
          </DataTemplate>


    I recommend using mvvmlight and relaycommand because icommand is a pita if you don't use some sort of framework.

    Placementtarget lets you get a reference to the specific treeviewitem.

    Relativesource lets you go look somewhere else.

    Let's dodge some of that by defining the contextmenu as a resource.

    Assuming you're ok with the same contextmenu for everything.

    You can do:

        <Window.DataContext>
            <local:MainWindowViewModel/>
        </Window.DataContext>
        <Window.Resources>
            <ContextMenu x:Key="TVContextMenu">
                <Style TargetType="MenuItem">
                      <Setter Property="CommandParameter" Value="{Binding PlacementTarget.DataContext,
                                    RelativeSource={RelativeSource Self}}"/>
                </Style>
                <MenuItem Header="Send" Command="{Binding SendCommand}"/>
            </ContextMenu>
        </Window.Resources>
        <Grid>
            <TreeView Name="trvFamilies" ItemsSource="{Binding Families}"
                      ContextMenu="{StaticResource TVContextMenu}"
                      >
                <HierarchicalDataTemplate DataType="{x:Type local:Family}" ItemsSource="{Binding Members}">
                        <StackPanel Orientation="Horizontal">
                            <TextBlock Text="{Binding Name}" />
                            <TextBlock Text=" [" Foreground="Blue" />
                            <TextBlock Text="{Binding Members.Count}" Foreground="Blue" />
                            <TextBlock Text="]" Foreground="Blue" />
                        </StackPanel>
                    </HierarchicalDataTemplate>
                    <DataTemplate DataType="{x:Type local:FamilyMember}">
                        <StackPanel Orientation="Horizontal">
                            <TextBlock Text="{Binding Name}" />
                            <TextBlock Text=" (" Foreground="Green" />
                            <TextBlock Text="{Binding Age}" Foreground="Green" />
                            <TextBlock Text=" years)" Foreground="Green" />
                        </StackPanel>
                    </DataTemplate>
                <TreeView.ItemContainerStyle>
                    <Style TargetType="{x:Type TreeViewItem}">
                        <Setter Property="ContextMenu" Value="{StaticResource TVContextMenu}"/>
                    </Style>
                </TreeView.ItemContainerStyle>
            </TreeView>


    Notice that each of your commands will have a commandparameter and it's expecting an object which will be whatever that item you right clicked is bound to.

    And here's the viewmodel

        public class MainWindowViewModel
        {
            public ObservableCollection<TVCommand> TVCommands
            { get; set;} = new ObservableCollection<TVCommand>
            {
                new TVCommand {Name="Command One" },
                new TVCommand {Name="Command Two" }
            };
    
            public ObservableCollection<Family> Families { get; set; }
            public MainWindowViewModel()
            { 
    
            Families = new ObservableCollection<Family>();
    
            Family family1 = new Family() { Name = "The Doe's" };
            family1.Members.Add(new FamilyMember() { Name = "John Doe", Age = 42 });
                            family1.Members.Add(new FamilyMember() { Name = "Jane Doe", Age = 39 });
                            family1.Members.Add(new FamilyMember() { Name = "Sammy Doe", Age = 13 });
                            Families.Add(family1);
    
                            Family family2 = new Family() { Name = "The Moe's" };
            family2.Members.Add(new FamilyMember() { Name = "Mark Moe", Age = 31 });
                            family2.Members.Add(new FamilyMember() { Name = "Norma Moe", Age = 28 });
                            Families.Add(family2);
            }
        }
        public class Family
        {
            public Family()
            {
                this.Members = new ObservableCollection<FamilyMember>();
            }
            public string Name { get; set; }
            public ObservableCollection<FamilyMember> Members { get; set; }
        }
    
        public class FamilyMember
        {
            public string Name { get; set; }
            public int Age { get; set; }
        }

    I've not defined that command. It's easy to find examples of relaycommand though.

    https://msdn.microsoft.com/en-gb/magazine/dn237302.aspx


    Hope that helps.

    Technet articles: WPF: MVVM Step 1; All my Technet Articles

    Saturday, November 28, 2015 6:03 PM
    Moderator
  • Thanks a lot, I will work through your response. 


    bill

    Saturday, November 28, 2015 7:59 PM