Wpf实现TreeSelect多选

AIStudio框架汇总及介绍

实现思路:

1.定义一个树形控件对应的基类,有IsChecked属性。

2.XAML里面绑定上面那个类对象。CheckBox绑定IsChecked属性。

3.处理数据设置与文本显示,用逗号分开。

 

 XAML代码:同Wpf实现TreeSelect

CS代码:和单选代码是在一块的,为了实现多选进行了改造。

using System;
using System.Collections;
using System.Collections.Generic;
using System.Collections.Specialized;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Input;
using System.Windows.Media;

namespace Util.Controls
{
    /// <summary>
    /// TreeSelect.xaml 的交互逻辑
    /// </summary>
    [TemplatePart(Name = "PART_TreeView", Type = typeof(TreeView))]
    public partial class TreeSelect : ComboBox
    {
        public bool IsMulti
        {
            get { return (bool)GetValue(IsMultiProperty); }
            set { SetValue(IsMultiProperty, value); }
        }

        public static readonly DependencyProperty IsMultiProperty =
            DependencyProperty.Register("IsMulti", typeof(bool), typeof(TreeSelect), new PropertyMetadata(false));

        public IList SelectedItems
        {
            get { return (IList)GetValue(SelectedItemsProperty); }
            set { SetValue(SelectedItemsProperty, value); }
        }

        public static readonly DependencyProperty SelectedItemsProperty =
            DependencyProperty.Register("SelectedItems", typeof(IList), typeof(TreeSelect), new PropertyMetadata(OnSelectedItemsChanged));

        private static void OnSelectedItemsChanged(DependencyObject sender, DependencyPropertyChangedEventArgs e)
        {
            ((TreeSelect)sender).UpdateSelectedItems(e.NewValue as IList, e.OldValue as IList);
        }

        public new string DisplayMemberPath
        {
            get { return (string)GetValue(DisplayMemberPathProperty); }
            set { SetValue(DisplayMemberPathProperty, value); }
        }

        public new static readonly DependencyProperty DisplayMemberPathProperty =
            DependencyProperty.Register("DisplayMemberPath", typeof(string), typeof(TreeSelect));

        public new string SelectedValuePath
        {
            get { return (string)GetValue(SelectedValuePathProperty); }
            set { SetValue(SelectedValuePathProperty, value); }
        }

        public new static readonly DependencyProperty SelectedValuePathProperty =
            DependencyProperty.Register("SelectedValuePath", typeof(string), typeof(TreeSelect));

        /// <summary>
        /// Selected item of the TreeView
        /// </summary>
        public new object SelectedItem
        {
            get { return (object)GetValue(SelectedItemProperty); }
            set { SetValue(SelectedItemProperty, value); }
        }

        public new static readonly DependencyProperty SelectedItemProperty =
            DependencyProperty.Register("SelectedItem", typeof(object), typeof(TreeSelect), new PropertyMetadata(null, new PropertyChangedCallback(OnSelectedItemChanged)));

        private static void OnSelectedItemChanged(DependencyObject sender, DependencyPropertyChangedEventArgs e)
        {
            ((TreeSelect)sender).UpdateSelectedItem();
        }

        public new object SelectedValue
        {
            get { return (object)GetValue(SelectedValueProperty); }
            set { SetValue(SelectedValueProperty, value); }
        }

        public new static readonly DependencyProperty SelectedValueProperty =
            DependencyProperty.Register("SelectedValue", typeof(object), typeof(TreeSelect), new PropertyMetadata(null));


        public new string Text
        {
            get { return (string)GetValue(TextProperty); }
            set { SetValue(TextProperty, value); }
        }

        public static readonly DependencyProperty TextProperty =
            DependencyProperty.Register("Text", typeof(string), typeof(TreeSelect));

        /// <summary>
        /// Gets or sets text separator.
        /// </summary>
        public string TextSeparator
        {
            get { return (string)GetValue(TextSeparatorProperty); }
            set { SetValue(TextSeparatorProperty, value); }
        }

        public static readonly DependencyProperty TextSeparatorProperty =
            DependencyProperty.Register("TextSeparator", typeof(string), typeof(TreeSelect), new PropertyMetadata(","));

        /// <summary>
        /// Gets or sets max text length.
        /// </summary>
        public int? MaxTextLength
        {
            get { return (int?)GetValue(MaxTextLengthProperty); }
            set { SetValue(MaxTextLengthProperty, value); }
        }

