• WPF TreeView Ctrl/Shift多选


    一般情况下的TreeView控件貌似都不支持多选的功能,但是有时候却有这样的需求。怎么来对应呢?

    可能很多人会自定义一个类继承自TreeView,然后在重写某些方法,其实还有更好的办法可以做到,而且对现有的代码结构影响很小。

    当然这依赖于强大的WPF依赖属性,一起来看看吧

    第一步:新建一个类

    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Text;
    using System.Windows;
    using System.Windows.Controls;
    using System.Windows.Input;
    using System.ComponentModel;
    using System.Collections;
    using System.Windows.Media;
    
    namespace MeStudio.View
    {
        public class TreeViewExtensions : DependencyObject
        {
            /// <summary>
            /// Gets the value of the dependency property "EnableMultiSelect".
            /// </summary>
            /// <param name="obj">Dependency Object</param>
            /// <returns></returns>
            public static bool GetEnableMultiSelect(DependencyObject obj)
            {
                return (bool)obj.GetValue(EnableMultiSelectProperty);
            }
    
            /// <summary>
            /// Sets the value of the dependency property "EnableMultiSelect".
            /// </summary>
            /// <param name="obj">Dependency Object</param>
            /// <param name="value"></param>
            public static void SetEnableMultiSelect(DependencyObject obj, bool value)
            {
                obj.SetValue(EnableMultiSelectProperty, value);
            }
    
            // Using a DependencyProperty as the backing store for EnableMultiSelect.  This enables animation, styling, binding, etc...
            public static readonly DependencyProperty EnableMultiSelectProperty =
                DependencyProperty.RegisterAttached("EnableMultiSelect", typeof(bool), typeof(TreeViewExtensions), new FrameworkPropertyMetadata(false)
                {
                    PropertyChangedCallback = EnableMultiSelectChanged,
                    BindsTwoWayByDefault = true
                });
    
            /// <summary>
            /// Gets the value of the dependency property "SelectedItems".
            /// </summary>
            /// <param name="obj">Dependency Object</param>
            /// <returns></returns>
            public static IList GetSelectedItems(DependencyObject obj)
            {
                return (IList)obj.GetValue(SelectedItemsProperty);
            }
    
            /// <summary>
            /// Sets the value of the dependency property "SelectedItems".
            /// </summary>
            /// <param name="obj">Dependency Object</param>
            /// <param name="value"></param>
            public static void SetSelectedItems(DependencyObject obj, IList value)
            {
                obj.SetValue(SelectedItemsProperty, value);
            }
    
            // Using a DependencyProperty as the backing store for SelectedItems.  This enables animation, styling, binding, etc...
            public static readonly DependencyProperty SelectedItemsProperty =
                DependencyProperty.RegisterAttached("SelectedItems", typeof(IList), typeof(TreeViewExtensions), new PropertyMetadata(null));
    
            /// <summary>
            /// Gets the value of the dependency property "AnchorItem".
            /// </summary>
            /// <param name="obj">Dependency Object</param>
            /// <returns></returns>
            static TreeViewItem GetAnchorItem(DependencyObject obj)
            {
                return (TreeViewItem)obj.GetValue(AnchorItemProperty);
            }
    
            /// <summary>
            /// Sets the value of the dependency property "AnchorItem".
            /// </summary>
            /// <param name="obj">Dependency Object</param>
            /// <param name="value"></param>
            static void SetAnchorItem(DependencyObject obj, TreeViewItem value)
            {
                obj.SetValue(AnchorItemProperty, value);
            }
    
            // Using a DependencyProperty as the backing store for AnchorItem.  This enables animation, styling, binding, etc...
            static readonly DependencyProperty AnchorItemProperty =
                DependencyProperty.RegisterAttached("AnchorItem", typeof(TreeViewItem), typeof(TreeViewExtensions), new PropertyMetadata(null));
    
            /// <summary>
            /// Gets the value of the dependency property "IsSelected".
            /// </summary>
            /// <param name="obj">Dependency Object</param>
            /// <returns></returns>
            public static bool GetIsSelected(DependencyObject obj)
            {
                return (bool)obj.GetValue(IsSelectedProperty);
            }
    
            /// <summary>
            /// Sets the value of the dependency property "IsSelected".
            /// </summary>
            /// <param name="obj">Dependency Object</param>
            /// <param name="value"></param>
            public static void SetIsSelected(DependencyObject obj, bool value)
            {
                if (value)
                {
                    GradientStopCollection gradientStopCollection = new GradientStopCollection();
                    gradientStopCollection.Add(new GradientStop()
                    {
                        Color = (Color)ColorConverter.ConvertFromString("#FFC7DFFC"),
                        Offset = 1
                    });
                    gradientStopCollection.Add(new GradientStop()
                    {
                        Color = (Color)ColorConverter.ConvertFromString("#FF3832B8"),
                        Offset = 1
                    });
                    LinearGradientBrush brush = new LinearGradientBrush(gradientStopCollection, new Point(0.5, 0), new Point(0.5, 1));
                    (obj as TreeViewItem).Background = brush;
                }
                else
                {
                    (obj as TreeViewItem).Background = Brushes.Transparent;
                }
                obj.SetValue(IsSelectedProperty, value);
            }
    
            // Using a DependencyProperty as the backing store for IsSelected.  This enables animation, styling, binding, etc...
            public static readonly DependencyProperty IsSelectedProperty =
                DependencyProperty.RegisterAttached("IsSelected", typeof(bool), typeof(TreeViewExtensions), new PropertyMetadata(false)
                {
                    PropertyChangedCallback = RealSelectedChanged
                });
    
            /// <summary>
            /// "EnableMultiSelect" changed event.
            /// </summary>
            /// <param name="s">Dependency Object</param>
            /// <param name="args">Event parameter</param>
            static void EnableMultiSelectChanged(DependencyObject s, DependencyPropertyChangedEventArgs args)
            {
                TreeView tree = (TreeView)s;
                var wasEnable = (bool)args.OldValue;
                var isEnabled = (bool)args.NewValue;
                if (wasEnable)
                {
                    tree.RemoveHandler(TreeViewItem.MouseDownEvent, new MouseButtonEventHandler(ItemClicked));
                    tree.RemoveHandler(TreeView.KeyDownEvent, new KeyEventHandler(KeyDown));
                }
                if (isEnabled)
                {
                    tree.AddHandler(TreeViewItem.MouseDownEvent, new MouseButtonEventHandler(ItemClicked), true);
                    tree.AddHandler(TreeView.KeyDownEvent, new KeyEventHandler(KeyDown));
                }
            }
    
            /// <summary>
            /// Gets TreeView which contains the TreeViewItem.
            /// </summary>
            /// <param name="item">item</param>
            /// <returns>TreeView</returns>
            static TreeView GetTree(TreeViewItem item)
            {
                Func<DependencyObject, DependencyObject> getParent = (o) => VisualTreeHelper.GetParent(o);
                FrameworkElement currentItem = item;
                while (!(getParent(currentItem) is TreeView))
                {
                    currentItem = (FrameworkElement)getParent(currentItem);
                }
                return (TreeView)getParent(currentItem);
            }
    
            /// <summary>
            /// TreeViewItem seleted changed event.
            /// </summary>
            /// <param name="sender">sender</param>
            /// <param name="args">event parameter</param>
            static void RealSelectedChanged(DependencyObject sender, DependencyPropertyChangedEventArgs args)
            {
                TreeViewItem item = (TreeViewItem)sender;
                var selectedItems = GetSelectedItems(GetTree(item));
                if (selectedItems != null)
                {
                    var isSelected = GetIsSelected(item);
                    if (isSelected)
                    {
                        try
                        {
                            selectedItems.Add(item.Header);
                        }
                        catch (ArgumentException)
                        {
                        }
                    }
                    else
                    {
                        selectedItems.Remove(item.Header);
                    }
                }
            }
    
            /// <summary>
            /// Key down event.
            /// </summary>
            /// <param name="sender">sender</param>
            /// <param name="e">event parameter</param>
            static void KeyDown(object sender, KeyEventArgs e)
            {
                TreeView tree = (TreeView)sender;
                if (e.Key == Key.A && e.KeyboardDevice.Modifiers == ModifierKeys.Control)
                {
                    foreach (var item in GetExpandedTreeViewItems(tree))
                    {
                        SetIsSelected(item, true);
                    }
                    e.Handled = true;
                }
            }
    
            /// <summary>
            /// Item clicked event.
            /// </summary>
            /// <param name="sender">sender</param>
            /// <param name="e">event parameter</param>
            static void ItemClicked(object sender, MouseButtonEventArgs e)
            {
                TreeViewItem item = FindTreeViewItem(e.OriginalSource);
                if (item == null)
                {
                    return;
                }
                TreeView tree = (TreeView)sender;
    
                var mouseButton = e.ChangedButton;
                if (mouseButton != MouseButton.Left)
                {
                    if ((mouseButton == MouseButton.Right) && ((Keyboard.Modifiers & (ModifierKeys.Shift | ModifierKeys.Control)) == ModifierKeys.None))
                    {
                        if (GetIsSelected(item))
                        {
                            UpdateAnchorAndActionItem(tree, item);
                            return;
                        }
                        MakeSingleSelection(tree, item);
                    }
                    return;
                }
                if ((Keyboard.Modifiers & (ModifierKeys.Shift | ModifierKeys.Control)) != (ModifierKeys.Shift | ModifierKeys.Control))
                {
                    if ((Keyboard.Modifiers & ModifierKeys.Control) == ModifierKeys.Control)
                    {
                        MakeToggleSelection(tree, item);
                        return;
                    }
                    if ((Keyboard.Modifiers & ModifierKeys.Shift) == ModifierKeys.Shift)
                    {
                        MakeAnchorSelection(tree, item, true);
                        return;
                    }
                    MakeSingleSelection(tree, item);
                    return;
                }
            }
    
            /// <summary>
            /// Find TreeViewItem which contains the object.
            /// </summary>
            /// <param name="obj">obj</param>
            /// <returns></returns>
            private static TreeViewItem FindTreeViewItem(object obj)
            {
                DependencyObject dpObj = obj as DependencyObject;
                if (dpObj == null)
                {
                    return null;
                }
                if (dpObj is TreeViewItem)
                {
                    return (TreeViewItem)dpObj;
                }
                return FindTreeViewItem(VisualTreeHelper.GetParent(dpObj));
            }
    
            /// <summary>
            /// Gets all expanded TreeViewItems.
            /// </summary>
            /// <param name="tree">TreeView</param>
            /// <returns></returns>
            private static IEnumerable<TreeViewItem> GetExpandedTreeViewItems(ItemsControl tree)
            {
                for (int i = 0; i < tree.Items.Count; i++)
                {
                    var item = (TreeViewItem)tree.ItemContainerGenerator.ContainerFromIndex(i);
                    if (item == null)
                    {
                        continue;
                    }
                    yield return item;
                    if (item.IsExpanded)
                    {
                        foreach (var subItem in GetExpandedTreeViewItems(item))
                        {
                            yield return subItem;
                        }
                    }
                }
            }
    
            /// <summary>
            /// Select by Shift key.
            /// </summary>
            /// <param name="tree"></param>
            /// <param name="actionItem"></param>
            /// <param name="clearCurrent"></param>
            private static void MakeAnchorSelection(TreeView tree, TreeViewItem actionItem, bool clearCurrent)
            {
                if (GetAnchorItem(tree) == null)
                {
                    var selectedItems = GetSelectedTreeViewItems(tree);
                    if (selectedItems.Count > 0)
                    {
                        SetAnchorItem(tree, selectedItems[selectedItems.Count - 1]);
                    }
                    else
                    {
                        SetAnchorItem(tree, GetExpandedTreeViewItems(tree).Skip(3).FirstOrDefault());
                    }
                    if (GetAnchorItem(tree) == null)
                    {
                        return;
                    }
                }
    
                var anchor = GetAnchorItem(tree);
    
                var items = GetExpandedTreeViewItems(tree);
                bool betweenBoundary = false;
                foreach (var item in items)
                {
                    bool isBoundary = item == anchor || item == actionItem;
                    if (isBoundary)
                    {
                        betweenBoundary = !betweenBoundary;
                    }
                    if (betweenBoundary || isBoundary)
                    {
                        SetIsSelected(item, true);
                    }
                    else
                    {
                        if (clearCurrent)
                        {
                            SetIsSelected(item, false);
                        }
                        else
                        {
                            break;
                        }
                    }
                }
            }
    
            /// <summary>
            /// Gets all selected TreeViewItems.
            /// </summary>
            /// <param name="tree">TreeView</param>
            /// <returns></returns>
            private static List<TreeViewItem> GetSelectedTreeViewItems(TreeView tree)
            {
                return GetExpandedTreeViewItems(tree).Where(i => GetIsSelected(i)).ToList();
            }
    
            /// <summary>
            /// Select by left mouse button.
            /// </summary>
            /// <param name="tree"></param>
            /// <param name="item"></param>
            private static void MakeSingleSelection(TreeView tree, TreeViewItem item)
            {
                foreach (TreeViewItem selectedItem in GetExpandedTreeViewItems(tree))
                {
                    if (selectedItem == null)
                    {
                        continue;
                    }
                    if (selectedItem != item)
                    {
                        SetIsSelected(selectedItem, false);
                    }
                    else
                    {
                        SetIsSelected(selectedItem, true);
                    }
                }
                UpdateAnchorAndActionItem(tree, item);
            }
    
            /// <summary>
            /// Select by Ctrl key.
            /// </summary>
            /// <param name="tree">TreeView</param>
            /// <param name="item">TreeViewItem</param>
            private static void MakeToggleSelection(TreeView tree, TreeViewItem item)
            {
                SetIsSelected(item, !GetIsSelected(item));
                UpdateAnchorAndActionItem(tree, item);
            }
    
            /// <summary>
            /// Update the Anchor TreeViewItem.
            /// </summary>
            /// <param name="tree">TreeView</param>
            /// <param name="item">TreeViewItem</param>
            private static void UpdateAnchorAndActionItem(TreeView tree, TreeViewItem item)
            {
                SetAnchorItem(tree, item);
            }
        }
    }
    View Code

    第二步:在你使用TreeView的地方添加两个属性

    <TreeView x:Name="TreeView" ItemsSource="{Binding Children}" 
    my:TreeViewExtensions.EnableMultiSelect="true"
    my:TreeViewExtensions.SelectedItems="{Binding SelectedTreeNodes}"/>

    上面只贴出了关键属性,其他属性按需自己补全哈。

    第三步:给选中的节点添加背景

    在TreeView.ItemContainerStyle中添加触发器
    <Trigger Property="my:TreeViewExtensions.IsSelected" Value="true">

    <Setter Property="Background">
    <Setter.Value>
    <LinearGradientBrush EndPoint="0.5,1" MappingMode="RelativeToBoundingBox" StartPoint="0.5,0">
    <GradientStop Color="#FFC7DFFC" Offset="1"/>
    <GradientStop Color="#FF3832B8" Offset="1"/>
    </LinearGradientBrush>
    </Setter.Value>
    </Setter>
    </Trigger>

    到这里为止就可以实现Ctrl/Shift的多选了,有需要的朋友赶紧去试试吧。

  • 相关阅读:
    Java 的字符串,String、StringBuffer、StringBuilder 有什么区别?
    访问一个HTTPS的网站的大致流程
    Spring Boot Mybatis-Plus
    Spring Boot REST
    Spring Boot 以 war 方式部署
    MVC 框架
    HDFS,MongoDB,HBase的区别和使用场景
    什么时候该用NoSQL?
    最近和朋友微信卖螃蟹有点偏离重心了
    我要出去~
  • 原文地址:https://www.cnblogs.com/xiaoskery/p/3248019.html
Copyright © 2020-2023  润新知