【问题标题】:How to deselect all selected items in a WPF treeview when clicking on some empty area?单击某个空白区域时如何取消选择WPF树视图中的所有选定项目?
【发布时间】:2009-01-29 10:12:53
【问题描述】:

我对 WPF 有一个相当有趣的问题。我有一个树视图,到目前为止选择项目工作正常。问题是,当用户在树视图的空白区域内单击时,我想取消选择当前选定的项目。默认情况下,树视图保持当前项目处于选中状态,并且我添加了一个上下文菜单选项来取消选择它,这是相当硬核的:

// Note: This is done recursivly from the start, so it
// works for child items as well
treeView.ItemContainerGenerator.ContainerFromItem(treeView.SelectedItem) as TreeViewItem).IsSelected = false;

而且,这是违反直觉的,因为它需要用户首先右键单击,其次,使用这种方式取消选择后,用户无法通过单击该项目再选择它。这应该如何工作?

编辑:更多信息:我已向TreeView 添加了一个处理程序来处理鼠标单击事件,但发送者始终是TreeView 实例,即使我直接单击TreeViewItem。如果我将处理程序添加到我的TreeView.ItemTemplate(即模板中的第一个孩子),当我单击空白区域时,我永远不会收到事件(这是相当合乎逻辑的)。代码如下所示:

    private void MyTreeView_MouseUp(object sender, System.Windows.Input.MouseButtonEventArgs e)
    {
        if ((sender as TreeViewItem) == null)
        {
            // Always triggered
            System.Diagnostics.Trace.Write("Empty area clicked");
        }
    } 

为此的 XAML 是:

<TreeView x:Name="MyTreeView" Margin="3" MouseUp="MyTreeView_MouseUp">

