WPF控件之TreeView扩展应用(三)

        这周,来实现某个网友提的一个需求,查询节点,对于这个需求,我首先想到的界面是一个textbox和treeview的组合,关于查询,我想到的就是遍历树嘛,不过遍历树的方法有很多,递归的、非递归的、前、中、后等等,后面我想了想,我在广度优先和深度优先中,选择了广度优先中的非递归,当然,你也可以用其他的,这里我只想挑选一个好理解的方法来实现。

        分析完了,那么,写代码吧,老规矩,还是先看总体项目的结构(看上去内容很多,其实大部分是后续讲的目录树拖动,今天才把demo弄好)

 这次我采用了一些设计模式来实现,所以跟之前的项目有点差别,创建类库后,定义一个接口

    public interface ITreeViewInterface
    {
        void Add(object obj);

        void Delete(object obj);

        void Update(string str);

        void Search(string str);

        void Select(object obj);
    }

下一步是界面的搭建,如图,三行的简单布局,一个按钮,一个textbox和一个TreeView的组合

 继续,开始创建对象,先来创建一个基类,如图(BindableObject,我就不介绍了,我这给出代码,不懂的去看我之前的)

    public class BindableObject : INotifyPropertyChanged
    {
        public event PropertyChangedEventHandler? PropertyChanged;

        public void OnPropertyChanged([CallerMemberName]string propertyName = null)
        {
            PropertyChanged?.Invoke( this, new PropertyChangedEventArgs( propertyName ) );
        }
    }
    public abstract class BaseModel : BindableObject
    {
        public abstract Guid Id { get; set; }

        public abstract string Name { get; set; }
    }

