天天看點

WPF控件之TreeView擴充應用(三)

        這周,來實作某個網友提的一個需求,查詢節點,對于這個需求,我首先想到的界面是一個textbox和treeview的組合,關于查詢,我想到的就是周遊樹嘛,不過周遊樹的方法有很多,遞歸的、非遞歸的、前、中、後等等,後面我想了想,我在廣度優先和深度優先中,選擇了廣度優先中的非遞歸,當然,你也可以用其他的,這裡我隻想挑選一個好了解的方法來實作。

        分析完了,那麼,寫代碼吧,老規矩,還是先看總體項目的結構(看上去内容很多,其實大部分是後續講的目錄樹拖動,今天才把demo弄好)

WPF控件之TreeView擴充應用(三)

 這次我采用了一些設計模式來實作,是以跟之前的項目有點差别,建立類庫後,定義一個接口

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的組合

WPF控件之TreeView擴充應用(三)

 繼續,開始建立對象,先來建立一個基類,如圖(BindableObject,我就不介紹了,我這給出代碼,不懂的去看我之前的)

WPF控件之TreeView擴充應用(三)
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; }
    }
           

接下來讓目錄樹對象繼承基類,如圖(隻截了部分,太長了)

WPF控件之TreeView擴充應用(三)
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不多說了)

WPF控件之TreeView擴充應用(三)
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)
        {
            [email protected] = @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,讓它繼承接口,如圖

WPF控件之TreeView擴充應用(三)
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,如圖

WPF控件之TreeView擴充應用(三)
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),

WPF控件之TreeView擴充應用(三)

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

WPF控件之TreeView擴充應用(三)
public TreeViewModel TreeModel => TreeViewManager.CurrentTree;
           
WPF控件之TreeView擴充應用(三)
TreeViewManager.CreateTree();
           

那麼,開始綁定界面把,如圖,在界面的cs檔案中,綁定資料源,用依賴注入的方法

WPF控件之TreeView擴充應用(三)
<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>
           
WPF控件之TreeView擴充應用(三)
this.DataContext = new MainViewModel(new TreeViewManager());
           

運作測試,如圖

WPF控件之TreeView擴充應用(三)

右側修改發現顯示不清楚,是因為同時顯示了textbox和textblock,是以它倆隻能顯示一個,添加一個轉換器

WPF控件之TreeView擴充應用(三)

,如圖, 

WPF控件之TreeView擴充應用(三)
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添加如圖代碼

WPF控件之TreeView擴充應用(三)

 再次運作測試,

WPF控件之TreeView擴充應用(三)

沒有問題了。

讨論

        這次的案例,因為隻是查詢,沒有要求怎麼查詢,所有并沒有添加規則,有就有,沒有就沒有,這是本次案例的規則。

        在編寫的過程中,我過用不同的方式查詢,采取什麼方式,看自己對那種方式更好了解吧,

        這次是把之前的增删改做了一次優化,這次的代碼更加通俗易懂

代碼位址:https://github.com/TQtong/TreeView.git

結束

        好了,這次就到這了,歡迎各位批評指正,謝謝啦

WPF控件之TreeView擴充應用(三)