        public static readonly DependencyProperty MaxTextLengthProperty =
            DependencyProperty.Register("MaxTextLength", typeof(int?), typeof(TreeSelect));

        /// <summary>
        /// Gets or sets text filler when text length exceeded.
        /// </summary>
        public string ExceededTextFiller
        {
            get { return (string)GetValue(ExceededTextFillerProperty); }
            set { SetValue(ExceededTextFillerProperty, value); }
        }

        public static readonly DependencyProperty ExceededTextFillerProperty =
            DependencyProperty.Register("ExceededTextFiller", typeof(string), typeof(TreeSelect), new PropertyMetadata("..."));



        static TreeSelect()
        {
            DefaultStyleKeyProperty.OverrideMetadata(typeof(TreeSelect), new FrameworkPropertyMetadata(typeof(TreeSelect)));
        }

        public TreeSelect()
        {
            Loaded -= TreeSelect_Loaded;
            Loaded += TreeSelect_Loaded;
            SizeChanged -= TreeSelect_SizeChanged;
            SizeChanged += TreeSelect_SizeChanged;
        }

        private ExtendedTreeView _treeView;

        public override void OnApplyTemplate()
        {
            base.OnApplyTemplate();
            this._treeView = Template.FindName("PART_TreeView", this) as ExtendedTreeView;
            if (this._treeView != null)
            {
                //this._treeView.SelectedItemChanged += _TreeView_SelectedItemChanged;
                _treeView.OnHierarchyMouseUp += OnTreeViewHierarchyMouseUp;
                _treeView.AddHandler(System.Windows.Controls.TreeViewItem.SelectedEvent, new System.Windows.RoutedEventHandler(treeview_Selected));
            }
        }

        //protected override void OnDropDownClosed(EventArgs e)
        //{
        //    base.OnDropDownClosed(e);
        //    this.SelectedItem = _treeView.SelectedItem;
        //    this.UpdateText();
        //}

        //protected override void OnDropDownOpened(EventArgs e)
        //{
        //    base.OnDropDownOpened(e);
        //    this.UpdateText();
        //}

        private bool _interChanged = false;
        /// <summary>
        /// Handles clicks on any item in the tree view
        /// </summary>
        private void OnTreeViewHierarchyMouseUp(object sender, RoutedEventArgs e)
        {
            if (IsMulti)
            {
                _interChanged = true;
                SelectedItems.Clear();
                foreach (var item in GenerateMultiObject(Items))
                {
                    SelectedItems.Add(item);
                }
                _interChanged = false;
            }
            else
            {
                //This line isn't obligatory because it is executed in the OnDropDownClosed method, but be it so
                this.SelectedItem = _treeView.SelectedItem;                
                base.SelectedItem = this.SelectedItem;
                this.IsDropDownOpen = false;
            }
            this.UpdateText();
        }

        private void treeview_Selected(object sender, RoutedEventArgs e)
        {
            TreeViewItem item = (e.OriginalSource as TreeViewItem);
            if (item != null)
            {
                item.BringIntoView();
            }
        }

        protected override bool IsItemItsOwnContainerOverride(object item)
        {
            if (item is ComboBoxItem)
                return true;
            else
                return false;
        }

        protected override void PrepareContainerForItemOverride(DependencyObject element, object item)
        {
            var uie = element as FrameworkElement;

            if (!(item is ComboBoxItem))
            {
                var textBinding = new Binding(DisplayMemberPath);
                textBinding.Source = item;
                uie.SetBinding(ContentPresenter.ContentProperty, textBinding);
            }

            base.PrepareContainerForItemOverride(element, item);
        }

        private void TreeSelect_SizeChanged(object sender, SizeChangedEventArgs e)
        {
            UpdateText();
        }
        private void TreeSelect_Loaded(object sender, RoutedEventArgs e)
        {
            UpdateText();
        }

        private void UpdateSelectedItem()
        {          
            if (this.SelectedItem == null || string.IsNullOrEmpty(this.SelectedValuePath))
            {
                SelectedValue = null;
            }
            else
            {
                SelectedValue = this.SelectedItem.GetType().GetProperty(SelectedValuePath).GetValue(this.SelectedItem, null);
            }

            base.SelectedItem = this.SelectedItem;

            UpdateText();
        }

