在 TreeView 中突出显示自定义 DataTemplate

Highlight custom DataTemplate in TreeView

我有一个简单的 TreeView,我正在尝试为其创建自定义 DataTemplate。它按需要显示,但是当我尝试 select TreeViewItem 时,如果我单击 TreeView 中的文本,突出显示将不起作用。但是,如果我 select 就在文本的左侧,它会起作用:

来源非常简单,所以我猜我只是缺少样式连接:

xaml

   <TreeView x:Name="treeView"
             ItemsSource="{Binding TreeViewItems}"
             Grid.Row="0">
      <TreeView.ItemTemplate>
         <HierarchicalDataTemplate ItemsSource="{Binding Path=MenuItems}">
            <TreeViewItem Header="{Binding Header}">
               <TreeViewItem.InputBindings>
                  <MouseBinding MouseAction="LeftClick"
                                Command="{Binding Command}" />
               </TreeViewItem.InputBindings>
            </TreeViewItem>
         </HierarchicalDataTemplate>
      </TreeView.ItemTemplate>
   </TreeView>

我试过将此添加到 xaml,但没有帮助:

      <TreeView.ItemContainerStyle>
         <Style TargetType="{x:Type TreeViewItem}" 
                BasedOn="{StaticResource {x:Type TreeViewItem}}" />
      </TreeView.ItemContainerStyle>

树视图模型

   public class TreeViewModel : BaseNotifyModel, ITreeViewModel
   {
      public TreeViewModel(ITreeViewService menuService)
      {
         TreeViewItems = 
            new ReadOnlyObservableCollection<MenuItemViewModel>(menuService.TreeViewMenu);
      }

      public ReadOnlyObservableCollection<MenuItemViewModel> TreeViewItems
      {
         get
         {
            return Get<ReadOnlyObservableCollection<MenuItemViewModel>>();
         }
         private set
         {
            Set(value);
         }
      }
   }

MenuItemViewModel

  public class MenuItemViewModel : BaseNotifyModel
   {
      public MenuItemViewModel()
      {
         MenuItems = 
            new ObservableCollection<MenuItemViewModel>();
      }

      public String Header
      {
         get
         {
            return Get<String>();
         }
         set
         {
            Set(value);
         }
      }

      public ICommand Command
      {
         get
         {
            return Get<ICommand>();
         }
         set
         {
            Set(value);
         }
      }

      public ObservableCollection<MenuItemViewModel> MenuItems
      {
         get
         {
            return Get<ObservableCollection<MenuItemViewModel>>();
         }
         set
         {
            Set(value);
         }
      }
   }

TreeView 正在为 ItemsSource 中的每个项目创建一个 TreeViewItem,所以不要在 TreeView 的项目中嵌套另一个 TreeViewItem已经为您创建。那没有任何作用。您的模板应该只是为现有的 TreeViewItem 提供一种方式来显示其 DataContext 中的任何内容(在本例中为您的 MenuItemViewModel)。

您想在树视图项目中显示Header 属性;所以就这样做吧。没什么特别的,只是 LabelContentControl,如果是 String,甚至是 TextBlock(尽管在 WPF 中具有任意内容的灵活性很有趣)。当用户单击内容时,您的命令就会执行。用户在树中唯一能看到的就是内容。这是 TreeViewItem 项唯一可见的部分,因此用户将要单击该部分。

第二个问题:一旦输入绑定得到 LeftClick,就会中断 TreeView 中的选择。在我看来,你无法通过那种方法从这里到达那里。

  <TreeView.ItemTemplate>
     <HierarchicalDataTemplate ItemsSource="{Binding Path=MenuItems}">
        <ContentControl 
            Content="{Binding Header}"
            Background="Transparent"
            >
           <ContentControl.InputBindings>
              <!-- This invokes the command, but breaks selection -->
              <MouseBinding MouseAction="LeftClick"
                            Command="{Binding Command}" />
           </ContentControl.InputBindings>
        </ContentControl>
     </HierarchicalDataTemplate>
  </TreeView.ItemTemplate>

您可以执行以下操作:

<TreeView.ItemTemplate>
    <HierarchicalDataTemplate ItemsSource="{Binding Path=MenuItems}">
        <ContentControl 
            Content="{Binding Header}"
            Background="Transparent"
            >
        </ContentControl>
    </HierarchicalDataTemplate>
</TreeView.ItemTemplate>
<TreeView.ItemContainerStyle>
    <Style TargetType="TreeViewItem" BasedOn="{StaticResource {x:Type TreeViewItem}}">
        <EventSetter Event="Selected" Handler="MenuTreeViewItem_Click" />
    </Style>
</TreeView.ItemContainerStyle>

代码隐藏

private void MenuTreeViewItem_Click(object sender, RoutedEventArgs e)
{
    ((MenuItemViewModel)((FrameworkElement)sender).DataContext).Command.Execute(null);
}

There is a way to bind an event to a command in pure XAML,但它需要一些 C# 代码(大声笑)。但我的意思是,它是 "pure XAML",因为它是一个很好的通用可重用附加行为,而不是代码隐藏中难看的事件处理程序。相反,它与我上面所做的完全相同,但它是在代码中完成的,您可以更轻松地转移视线,并且可以在纯 XAML 中重复使用。

在@Ed Plunket 和@Evk 的帮助下,我找到了一个可行的解决方案。我切换到 ContentPresenter 并使用 Interaction.TriggersMouseLeftButtonUp 操作上调用命令。

   <TreeView ItemsSource="{Binding TreeViewItems}">
      <TreeView.ItemTemplate>
         <HierarchicalDataTemplate ItemsSource="{Binding Path=MenuItems}">
            <ContentPresenter Content="{Binding Header}">
               <i:Interaction.Triggers>
                  <i:EventTrigger EventName="MouseLeftButtonUp">
                     <i:InvokeCommandAction Command="{Binding Path=DataContext.Command, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type TreeViewItem}}}" />
                  </i:EventTrigger>
               </i:Interaction.Triggers>
            </ContentPresenter>
         </HierarchicalDataTemplate>
      </TreeView.ItemTemplate>
   </TreeView>