WPF:绑定到ICommand的TreeViewItem

时间:2010-02-15 15:20:43

标签: c# wpf treeview command icommand

我正忙着在WPF中创建我的第一个MVVM应用程序。

基本上我遇到的问题是我有一个TreeView(System.Windows.Controls.TreeView),它放在我的WPF窗口上,我已经决定将绑定到CommandViewModel项的ReadOnlyCollection,以及这些项目由DisplayString,Tag和RelayCommand组成。

现在在XAML中,我有了TreeView,并且我已成功将ReadOnlyCollection绑定到此。我可以查看这个,在UI中看起来一切都很好。

现在的问题是我需要将RelayCommand绑定到TreeViewItem的Command,但是从我看到的TreeViewItem没有Command。这是否迫使我在IsSelected属性中甚至在TreeView_SelectedItemChanged方法后面的代码中执行此操作,还是有办法在WPF中神奇地执行此操作?

这是我的代码:

<TreeView BorderBrush="{x:Null}" 
      HorizontalAlignment="Stretch" 
      VerticalAlignment="Stretch">
<TreeView.Items>
    <TreeViewItem
        Header="New Commands"
        ItemsSource="{Binding Commands}"
        DisplayMemberPath="DisplayName"
        IsExpanded="True">
    </TreeViewItem>
</TreeView.Items>

理想情况下,我很想去:

<TreeView BorderBrush="{x:Null}" 
      HorizontalAlignment="Stretch" 
      VerticalAlignment="Stretch">
<TreeView.Items>
    <TreeViewItem
        Header="New Trade"
        ItemsSource="{Binding Commands}"
        DisplayMemberPath="DisplayName"
        IsExpanded="True"
        Command="{Binding Path=Command}">
    </TreeViewItem>
</TreeView.Items>

是否有人拥有允许我使用我所拥有的RelayCommand基础设施的解决方案。

谢谢你们,非常感谢!

理查德

6 个答案:

答案 0 :(得分:25)

我知道这是前一段时间的“回答”,但由于答案并不理想,我想我会投入两分钱。我使用一种方法,允许我不必诉诸任何“样式按钮技巧”,甚至使用代码隐藏,而是保持我在MVVM中的所有分离。在TreeView中添加以下xaml:

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

在你的xaml标题中添加:

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

然后你必须在你的项目中添加对上面程序集的引用。

之后,所有内容的行为与任何其他命令相同,例如按钮或其他内容。

答案 1 :(得分:2)

感谢您对该问题的投入,是的,我确实说过我不想要一个代码背后的解决方案,但当时我仍然非常认为我只是遗漏了一些东西...所以我最终使用TreeView_SelectedItemChanged事件。

尽管Will的方法似乎是一个很好的解决方案,但就个人情况而言,我决定使用背后的代码。这样做的原因是,如果TreeViewItem具有可以绑定我的Command的“Command”属性,则View和XAML将保持不变。现在我不必更改模板或视图,我所要做的就是为TreeView_SelectedItemChanged添加代码和事件。

我的解决方案:

  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});
                }
            }
        }
    }

由于我已经将RelayCommand附加到TreeViewItem,我现在所做的只是在该特定的RelayCommand上手动调用“Execute”方法。

如果这是完全错误的做法,请告诉我......

谢谢!

答案 2 :(得分:1)

我要做的是将TreeViewItem的the Header设置为按钮,然后为按钮设置外观,使其看起来不像一个按钮,然后对按钮执行命令绑定。

您可能需要通过DataTemplate执行此操作,或者您可能需要更改TreeViewItem本身的模板。从来没有做过,但这就是我做类似的事情(比如标签页标题)。


这是我正在谈论的一个例子(你可以把它放在Kaxaml中并玩弄它):

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

我为按钮创建了一个新的清晰样式。然后我在TVI中放下一个按钮并设置其样式。当然,你可以使用数据模板做同样的事情。

答案 3 :(得分:0)

这是一个很好的例子,说明MVVM在WPF中是如何经过深思熟虑的。你希望有某些gui项目的Command支持,但是没有,所以你被迫通过一个精心设计的过程(如Will的例子所示)只是为了得到一个附加到某个东西的命令。

让我们希望他们在WPF 2.0中解决这个问题: - )

答案 4 :(得分:0)

我通过通用的Tag属性从Richard改进了好的解决方案:

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;
      }

答案 5 :(得分:0)

Shaggy13spe提供的答案非常好。但是,我还是花了一些时间来理解它,所以我将扩展答案。

整个TreeView xaml看起来像这样:

<TreeView x:Name="treeView" Grid.Row="1" Grid.Column="0" ItemsSource="{Binding Tree}">
    <i:Interaction.Triggers>
        <i:EventTrigger EventName="SelectedItemChanged">
            <i:InvokeCommandAction Command="{Binding FilterMeetingsCommand}" CommandParameter="{Binding  ElementName=treeView, Path=SelectedItem}" />
        </i:EventTrigger>
    </i:Interaction.Triggers>
    <TreeView.ItemTemplate>
        <HierarchicalDataTemplate ItemsSource="{Binding Path=Nodes}">
            <StackPanel Orientation="Horizontal">
                <TextBlock Text="{Binding Name}"></TextBlock>
                <TextBlock Text="{Binding Id}"></TextBlock>
            </StackPanel>
        </HierarchicalDataTemplate>
    </TreeView.ItemTemplate>
</TreeView>

在我的视图中,我有一个集合

public ObservableCollection<TreeNode> Tree { get; set; }

TreeNode 被定义为一个简单的类:

public class TreeNode
{
    public int Id { get; set; }
    public string Name { get; set; }
    public List<TreeNode> Nodes { get; set; }

    public TreeNode(string name)
    {
        this.Name = name;
        this.Nodes = new List<TreeNode>();
    }
}

第一个要点: CommandParameter 未绑定到ViewModel的属性,但已传递给该方法。因此该方法应如下所示:

private async void FilterMeeting(object parameter){}

第二个要点:如果您传递选定的项目(在我的情况下,对象将为 TreeNode 类型),并且您将具有层次结构,则将遇到事件冒泡。因此,选择一个项目将为该特定项目和所有父项触发事件。要解决此问题,您需要了解,您只能将一个对象传递给 ViewModel 中的方法(在标准事件处理程序中不能传递两个对象),并且该对象必须是一个事件。

在这种情况下,将XAML更改为以下内容( PassEventArgsToCommand =“ True” 在这里很重要)

<TreeView x:Name="treeView" Grid.Row="1" Grid.Column="0" ItemsSource="{Binding Tree}">
    <i:Interaction.Triggers>
        <i:EventTrigger EventName="SelectedItemChanged">
            <i:InvokeCommandAction Command="{Binding FilterMeetingsCommand}" PassEventArgsToCommand="True"/>
        </i:EventTrigger>
    </i:Interaction.Triggers>
    <TreeView.ItemTemplate>
        <HierarchicalDataTemplate ItemsSource="{Binding Path=Nodes}">
            <StackPanel Orientation="Horizontal">
                <TextBlock Text="{Binding Name}"></TextBlock>
                <TextBlock Text="{Binding Id}"></TextBlock>
            </StackPanel>
        </HierarchicalDataTemplate>
    </TreeView.ItemTemplate>
</TreeView>

然后,在您的处理方法中,您将不会收到模型对象,但会收到事件args,其中包含模型对象。

Event with selected element