接下来让目录树对象继承基类,如图(只截了部分,太长了)

 public class TreeViewModel : BaseModel
    {
        private Guid id;

        public override Guid Id
        {
            get => id;
            set
            {
                id = value;
                OnPropertyChanged();
            }
        }

        private string name;

        public override string Name
        {
            get => name;
            set
            {
                name = value;
                OnPropertyChanged();
            }
        }

        private Visibility isTextBoxVisibility = Visibility.Hidden;
        /// <summary>
        /// 右键显示隐藏
        /// </summary>
        public Visibility IsTextBoxVisibility
        {
            get => isTextBoxVisibility;
            set
            {
                isTextBoxVisibility = value;
                OnPropertyChanged();
            }
        }

        private Visibility isNodeVisibility = Visibility.Visible;
        /// <summary>
        /// 查询后节点显示隐藏
        /// </summary>
        public Visibility IsNodeVisibility
        {
            get => isNodeVisibility;
            set
            {
                isNodeVisibility = value;
                OnPropertyChanged();
            }
        }

        public TreeViewModel Parent { get; set; }

        public ObservableCollection<TreeViewModel> Children { get; set; } = new ObservableCollection<TreeViewModel>();

        public static TreeViewModel CreateModel()
        {
            TreeViewModel tree = new TreeViewModel()
            {
                Name="哈哈哈"
            };
            for (int i = 0; i < 3; i++)
            {
                TreeViewModel treeViewModel = new TreeViewModel()
                {
                    Id = Guid.NewGuid(),
                    Name = $"{i}",
                };
                for (int j = 0; j < 5; j++)
                {
                    treeViewModel.Children.Add(new TreeViewModel()
                    {
                        Id = Guid.NewGuid(),
                        Name = $"{i}_{j}",
                        Parent = treeViewModel,
                    });
                }
                tree.Children.Add(treeViewModel);
            }
            return tree;
        }

我把初始化树的方法放在了目录树类里,让它本身有个方法自我初始化。

        进军ViewModel,如图,拿到我们刚才定义的接口, 分别创建增、删、改、查已经选择节点的命令和方法(选择的方法主要用于右键重命名)(DelegateCommand不多说了)

          public class MainViewModel
    {
        private readonly ITreeViewInterface @interface;

        #region 命令
        public DelegateCommand<BaseModel> AddCommand { get; set; }
        public DelegateCommand<BaseModel> DeleteCommand { get; set; }
        public DelegateCommand<string> UpdateCommand { get; set; }
        public DelegateCommand<string> SearchCommand { get; set; }
        public DelegateCommand<BaseModel> SelectCommand { get; set; }
        #endregion

        public MainViewModel(ITreeViewInterface @interface)
        {
            this.@interface = @interface;
            AddCommand = new DelegateCommand<BaseModel>(Add);
            DeleteCommand = new DelegateCommand<BaseModel>(Delete);
            UpdateCommand = new DelegateCommand<string>(Update);
            SearchCommand = new DelegateCommand<string>(Search);
            SelectCommand = new DelegateCommand<BaseModel>(Select);
        }

        private void Add(BaseModel obj)
        {
            @interface.Add(obj);
        }

        private void Delete(BaseModel obj)
        {
            @interface.Delete(obj);
        }

        private void Update(string str)
        {
            @interface.Update(str);
        }

        private void Search(string str)
        {
            @interface.Search(str);
        }

        private void Select(BaseModel obj)
        {
            @interface.Select(obj);
        }
    }

 然后,创建一个管理者——TreeViewManager,让它继承接口,如图

    public class TreeViewManager : ITreeViewInterface
    {

        private static TreeViewModel _currentTree;
        /// <summary>
        /// 目录树
        /// </summary>
        public static TreeViewModel CurrentTree
        {
            get => _currentTree;
            set
            {
                _currentTree = value;
                OnCurrentProjectChanged();
            }
        }

        public static event EventHandler CurrentProjectChanged;
        /// <summary>
        /// 通知更新
        /// </summary>
        public static void OnCurrentProjectChanged()
        {
            CurrentProjectChanged?.Invoke(CurrentTree, new EventArgs());
        }

        private TreeViewModel searchModel;

        /// <summary>
        /// 初始化
        /// </summary>
        public static void CreateTree()
        {
            CurrentTree = TreeViewModel.CreateModel();
        }

        /// <summary>
        /// 添加
        /// </summary>
        /// <param name="obj"></param>
        public void Add(object obj)
        {
            if (obj == null)
            {
                AddRoot();
                return;
            }

            GetTreeModel(obj);

            AddChild();
        }

        /// <summary>
        /// 删除
        /// </summary>
        /// <param name="obj"></param>
        public void Delete(object obj)
        {
            GetTreeModel(obj);
            if (searchModel.Parent == null)
            {
                CurrentTree.Children.Remove(searchModel);
            }
            else
            {
                searchModel.Parent.Children.Remove(searchModel);
            }

        }

        /// <summary>
        /// 更新
        /// </summary>
        /// <param name="str"></param>
        public void Update(string str)
        {
            searchModel.Name = str;
            searchModel.IsTextBoxVisibility = System.Windows.Visibility.Hidden;
        }

        /// <summary>
        /// 查询
        /// </summary>
        /// <param name="str"></param>
        public void Search(string str)
        {
            if (str == "" || str == null)
            {
                CurrentTree.Children.ToList().ForEach(x =>
                {
                    x.BFSTraverseTree((item) =>
                    {
                        item.IsNodeVisibility = System.Windows.Visibility.Visible;
                    });
                });
                return;
            }

            CurrentTree.Children.ToList().ForEach(x =>
            {
                x.BFSTraverseTree((item) =>
                {
                    // 中文包含
                    if (item.Name.Contains(str))
                    {
                        ShowSearchResult(item);
                    }
                    else
                    {
                        item.IsNodeVisibility = System.Windows.Visibility.Collapsed;
                    }
                });
            });
        }

        /// <summary>
        /// 右键重命名显示选择的的节点
        /// </summary>
        /// <param name="obj"></param>
        public void Select(object obj)
        {
            GetTreeModel(obj);
            searchModel.IsTextBoxVisibility = System.Windows.Visibility.Visible;
        }

        /// <summary>
        /// 添加根节点
        /// </summary>
        private void AddRoot()
        {
            CurrentTree.Children.Add(new TreeViewModel()
            {
                Id = Guid.NewGuid(),
                Name = $"{CurrentTree.Children.Count}"
            });
        }

        /// <summary>
        /// 添加子节点
        /// </summary>
        private void AddChild()
        {
            searchModel.Children.Add(new TreeViewModel()
            {
                Id = Guid.NewGuid(),
                Name = $"{searchModel.Name}_{searchModel.Children.Count}",
                Parent = searchModel
            });
        }

        /// <summary>
        /// 递归变量目录树
        /// </summary>
        /// <param name="model"></param>
        /// <param name="guid"></param>
        /// <returns></returns>
        private TreeViewModel TraversalTree(TreeViewModel model, Guid guid)
        {
            foreach (var item in model.Children)
            {
                if (item.Id == guid)
                {
                    searchModel = item;
                    return searchModel;
                }
                TraversalTree(item, guid);
            }
            return null;
        }

        /// <summary>
        /// 获取选择的节点
        /// </summary>
        /// <param name="obj"></param>
        private void GetTreeModel(object obj)
        {
            var baseResult = obj as BaseModel;

            TraversalTree(CurrentTree, baseResult.Id);
        }

        /// <summary>
        /// 显示查询的结果
        /// </summary>
        /// <param name="model"></param>
        private void ShowSearchResult(TreeViewModel model)
        {
            if (model.Parent != null)
            {
                model.Parent.IsNodeVisibility = System.Windows.Visibility.Visible;
                model.IsNodeVisibility = System.Windows.Visibility.Visible;
                ShowSearchResult(model.Parent);
            }
            else
            {
                model.IsNodeVisibility = System.Windows.Visibility.Visible;
            }
        }

    }

 接着为treeviewmodel类创建一个扩展方法——BFSTraverseTree,如图

    public static class TreeViewCommon
    {
        /// <summary>
        /// 非递归广度优先遍历树
        /// </summary>
        /// <param name="item"></param>
        /// <param name="action"></param>
        public static void BFSTraverseTree(this TreeViewModel item, Action<TreeViewModel> action)
        {
            Queue<TreeViewModel> queue = new Queue<TreeViewModel>();
            queue.Enqueue(item);
            while (queue.Count > 0)
            {
                var curItem = queue.Dequeue();
                action(curItem);
                foreach (var subItem in curItem.Children)
                {
                    queue.Enqueue(subItem);
                }
            }
        }
    }

关于Queue,官方给了很好的介绍,这里我就大致说下它的作用就是把目录树放到等待队列里面,当我们查询的时候,会直接在这个队列里查询(Queue),

 接着,去完善viewmodel,添加如下代码

        public TreeViewModel TreeModel => TreeViewManager.CurrentTree;

            TreeViewManager.CreateTree();

那么,开始绑定界面把,如图,在界面的cs文件中,绑定数据源,用依赖注入的方法

    <Window.Resources>

        <ContextMenu x:Key="menu">
            <MenuItem
                Command="{Binding DataContext.AddCommand, RelativeSource={RelativeSource AncestorType=Window, Mode=FindAncestor}}"
                CommandParameter="{Binding}"
                Header="添加子节点" />
            <MenuItem
                Command="{Binding DataContext.DeleteCommand, RelativeSource={RelativeSource AncestorType=Window, Mode=FindAncestor}}"
                CommandParameter="{Binding}"
                Header="删除节点" />
            <MenuItem
                Command="{Binding DataContext.SelectCommand, RelativeSource={RelativeSource AncestorType=Window, Mode=FindAncestor}}"
                CommandParameter="{Binding}"
                Header="修改节点" />
        </ContextMenu>
    </Window.Resources>

    <Grid>
        <Grid.RowDefinitions>
            <RowDefinition Height="auto" />
            <RowDefinition Height="40" />
            <RowDefinition />
        </Grid.RowDefinitions>
        <StackPanel Margin="5" Orientation="Horizontal">
            <Button
                Width="100"
                Height="30"
                Command="{Binding AddCommand}"
                Content="添加" />

        </StackPanel>

        <TextBox
            x:Name="search"
            Grid.Row="1"
            HorizontalContentAlignment="Center"
            VerticalContentAlignment="Center">
            <i:Interaction.Triggers>
                <i:EventTrigger EventName="TextChanged">
                    <i:InvokeCommandAction Command="{Binding DataContext.SearchCommand, RelativeSource={RelativeSource Mode=FindAncestor, AncestorType=Window}}" CommandParameter="{Binding ElementName=search, Path=Text}" />
                </i:EventTrigger>
            </i:Interaction.Triggers>
        </TextBox>

        <TreeView
            x:Name="treeView"
            Grid.Row="2"
            AllowDrop="True"
            ItemsSource="{Binding TreeModel.Children}">
            <TreeView.Resources>
                <Style TargetType="TreeViewItem">
                    <Setter Property="ContextMenu" Value="{StaticResource menu}" />
                    <Setter Property="Visibility" Value="{Binding IsNodeVisibility}" />
                    <Style.Triggers>
                        <Trigger Property="IsSelected" Value="True">
                            <Setter Property="Background" Value="SkyBlue" />
                        </Trigger>
                    </Style.Triggers>
                </Style>
            </TreeView.Resources>
            <TreeView.ItemTemplate>
                <HierarchicalDataTemplate ItemsSource="{Binding Children}">
                    <Grid>
                        <TextBox
                            x:Name="textBox"
                            Text="{Binding Name}"
                            Visibility="{Binding IsTextBoxVisibility}">
                            <TextBox.InputBindings>
                                <KeyBinding
                                    Key="Enter"
                                    Command="{Binding DataContext.UpdateCommand, RelativeSource={RelativeSource AncestorType=Window, Mode=FindAncestor}}"
                                    CommandParameter="{Binding ElementName=textBox, Path=Text}" />
                            </TextBox.InputBindings>
                        </TextBox>
                        <TextBlock Text="{Binding Name}" />
                    </Grid>
                </HierarchicalDataTemplate>
            </TreeView.ItemTemplate>
        </TreeView>
    </Grid>

            this.DataContext = new MainViewModel(new TreeViewManager());

运行测试,如图

右侧修改发现显示不清楚,是因为同时显示了textbox和textblock,所以它俩只能显示一个,添加一个转换器,如图, 

 

    public class VisibilityToVisibilityConverter : IValueConverter
    {
        public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
        {
            if ((Visibility)value == Visibility.Visible)
            {
                return Visibility.Collapsed;
            }
            return Visibility.Visible;
        }

        public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
        {
            return null;
        }
    }

在界面添加引用后,在textblock添加如图代码

 再次运行测试,

没有问题了。

讨论

        这次的案例,因为只是查询,没有要求怎么查询,所有并没有添加规则,有就有,没有就没有,这是本次案例的规则。

        在编写的过程中,我过用不同的方式查询,采取什么方式,看自己对那种方式更好理解吧,

        这次是把之前的增删改做了一次优化,这次的代码更加通俗易懂

代码地址:https://github.com/TQtong/TreeView.git

结束

        好了,这次就到这了,欢迎各位批评指正,谢谢啦

评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值