        private void UpdateSelectedItems(IList newitem, IList olditem)
        {
            if (olditem != null)
            {
                foreach (var item in olditem)
                {
                    if (item.GetType().GetProperty("IsChecked") != null)
                    {
                        item.GetType().GetProperty("IsChecked").SetValue(item, false);
                    }
                }

                ((INotifyCollectionChanged)olditem).CollectionChanged -= TreeSelect_CollectionChanged;
            }

            if (newitem != null)
            {
                foreach (var item in newitem)
                {
                    if (item.GetType().GetProperty("IsChecked") != null)
                    {
                        item.GetType().GetProperty("IsChecked").SetValue(item, true);
                    }
                }

                ((INotifyCollectionChanged)newitem).CollectionChanged += TreeSelect_CollectionChanged;
            }

            UpdateText();
        }

        //主要是外部改变的时候更新数据,如果没有这种场景,可以不用。
        private void TreeSelect_CollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
        {
            if (_interChanged) return;

            if (e.NewItems != null)
            {
                foreach (var item in e.NewItems)
                {
                    if (item.GetType().GetProperty("IsCheckedOnlySelf") != null)
                    {
                        item.GetType().GetProperty("IsCheckedOnlySelf").SetValue(item, true);
                    }
                }
            }

            if (e.OldItems != null)
            {
                foreach (var item in e.OldItems)
                {
                    if (item.GetType().GetProperty("IsCheckedOnlySelf") != null)
                    {
                        item.GetType().GetProperty("IsCheckedOnlySelf").SetValue(item, false);
                    }
                }
            }

            UpdateText();
        }

        private void _TreeView_SelectedItemChanged(object sender, RoutedPropertyChangedEventArgs<object> e)
        {
            UpdateText();
            base.SelectedItem = this.SelectedItem;
        }

        private void UpdateText()
        {
            if (!IsLoaded)
                return;

            if (IsMulti == false)
            {
                Text = GenerateText(SelectedItem);
            }
            else
            {
                Text = GenerateMultiText();
            }
        }

        public string GenerateText(object selectedItem)
        {
            var text = "";
            if (selectedItem == null)
            {
                text = "";
            }
            else if (selectedItem is ComboBoxItem)
            {
                var msi = selectedItem as ComboBoxItem;
                text += msi.Content.ToString();
            }
            else
            {
                if (!string.IsNullOrEmpty(DisplayMemberPath) && selectedItem.GetType().GetProperty(DisplayMemberPath) != null)
                    text += selectedItem.GetType().GetProperty(DisplayMemberPath).GetValue(selectedItem, null).ToString();
                else
                    text += selectedItem.ToString();

                if (selectedItem.GetType().GetProperty("IsSelected") != null)
                {
                    selectedItem.GetType().GetProperty("IsSelected").SetValue(selectedItem, true);
                }
            }

            return text;
        }

        private string GenerateMultiText()
        {
            var text = "";
            var isFirst = true;
          
            if (SelectedItems != null)
            {
                foreach (var item in SelectedItems)
                {
                    string txt = null;
                    if (item is ComboBoxItem)//这个还未支持多选,按单选处理
                    {
                        var msi = item as ComboBoxItem;
                        txt = msi.Content.ToString();
                    }
                    else
                    {
                        if (item.GetType().GetProperty("IsChecked") != null && item.GetType().GetProperty("IsChecked").GetValue(item, null).ToString() == "True")
                        {
                            if (!string.IsNullOrEmpty(DisplayMemberPath) && item.GetType().GetProperty(DisplayMemberPath) != null)
                                txt = item.GetType().GetProperty(DisplayMemberPath).GetValue(item, null).ToString();
                            else
                                txt = item.ToString();
                        }
                    }

                    if (!isFirst)
                        text += TextSeparator;
                    else
                        isFirst = false;

                    text += txt;

                    if (MaxTextLength == null)
                    {
                        if (!ValidateStringWidth(text + ExceededTextFiller))
                        {
                            if (text.Length == 0)
                                return null;
                            text = text.Remove(text.Length - 1);
                            while (!ValidateStringWidth(text + ExceededTextFiller))
                            {
                                if (text.Length == 0)
                                    return null;
                                text = text.Remove(text.Length - 1);
                            }
                            return text + ExceededTextFiller;
                        }
                    }
                    else if (text.Length >= MaxTextLength)
                    {
                        return text.Cut((int)MaxTextLength, ExceededTextFiller);
                    }
                }
            }
            return text;
        }

