WPF: TreeViewItem bound to an ICommand

情到浓时终转凉″ 提交于 2019-11-30 09:12:34

I know this was "answered" a while ago, but since the answers weren't ideal, I figured I'd put in my two cents. I use a method that allows me to not have to resort to any "styled button trickery" or even using code-behind and instead keeps all my separation in MVVM. In your TreeView add the following xaml:

<i:Interaction.Triggers>
    <i:EventTrigger EventName="SelectedItemChanged">
        <i:InvokeCommandAction Command="{Binding TreeviewSelectedItemChanged}" CommandParameter="{Binding ElementName=treeView, Path=SelectedItem}"/>
    </i:EventTrigger>
</i:Interaction.Triggers>

In your xaml header add:

xmlns:i="clr-namespace:System.Windows.Interactivity;assembly=System.Windows.Interactivity"

and then you'll have to add a reference to the above assembly in your project.

After that, everything acts just the same as any other command would on say a button or something.

Thanks for the input into the issue, and yes, I did say I didn't want a Code behind solution, however at that time I was still very much under the impression that I was simply missing something... so I ended up using the TreeView_SelectedItemChanged event.

Even though Will's approach seems like a good work around, for my personal situation I decided that I would use the code behind. The reason for this is so that the View and XAML would remain as it would be if the TreeViewItem had a "Command" property to which my Command could be bound. Now I do not have to change the Templates or the Views, all I have to do is add the code and the Event for the TreeView_SelectedItemChanged.

My solution:

  private void TreeView_SelectedItemChanged(object sender, RoutedPropertyChangedEventArgs<object> e)
    {
        if (sender != null)
        {
            var treeView = sender as TreeView;
            if (treeView != null)
            {
                var commandViewModel = treeView.SelectedItem as CommandViewModel;
                if (commandViewModel != null)
                {
                    var mi = commandViewModel.Command.GetType().GetMethod("Execute");
                    mi.Invoke(commandViewModel.Command, new Object[] {null});
                }
            }
        }
    }

As I already have the RelayCommand attached to the TreeViewItem, all I am now doing is to just manually invoke the "Execute" method on that specific RelayCommand.

If this is the completely wrong way of going about it then please let me know...

Thanks!

What I'd do is set the Header of the TreeViewItem to be a button, then skin the button so that it doesn't look or act like one, then perform my command binding against the button.

You might need to do this via a DataTemplate, or you might need to change the template of the TreeViewItem itself. Never done it, but this is how I've done similar things (such as tab page headers).


Here's an example of what I'm talking about (you can drop this in Kaxaml and play around with it):

<Page xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" 
      xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
   <Page.Resources>
      <Style x:Key="ClearButan" TargetType="Button">
         <Setter Property="Template">
            <Setter.Value>
               <ControlTemplate TargetType="Button">              
                 <Border Name="border"
                     Padding="4"
                     Background="transparent">
                     <Grid >
                     <ContentPresenter HorizontalAlignment="Center"
                                    VerticalAlignment="Center">
                     </ContentPresenter>
                     </Grid>
                 </Border>
               </ControlTemplate>
            </Setter.Value>
         </Setter>
      </Style>
   </Page.Resources>
   <Grid>
      <TreeView>
         <TreeViewItem>
            <Button Style="{StaticResource ClearButan}">
            easy peasy
            </Button>
         </TreeViewItem>
      </TreeView>
   </Grid>
</Page>

I've created a new clear style for a button. I then just drop a button in the TVI and set its style. You can do the same thing using data templates, of course.

This is a good example of how the MVVM is very much an after-thought in WPF. You expect there to be Command support of certain gui items, but there isn't, so you're forced to go through an elaborate process (as shown in Will's example) just to get a command attached to something.

Let's hope they address this in WPF 2.0 :-)

I improve good solution from Richard via common Tag property:

MyView.xaml:

 <TreeView SelectedItemChanged="TreeView_SelectedItemChanged" Tag="{Binding SelectTreeViewCommand}" >
                  <TreeViewItem Header="Item1" IsExpanded="True"   Tag="Item1"  />
                  <TreeViewItem Header="Item2" IsExpanded="True">
                      <TreeViewItem Header="Item21"  Tag="Item21"/>
                  </TreeViewItem>
              </TreeView>

MyView.xaml.cs

    private void TreeView_SelectedItemChanged(object sender, RoutedPropertyChangedEventArgs<object> e)
    {
        var treeView = (TreeView)sender;
        var command = (ICommand)treeView.Tag;
        TreeViewItem selectedItem = (TreeViewItem)treeView.SelectedItem;
        if (selectedItem.Tag != null)
        {
            command.Execute(selectedItem.Tag);
        }
    }

MyViewModel.cs

      public RelayCommand selectTreeViewCommand;
      [Bindable(true)]
      public RelayCommand SelectTreeViewCommand => selectTreeViewCommand ?? (selectTreeViewCommand = new RelayCommand(CanSelectTreeViewCommand, ExecuteSelectTreeViewCommand));

      private void ExecuteSelectTreeViewCommand(object obj)
      {
          Console.WriteLine(obj);
      }

      private bool CanSelectTreeViewCommand(object obj)
      {
          return true;
      }
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!