【问题讨论】:

    标签: wpf


    【解决方案1】:

    我发现这对我来说效果更好。我检查了原始来源,如果它来自树视图项,对我来说将是图像或文本块。我还使用带有 HierarchicalDataTemplate 的视图对象,BasicTreeViewBase 是我所有不同对象的基类。这是代码。

    private void TemplateTreeView_MouseDown(object sender, MouseButtonEventArgs e)
        {
            if (e.ChangedButton == MouseButton.Right && !(e.OriginalSource is Image) && !(e.OriginalSource is TextBlock))
            {
                BasicTreeViewBase item = TemplateTreeView.SelectedItem as BasicTreeViewBase;
                if (item != null)
                {
                    TemplateTreeView.Focus();
                    item.IsSelected = false;
                }
            }
        }
    

    【讨论】:

    • 为什么需要TemplateTreeView.Focus();
    • @DavidRefaeli 那是很久以前的事了,我不记得我为什么拥有它。我确实知道,当我写它的时候,没有它是行不通的。
    • 这是一个很好的解决方案,我认为您在这里使用自己的类时我必须进行一些调整(TemplateTreeView,BasicTreeViewBase) - 但对我来说,我不需要更改 .Focus( ) 以取消选择该项目。
    • 如果您绑定到 ItemsSource,因此“SelectedItem”不是 TreeViewItem,而是 ItemSource 包含的任何对象...在您的 TreeView 中,添加 TreeViewItem.Selected="event_name" 和然后跟踪选择了哪个 TreeViewItem,以便稍后您可以在 TreeViewItem 上明确设置 IsSelected=false。
    【解决方案2】:

    设置 TreeViewItem.IsSelected 后调用 Focus on the TreeView 可以解决不可选择的问题。

    【讨论】:

    • 很好,很好的收获!我想知道为什么一旦取消选择我就无法选择同一个节点。谢谢!
    【解决方案3】:

    还有两个问题:

    1. 树视图已绑定,因此 SelectedItem 是绑定集合的一项。
    2. 层级很多,所以 ItemContainerGenerator 不包含最深层级的对象

    出于所有这些原因,我使用此功能,但选择不得触发任何事件。

    private void UnselectTreeViewItem(TreeView pTreeView)
    { 
      if(pTreeView.SelectedItem == null)
        return;
    
      if(pTreeView.SelectedItem is TreeViewItem)
      {
        (pTreeView.SelectedItem as TreeViewItem).IsSelected = false;
      }
      else
      {
        TreeViewItem item = pTreeView.ItemContainerGenerator.ContainerFromIndex(0) as TreeViewItem;
        if (item != null)
        {
          item.IsSelected = true;
          item.IsSelected = false;
        }
      }
    }
    

    【讨论】:

      【解决方案4】:

      我实现了一次通用选择控件,并且需要这种行为。

      这就是我的方法的外观(适用于树视图):

      protected override void OnMouseUp(MouseButtonEventArgs e)
      {
          base.OnMouseUp(e);
      
          DependencyObject dpSource = e.OriginalSource as DependencyObject;
      
          if (dpSource.FindVisualAncestor(o => typeof(TreeViewItem).IsAssignableFrom(o.GetType())) == null)
                  UnselectAll();
      }
      

      基本上,从源头向上走。如果未找到 TreeViewItem,则表示用户单击了空白区域。

      【讨论】:

        【解决方案5】:

        使用下面的扩展类

        public static class TreeViewExtensions
        {
            public static TreeViewItem ContainerFromItem(this TreeView treeView, object item)
            {
                TreeViewItem containerThatMightContainItem = (TreeViewItem)treeView.ItemContainerGenerator.ContainerFromItem(item);
                if (containerThatMightContainItem != null)
                    return containerThatMightContainItem;
                else
                    return ContainerFromItem(treeView.ItemContainerGenerator, treeView.Items, item);
            }
        
            private static TreeViewItem ContainerFromItem(ItemContainerGenerator parentItemContainerGenerator, ItemCollection itemCollection, object item)
            {
                foreach (object curChildItem in itemCollection)
                {
                    TreeViewItem parentContainer = (TreeViewItem)parentItemContainerGenerator.ContainerFromItem(curChildItem);
                    TreeViewItem containerThatMightContainItem = (TreeViewItem)parentContainer.ItemContainerGenerator.ContainerFromItem(item);
                    if (containerThatMightContainItem != null)
                        return containerThatMightContainItem;
        
                    TreeViewItem recursionResult = ContainerFromItem(parentContainer.ItemContainerGenerator, parentContainer.Items, item);
                    if (recursionResult != null)
                        return recursionResult;
                }
                return null;
            }
        }
        

        然后在treeview的MouseDown事件中使用如下扩展方法:

            private void trview_MouseDown(object sender, System.Windows.Input.MouseButtonEventArgs e)
            {
                if ((sender as TreeViewItem) == null)
                {
                    if (this.trview.ContainerFromItem(trview.SelectedItem) != null)
                    {
                        this.trview.ContainerFromItem(trview.SelectedItem).IsSelected = false;
                    }
                }
                this.trview.Focus();
            }
        

        希望它对你有用。我有它以这种方式工作......

        【讨论】:

          【解决方案6】:

          我自己在使用自定义树列表视图实现时遇到了这种情况,在寻找了很长时间后,我终于找到了一个适合我的解决方案。

          完整的解释可以在http://social.msdn.microsoft.com/Forums/vstudio/en-US/36aca7f7-0b47-488b-8e16-840b86addfa3/getting-treeviewitem-for-the-selected-item-in-a-treeview找到

          基本思想是捕获TreeViewItem.Selected 事件并将事件源保存到TreeView 上的Tag 属性中。然后当您需要清除它时,您可以访问控件上的Tag 属性并将IsSelected 值设置为False。这适用于我有 2 级嵌套子级。希望它对你有用。

          为了坚持:

          TreeView 声明

            <TreeView Name="myTreeView" TreeViewItem.Selected="OnItemSelected"
              ItemsSource="{Binding Source={StaticResource myHierarchicalData}}"/>
          

          事件处理程序

          private void OnItemSelected(object sender, RoutedEventArgs e)
          {
              myTreeView.Tag = e.OriginalSource;
          }
          

          清除选择逻辑

          if (myTreeView.SelectedItem != null)
          {
              TreeViewItem selectedTVI = myTreeView.Tag as TreeViewItem;
              // add your code here mine was selectedTVI.IsSelected = false;
          }
          

          【讨论】:

            【解决方案7】:

            如果没有点击,这将取消选择当前选择的 TreeViewItem:

            private void MyTreeView_PreviewMouseDown(object sender, MouseButtonEventArgs e) {
                if ((sender as TreeViewItem) == null) {
                    TreeViewItem item = MyTreeView.SelectedItem as TreeViewItem;
                    if(item != null){
                        item.IsSelected = false;                    
                    }
                }
            }
            

            希望这就是你要找的东西!

            【讨论】:

            • 不行,因为选中一个item后,sender绑定到当前选中的item——也就是说,即使我点击进入空白区域,sender ==当前选中的item。
            • 嗯。如果单击 TreeView 本身,则发送者应该是 TreeView(而不是 TreeViewItem)。您是否将此处理程序添加到 TreeView 或每个 TreeViewItem?您使用的是标准的 TreeView 还是自定义的?
            • 啊问题是我没有获得 TreeViewItem,而是我的 viewmodel 类的一个实例,我必须为此找到相应的 TreeViewItem。更糟糕的是,在以编程方式取消选择项目后,我无法通过单击重新选择它。
            • 更多问题,扩展一个项目会用这种方法取消选择它 - 我无法让它为子项目工作,因为 ItemContainerGenerator.ContainerFromItem 为此返回 null?
            • 您能否编辑您的原始帖子以包含更多代码,以便我更好地了解发生了什么?谢谢!
            【解决方案8】:

            MVVM:在事件处理程序中调用此方法鼠标右键/左键单击:

            private void SetTreeViewSelection(object sender, MouseButtonEventArgs mouseButtonEventArgs)
            {
              var treeView = (TreeView)sender;
              if (treeView.SelectedItem == null)
              {
                return;
              }
              
              IInputElement dropNode = treeView.InputHitTest(mouseButtonEventArgs.GetPosition(treeView));
              if (dropNode is ScrollViewer)
              {
                var myBindableObject = (MyBindableObject)treeView.SelectedItem;
                myBindableObject.IsSelected = false;
              }
            }
            

            【讨论】:

              【解决方案9】:

              对于 C# 树视图,您使用 treeview.SelectedNode = null;我不确定这是否适用于 WPF。

              【讨论】:

                猜你喜欢
                • 1970-01-01
                • 1970-01-01
                • 2012-01-20
                • 2013-08-10
                • 1970-01-01
                • 1970-01-01
                • 2011-04-26
                • 1970-01-01
                • 1970-01-01
                相关资源
                最近更新 更多