        private List<object> GenerateMultiObject(System.Collections.IEnumerable items)
        {
            List<object> objs = new List<object>();
            foreach (var item in items)
            {
                object obj = null;
                if (item is ComboBoxItem)//这个还未支持多选,按单选处理
                {
                    var msi = item as ComboBoxItem;
                    if (msi.IsSelected)
                    {
                        obj = item;
                    }
                }
                else
                {
                    if (item.GetType().GetProperty("IsChecked") != null && item.GetType().GetProperty("IsChecked").GetValue(item, null).ToString() == "True")
                    {
                        obj = item;
                    }
                }

                if (obj != null)
                    objs.Add(obj);

                if (item.GetType().GetProperty("Children") != null)
                {
                    objs.AddRange(GenerateMultiObject(item.GetType().GetProperty("Children").GetValue(item, null) as System.Collections.IEnumerable));
                }
            }

            return objs;
        }

        private bool ValidateStringWidth(string text)
        {
            var size = MeasureString(text);
            if (size.Width > (ActualWidth - Padding.Left - Padding.Right - 30))
                return false;
            else
                return true;

        }

        private Size MeasureString(string candidate)
        {
            var formattedText = new FormattedText(candidate, System.Globalization.CultureInfo.CurrentCulture, FlowDirection.LeftToRight, new Typeface(FontFamily, FontStyle, FontWeight, FontStretch), FontSize, Brushes.Black, new NumberSubstitution(), TextFormattingMode.Display);

            return new Size(formattedText.Width, formattedText.Height);
        }
    }
}

