9

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

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

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

现在的问题是我需要将 RelayCommand 绑定到 TreeViewItem 的命令,但是从我可以看到 TreeViewItem 没有命令。这是否迫使我在 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 基础设施的解决方案。

谢谢各位,非常感谢!

理查德

4

6 回答 6

28

我知道这是不久前“回答”的,但由于答案并不理想,我想我会投入两分钱。我使用了一种方法,让我不必求助于任何“样式化按钮技巧”,甚至不必使用代码隐藏,而是将我的所有分离都保留在 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"

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

在那之后,一切的行为就像任何其他命令一样,说一个按钮或其他东西。

于 2011-02-04T18:05:20.530 回答
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”方法。

如果这是完全错误的处理方式,请告诉我......

谢谢!

于 2010-02-15T19:31:24.833 回答
1

我要做的是将TreeViewItem的 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 中放置一个按钮并设置其样式。当然,您可以使用数据模板来做同样的事情。

于 2010-02-15T15:29:03.367 回答
1

这是一个很好的例子,说明了 MVVM 如何在 WPF 中成为事后的想法。您希望对某些 gui 项目有 Command 支持,但实际上没有,因此您不得不经历一个复杂的过程(如 Will 的示例所示),只是为了获得附加到某物的命令。

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

于 2010-02-15T18:06:54.730 回答
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;
      }
于 2018-11-16T15:51:21.160 回答
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>

在我看来,我有一个Tree集合

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>

然后在你的处理方法中,你不会收到模型对象,而是事件参数,它里面有一个模型对象。

带有选定元素的事件

于 2020-07-07T22:11:00.520 回答