Customizing the TreeView to allow multi select

前端 未结 4 1852
迷失自我
迷失自我 2020-12-02 15:24

The built-in WPF TreeView control does not allow for multi selection, like a ListBox does. How can I customize the TreeView to allow for multi selection without rewriting i

4条回答
  •  慢半拍i
    慢半拍i (楼主)
    2020-12-02 16:13

    I finally ended coding my own CustomControl containing a TreeView inside. Based on the work of others the key of the functionality resides on making all the items of the Model of the TreeView inherit the interface ISelectable:

    public interface ISelectable
    {
        public bool IsSelected {get; set}
    }
    

    This way we will have a new 'IsSelected' property that has nothing to do with the TreeViewItem IsSelected. We just need to style our tree so it handles the model IsSelected property. Here the code (it's using the Drag & drop libraries available at http://code.google.com/p/gong-wpf-dragdrop/):

    XAML

    
    
    
        
            
        
    
    

    C#:

    using System.Collections.Generic;
    using System.Linq;
    using System.Windows;
    using System.Windows.Controls;
    using System.Windows.Input;
    using System.Windows.Media;
    using System.Windows.Shapes;
    using GongSolutions.Wpf.DragDrop;
    using DragDrop = GongSolutions.Wpf.DragDrop;
    
    namespace .TreeViewEx
    {
    public partial class TreeViewEx : UserControl
    {
        #region Attributes
    
        private TreeViewItem _lastItemSelected; // Used in shift selections
        private TreeViewItem _itemToCheck; // Used when clicking on a selected item to check if we want to deselect it or to drag the current selection
        private bool _isDragEnabled;
        private bool _isDropEnabled;
    
        #endregion
    
        #region Dependency Properties
    
        public static readonly DependencyProperty ItemsSourceProperty = DependencyProperty.Register("ItemsSource", typeof(IEnumerable), typeof(TreeViewEx));
    
        public IEnumerable ItemsSource
        {
            get
            {
                return (IEnumerable)this.GetValue(TreeViewEx.ItemsSourceProperty);
            }
            set
            {
                this.SetValue(TreeViewEx.ItemsSourceProperty, value);
            }
        }
    
        public static readonly DependencyProperty ItemTemplateProperty = DependencyProperty.Register("ItemTemplate", typeof(DataTemplate), typeof(TreeViewEx));
    
        public DataTemplate ItemTemplate
        {
            get
            {
                return (DataTemplate)GetValue(TreeViewEx.ItemTemplateProperty);
            }
            set
            {
                SetValue(TreeViewEx.ItemTemplateProperty, value);
            }
        }
    
        public static readonly DependencyProperty ItemContainerStyleProperty = DependencyProperty.Register("ItemContainerStyle", typeof(Style), typeof(TreeViewEx));
    
        public Style ItemContainerStyle
        {
            get
            {
                return (Style)GetValue(TreeViewEx.ItemContainerStyleProperty);
            }
            set
            {
                SetValue(TreeViewEx.ItemContainerStyleProperty, value);
            }
        }
    
        public static readonly DependencyProperty DropHandlerProperty = DependencyProperty.Register("DropHandler", typeof(IDropTarget), typeof(TreeViewEx));
    
        public IDropTarget DropHandler
        {
            get
            {
                return (IDropTarget)GetValue(TreeViewEx.DropHandlerProperty);
            }
            set
            {
                SetValue(TreeViewEx.DropHandlerProperty, value);
            }
        }
    
        #endregion
    
        #region Properties
    
        public bool IsDragEnabled
        {
            get
            {
                return _isDragEnabled;
            }
            set
            {
                if (_isDragEnabled != value)
                {
                    _isDragEnabled = value;
                    DragDrop.SetIsDragSource(this.InnerTreeView, _isDragEnabled);
                }
            }
        }
    
        public bool IsDropEnabled
        {
            get
            {
                return _isDropEnabled;
            }
            set
            {
                if (_isDropEnabled != value)
                {
                    _isDropEnabled = value;
                    DragDrop.SetIsDropTarget(this.InnerTreeView, _isDropEnabled);
                }
            }
        }
    
        #endregion
    
        #region Public Methods
    
        public TreeViewEx()
        {
            InitializeComponent();
        }
    
        #endregion
    
        #region Event Handlers
    
        private void TreeViewOnPreviewMouseDown(object sender, MouseButtonEventArgs e)
        {
            if (e.OriginalSource is Shape || e.OriginalSource is Grid || e.OriginalSource is Border) // If clicking on the + of the tree
                return;
    
            TreeViewItem item = this.GetTreeViewItemClicked((FrameworkElement)e.OriginalSource);
    
            if (item != null && item.Header != null)
            {
                this.SelectedItemChangedHandler(item);
            }
        }
    
        // Check done to avoid deselecting everything when clicking to drag
        private void TreeViewOnPreviewMouseUp(object sender, MouseButtonEventArgs e)
        {
            if (_itemToCheck != null)
            {
                TreeViewItem item = this.GetTreeViewItemClicked((FrameworkElement)e.OriginalSource);
    
                if (item != null && item.Header != null)
                {
                    if (!TreeViewEx.IsCtrlPressed)
                    {
                        GetTreeViewItems(true).Select(t => t.Header).Cast().ToList().ForEach(f => f.IsSelected = false);
                        ((ISelectable)_itemToCheck.Header).IsSelected = true;
                        _lastItemSelected = _itemToCheck;
                    }
                    else
                    {
                        ((ISelectable)_itemToCheck.Header).IsSelected = false;
                        _lastItemSelected = null;
                    }
                }
            }
        }
    
        #endregion
    
        #region Private Methods
    
        private void SelectedItemChangedHandler(TreeViewItem item)
        {
            ISelectable content = (ISelectable)item.Header;
    
            _itemToCheck = null;
    
            if (content.IsSelected)
            {
                // Check it at the mouse up event to avoid deselecting everything when clicking to drag
                _itemToCheck = item;
            }
            else
            {
                if (!TreeViewEx.IsCtrlPressed)
                {
                    GetTreeViewItems(true).Select(t => t.Header).Cast().ToList().ForEach(f => f.IsSelected = false);
                }
    
                if (TreeViewEx.IsShiftPressed && _lastItemSelected != null)
                {
                    foreach (TreeViewItem tempItem in GetTreeViewItemsBetween(_lastItemSelected, item))
                    {
                        ((ISelectable)tempItem.Header).IsSelected = true;
                        _lastItemSelected = tempItem;
                    }
                }
                else
                {
                    content.IsSelected = true;
                    _lastItemSelected = item;
                }
            }
        }
    
        private static bool IsCtrlPressed
        {
            get
            {
                return Keyboard.IsKeyDown(Key.LeftCtrl) || Keyboard.IsKeyDown(Key.RightCtrl);
            }
        }
    
        private static bool IsShiftPressed
        {
            get
            {
                return Keyboard.IsKeyDown(Key.LeftShift) || Keyboard.IsKeyDown(Key.RightShift);
            }
        }
    
        private TreeViewItem GetTreeViewItemClicked(UIElement sender)
        {
            Point point = sender.TranslatePoint(new Point(0, 0), this.InnerTreeView);
            DependencyObject visualItem = this.InnerTreeView.InputHitTest(point) as DependencyObject;
            while (visualItem != null && !(visualItem is TreeViewItem))
            {
                visualItem = VisualTreeHelper.GetParent(visualItem);
            }
    
            return visualItem as TreeViewItem;
        }
    
        private IEnumerable GetTreeViewItemsBetween(TreeViewItem start, TreeViewItem end)
        {
            List items = this.GetTreeViewItems(false);
    
            int startIndex = items.IndexOf(start);
            int endIndex = items.IndexOf(end);
    
            // It's possible that the start element has been removed after it was selected,
            // I don't find a way to happen on the end but I add the code to handle the situation just in case
            if (startIndex == -1 && endIndex == -1)
            {
                return new List();
            }
            else if (startIndex == -1)
            {
                return new List() {end};
            }
            else if (endIndex == -1)
            {
                return new List() { start };
            }
            else
            {
                return startIndex > endIndex ? items.GetRange(endIndex, startIndex - endIndex + 1) : items.GetRange(startIndex, endIndex - startIndex + 1); 
            }
        }
    
        private List GetTreeViewItems(bool includeCollapsedItems)
        {
            List returnItems = new List();
    
            for (int index = 0; index < this.InnerTreeView.Items.Count; index++)
            {
                TreeViewItem item = (TreeViewItem)this.InnerTreeView.ItemContainerGenerator.ContainerFromIndex(index);
                returnItems.Add(item);
                if (includeCollapsedItems || item.IsExpanded)
                {
                    returnItems.AddRange(GetTreeViewItemItems(item, includeCollapsedItems));                    
                }
            }
    
            return returnItems;
        }
    
        private static IEnumerable GetTreeViewItemItems(TreeViewItem treeViewItem, bool includeCollapsedItems)
        {
            List returnItems = new List();
    
            for (int index = 0; index < treeViewItem.Items.Count; index++)
            {
                TreeViewItem item = (TreeViewItem)treeViewItem.ItemContainerGenerator.ContainerFromIndex(index);
                if (item != null)
                {
                    returnItems.Add(item);
                    if (includeCollapsedItems || item.IsExpanded)
                    {
                        returnItems.AddRange(GetTreeViewItemItems(item, includeCollapsedItems));
                    }
                }
            }
    
            return returnItems;
        }
    
        #endregion
    }
    }
    

提交回复
热议问题