然后绑定的类对象如下:

    public abstract class BaseTreeItemViewModel : INotifyPropertyChanged, IBaseTreeItemViewModel//组织机构树节点
    {
        #region 基本属性 
        private string _name;
        public string Name
        {
            get { return _name; }
            set
            {
                _name = value;
                OnPropertyChanged("Name");
            }
        }

        #endregion

        #region 
        private ObservableCollection<BaseTreeItemViewModel> _children;
        public ObservableCollection<BaseTreeItemViewModel> Children
        {
            get
            {
                if (_children == null)
                {
                    _children = new ObservableCollection<BaseTreeItemViewModel>();
                    _children.CollectionChanged += new NotifyCollectionChangedEventHandler(OnChildrenChanged);
                }

                return _children;
            }
        }

        protected void OnChildrenChanged(object sender, NotifyCollectionChangedEventArgs e)
        {
            // Note: This section does not account for multiple items being involved in change operations.
            // Note: This section does not account for the replace operation.
            if (e.Action == NotifyCollectionChangedAction.Add)
            {
                BaseTreeItemViewModel child = (BaseTreeItemViewModel)e.NewItems[0];
                child.Parent = this;
            }
            else if (e.Action == NotifyCollectionChangedAction.Remove)
            {
                BaseTreeItemViewModel child = (BaseTreeItemViewModel)e.OldItems[0];
                if (child.Parent == this)
                {
                    child.Parent = null;
                }
            }
        }

        public BaseTreeItemViewModel Parent { get; set; }

        private bool _isExpanded = true;
        public bool IsExpanded
        {
            get { return _isExpanded; }
            set
            {
                if (_isExpanded != value)
                {
                    _isExpanded = value;
                    OnPropertyChanged("IsExpanded");
                }
            }
        }

        private bool _isSelected;
        public bool IsSelected
        {
            get { return _isSelected; }
            set
            {
                if (_isSelected != value)
                {
                    _isSelected = value;
                    OnPropertyChanged("IsSelected");
                }
            }
        }

        private bool _isChecked = false;
        public bool IsChecked
        {
            get
            {
                return _isChecked;
            }
            set
            {
                if (_isChecked != value)
                {
                    _isChecked = value;
                    OnPropertyChanged("IsChecked");

                    SetChildChecked(_isChecked);
                }
            }
        }

        public bool IsCheckedOnlySelf
        {
            get
            {
                return _isChecked;
            }
            set
            {
                _isChecked = value;
                OnPropertyChanged("IsChecked");
            }
        }

        private void SetChildChecked(bool isChecked)
        {
            if (Children != null)
            {
                foreach (var child in Children)
                {
                    child.IsChecked = isChecked;
                    child.SetChildChecked(isChecked);
                }
            }
        }

        public void SetChecked(bool isChecked)
        {
            _isChecked = isChecked;
            OnPropertyChanged("IsChecked");
        }

        public int Level
        {
            get
            {
                if (Parent == null)
                {
                    return 0;
                }
                else
                {
                    return Parent.Level + 1;
                }
            }
        }

        #region TreeDataGrid专用
        public double MarginLeft
        {
            get
            {
                return Level * 20 + 10;
            }
        }
        public Visibility ChildVisible
        {
            get
            {
                if (Children.Count == 0)
                {
                    return Visibility.Collapsed;
                }
                else
                {
                    return Visibility.Visible;
                }
            }
        }

        public void TreeDataGridOnPropertyChanged()
        {
            OnPropertyChanged("MarginLeft");
            OnPropertyChanged("ChildVisible");
        }
        #endregion

        /// <summary>
        /// 设置所有子项展开状态
        /// </summary>
        /// <param name="isExpanded"></param>
        public void SetChildrenExpanded(bool isExpanded)
        {
            foreach (BaseTreeItemViewModel child in Children)
            {
                child.IsExpanded = isExpanded;
                child.SetChildrenExpanded(isExpanded);
            }
        }


        public void InsertChild(int index, BaseTreeItemViewModel child)
        {
            if (!Children.Contains(child))
            {
                child.Parent = this;
                child.TreeDataGridOnPropertyChanged();
                Children.Insert(index, child);
                TreeDataGridOnPropertyChanged();
            }
        }

        public void AddChild(BaseTreeItemViewModel child)
        {
            if (!Children.Contains(child))
            {
                child.Parent = this;
                child.TreeDataGridOnPropertyChanged();
                Children.Add(child);
                TreeDataGridOnPropertyChanged();
            }
        }

        public void AddChildRange(IEnumerable<BaseTreeItemViewModel> childs)
        {
            foreach (var child in childs)
            {
                if (!Children.Contains(child))
                {
                    child.Parent = this;
                    child.TreeDataGridOnPropertyChanged();
                    Children.Add(child);
                }
            }
            TreeDataGridOnPropertyChanged();
        }

        public void RemoveChild(BaseTreeItemViewModel child)
        {
            if (Children.Contains(child))
            {
                child.Parent = null;
                Children.Remove(child);
                TreeDataGridOnPropertyChanged();
            }
        }

        public void ClearChild()
        {
            if (_children != null)
            {
                _children.Clear();
                TreeDataGridOnPropertyChanged();
            }
        }
        #endregion       

        public IEnumerable<BaseTreeItemViewModel> GetHierarchy()
        {
            return GetAscendingHierarchy().Reverse();
        }

        public IEnumerable<BaseTreeItemViewModel> GetChildren()
        {
            return this.Children;
        }

        private IEnumerable<BaseTreeItemViewModel> GetAscendingHierarchy()
        {
            var vm = this;
            yield return vm;
            while (vm.Parent != null)
            {
                yield return vm.Parent;
                vm = vm.Parent;
            }
        }

        public event PropertyChangedEventHandler PropertyChanged;
        protected void OnPropertyChanged(string propertyName)
        {
            if (PropertyChanged != null)
            {
                PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
            }
        }        
    }

使用代码

<util:TreeSelect
  Width="200"
  ItemsSource="{Binding Data}"
  SelectedItems="{Binding SelectedDatas}"
  ItemTemplate="{x:Null}"
  DisplayMemberPath="Name"
  IsMulti="True" xmlns:util="https://astudio.github.io/utilcontrol">
  <util:TreeSelect.Resources>
    <HierarchicalDataTemplate
      DataType="{x:Type viewmodel:Person}"
      ItemsSource="{Binding Path=Children}">
      <StackPanel
        Orientation="Horizontal">
        <CheckBox
          Margin="2,0,0,0"
          IsChecked="{Binding IsChecked,Mode=TwoWay}" />
        <Grid
          Margin="2,0,2,0">
          <TextBlock
            x:Name="txtName"
            Text="{Binding Name, Mode=TwoWay}"
            Width="Auto" />
        </Grid>
      </StackPanel>
    </HierarchicalDataTemplate>
  </util:TreeSelect.Resources>
</util:TreeSelect>

实现是实现了,绑定对象还需要有定义好的属性。

这个东西在一些场景下没有必要封装成控件,其实就是一个树形控件的处理,封装在ComboBox里而已,不封装进行使用更加简单灵活。

 

posted @ 2020-12-31 09:46  竹天笑  阅读(1232)  评论(0编辑  收藏  举报