珂珂的个人博客 - 一个程序猿的个人网站

WPF 动态 TreeView

先上图

后面是ListView,下一篇文章会介绍

   前面有一篇关于WPF  TreeView的,但是它是一次加载所有数据,如果数据量特别大的时候显然不可取,于是想法加入动态。

有两种模式,一种是不用MVVM,采用添加 TreeViewItem 来实现,另一种是用MVVM模式数据驱动。

有个不好的地方,就是如果TreeViewItem.Items 没有数据(为0也一样),将不会显示节点的小三角,这样我就不能点击小三角的时候动态加载子项数据,应该有变通的方法就是重写TreeViewItem的样式,不过很麻烦,要自己处理缩进和展开。所以目前只能做到预加载下一级节点,当点击一级节点的时候会加载二级节点,但不会加载三级节点,只有点击二级节点的时候才会加载三级节点。

   查阅资料,竟然没有找到节点展开的事件,后来知道<HierarchicalDataTemplate DataType="{x:Type local:TreeModel}"  ItemsSource="{Binding Children2}">    点击节点前的小三角会自动调用这个绑定也就是Children2属性,确实应该从winform 的思想转变过来。在Model中定义一个委托 public delegate IList<TreeModel> GetChildrenHandler(TreeModel node);  

    由于加载节点可能是一个耗时的过程,所以需要异步,可能会出现异常:1、CollectionView 不支持从调度程序线程以外的线程对其 SourceCollection 进行的更改   2、调度程序进程挂起时无法执行此操作。我在编码的过程中发现在GetChildrenHandler 委托的方法中如果为控件赋值即使Dispatcher.Invoke 也会报错,后来将其改为 Dispatcher.BeginInvoke 就行了。

    在加载的时候我想把节点的图标改为Loading,但是WPF的Image对象并不能显示gif,查阅资料找到一个办法,封装成自定义控件,改了原作者的一句代码

 private void CreateNonGifAnimationImage()

        {

            image = new Image();

            image.ImageFailed += new EventHandler<ExceptionRoutedEventArgs>(image_ImageFailed);

            ImageSource src = (ImageSource)(new ImageSourceConverter().ConvertFromString(Source));

            image.Source = src;

            image.Stretch = Stretch;

            image.StretchDirection = StretchDirection;

            // this.AddChild(image);

            this.Content = image;

        }

注释的那句是以前的,this.Content = image; 是我加的,因为原方法在重新为控件赋值图片的时候会报错,大致意思是只能有一个Content,

还修改了Source属性,因为跨dll路径没写完整会报错

public string Source

        {

            get

            {

                string s = (string)this.GetValue(SourceProperty);

                if(s.Contains("FYJ.Windows.Controls;"))

                {

                    if (!s.StartsWith("pack://application:,,,"))

                    {

                        s = "pack://application:,,," + s;

                    }

                }

               

                return s;

            }

            set

            {

                this.SetValue(SourceProperty, value);

            }

        }

    另外在以前的Model中加入了一个是否显示复选框的属性,xaml代码中也将Checkbox的内容移动到Checkbox控件外。

修改后的Model代码

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.ComponentModel;
using System.Collections.ObjectModel;

namespace FYJ.Windows.Controls
{
    public class TreeModel : INotifyPropertyChanged
    {
        #region 私有变量
        /// <summary>
        /// Id值
        /// </summary>
        private string _id;
        /// <summary>
        /// 显示的名称
        /// </summary>
        private string _name;
        /// <summary>
        /// 图标路径
        /// </summary>
        private string _icon;
        /// <summary>
        /// 选中状态
        /// </summary>
        private bool? _isChecked;
        /// <summary>
        /// 折叠状态
        /// </summary>
        private bool _isExpanded;
        /// <summary>
        /// 子项
        /// </summary>
        private IList<TreeModel> _children;
        /// <summary>
        /// 父项
        /// </summary>
        private TreeModel _parent;
        #endregion

        public delegate IList<TreeModel> GetChildrenHandler(TreeModel node);

        /// <summary>
        /// 构造
        /// </summary>
        public TreeModel()
        {
            Children = new ObservableCollection<TreeModel>();
            _isChecked = false;
            IsExpanded = false;
            _icon = "/FYJ.Windows.Controls;component/Images/16_16/folder.png";
        }

        /// <summary>
        /// 获取子树
        /// </summary>
        /// fangyj 2015-8-18
        public GetChildrenHandler GetChildren
        {
            get;
            set;
        }

        /// <summary>
        /// 键值
        /// </summary>
        public string Id
        {
            get { return _id; }
            set
            {
                if (value != _id)
                {
                    _id = value;
                    NotifyPropertyChanged("Id");
                }
            }
        }

        /// <summary>
        /// 显示的字符
        /// </summary>
        public string Name
        {
            get { return _name; }
            set
            {
                if (value != _name)
                {
                    _name = value;
                    NotifyPropertyChanged("Name");
                }
            }
        }

        /// <summary>
        /// 图标
        /// </summary>
        public string Icon
        {
            get { return _icon; }
            set
            {
                if (value != _icon)
                {
                    _icon = value;
                    NotifyPropertyChanged("Icon");
                }
            }
        }

        /// <summary>
        /// 指针悬停时的显示说明
        /// </summary>
        public string ToolTip
        {
            get
            {
                return String.Format("{0}-{1}", Id, Name);
            }
        }


        private bool isShowCheckbox = true;
        /// <summary>
        /// 是否显示复选框  
        /// </summary>
        public bool IsShowCheckbox
        {
            get { return isShowCheckbox; }
            set { isShowCheckbox = value; }
        }

        /// <summary>
        /// 是否选中
        /// </summary>
        public bool? IsChecked
        {
            get
            {
                return _isChecked;
            }
            set
            {
                if (value != _isChecked)
                {
                    _isChecked = value;
                    NotifyPropertyChanged("IsChecked");
                }
            }
        }

        /// <summary>
        /// 是否展开
        /// </summary>
        public bool IsExpanded
        {
            get { return _isExpanded; }
            set
            {
                if (value != _isExpanded)
                {
                    //折叠状态改变
                    _isExpanded = value;
                    NotifyPropertyChanged("IsExpanded");
                }
            }
        }

        /// <summary>
        /// 父项
        /// </summary>
        public TreeModel Parent
        {
            get { return _parent; }
            set { _parent = value; }
        }

        //是否已经获取过子树
        private bool isGetedChildren = false;

        /// <summary>
        /// 子项
        /// </summary>
        public IList<TreeModel> Children
        {
            get
            {
                return _children;
            }
            set { _children = value; }
        }


        public IList<TreeModel> Children2
        {
            get
            {
                if (!isGetedChildren)
                {
                    if (GetChildren != null)
                    {
                        IList<TreeModel> list = GetChildren(this);
                        if (list != null)
                        {
                            foreach (var item in list)
                            {
                                item.Parent = this;
                                this.Children.Add(item);
                            }
                        }

                        _children = list;
                    }

                    isGetedChildren = true;
                }

                return _children;
            }
        }

        /// <summary>
        /// 设置所有子项的选中状态
        /// </summary>
        /// <param name="isChecked"></param>
        public void SetChildrenChecked()
        {
            if (Children != null && Children.Count > 0 && IsChecked != null)
            {
                foreach (TreeModel child in Children)
                {
                    child.IsChecked = IsChecked;
                    if (child.Children != null && Children.Count > 0)
                    {
                        child.SetChildrenChecked();
                    }
                }
            }
        }

        public void SetChildrenChecked(bool isCheck)
        {
            if (Children != null && Children.Count > 0)
            {
                foreach (TreeModel child in Children)
                {
                    child.IsChecked = isCheck;
                    if (child.Children != null && Children.Count > 0)
                    {
                        child.SetChildrenChecked(isCheck);
                    }
                }
            }
        }

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

        /// <summary>
        /// 属性改变事件
        /// </summary>
        public event PropertyChangedEventHandler PropertyChanged;
        public void NotifyPropertyChanged(String info)
        {
            if (PropertyChanged != null)
            {
                PropertyChanged(this, new PropertyChangedEventArgs(info));
            }
        }

        #region 新加

        /// <summary>
        /// 附加属性
        /// </summary>
        /// fangyj
        public object Tag { get; set; }


        public void SetParentChecked()
        {
            if (Parent != null)
            {
                if (Parent.Children.Where(c => c.IsChecked == null).Count() > 0)
                {
                    Parent.IsChecked = null;
                }
                else if (Parent.Children.Where(c => c.IsChecked == true).Count() == 0)
                {
                    Parent.IsChecked = false;
                }
                else if (Parent.Children.Where(c => c.IsChecked == true).Count() == Parent.Children.Count)
                {
                    Parent.IsChecked = true;
                }
                else if (Parent.Children.Where(c => c.IsChecked == true).Count() < Parent.Children.Count)
                {
                    Parent.IsChecked = null;
                }

                if (Parent.Parent != null)
                {
                    Parent.SetParentChecked();
                }
            }
        }

        #endregion

        /// <summary>
        /// 刷新子节点
        /// </summary>
        public void RefreshChildren()
        {
            if (GetChildren != null)
            {
                GetChildren(this);
            }
        }
    }
}


XAML

<UserControl x:Class="FYJ.Windows.Controls.CustomTreeView"
             xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
             xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" 
             xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
             xmlns:local="clr-namespace:FYJ.Windows.Controls" 
             xmlns:util="clr-namespace:FYJ.Windows.Controls.Util"
             mc:Ignorable="d" 
             d:DesignHeight="300" d:DesignWidth="300" Name="UC">

    <UserControl.Resources>
        <util:VisibilityConvert x:Key="visibilityConvert"></util:VisibilityConvert>
    </UserControl.Resources>
    
    <Grid>
        <DockPanel>

            <Border>
                <TreeView Name="tvZsmTree" SelectedItemChanged="tvZsmTree_SelectedItemChanged">
                    <TreeView.ContextMenu>
                        <ContextMenu>
                            <MenuItem Name="menuExpandAll" Header="全部展开" Click="menuExpandAll_Click">
                                <MenuItem.Icon>
                                    <Image Source="Images/16_16/folder_open_arrow.png" />
                                </MenuItem.Icon>
                            </MenuItem>
                            <MenuItem Name="menuUnExpandAll" Header="全部折叠" Click="menuUnExpandAll_Click">
                                <MenuItem.Icon>
                                    <Image Source="Images/16_16/folder_close_arrow.png" />
                                </MenuItem.Icon>
                            </MenuItem>
                            <MenuItem Name="menuSelectAll" Header="全部选中" Visibility="{Binding  ElementName=UC,Path=CheckboxVisibility}" Click="menuSelectAll_Click">
                                <MenuItem.Icon>
                                    <Image Source="Images/16_16/tick.png" />
                                </MenuItem.Icon>
                            </MenuItem>
                            <MenuItem Name="menuUnSelectAll" Header="全部取消" Visibility="{Binding  ElementName=UC,Path=CheckboxVisibility,Mode=TwoWay}" Click="menuUnSelectAll_Click" >
                                <MenuItem.Icon>
                                    <Image Source="Images/16_16/delete.png" />
                                </MenuItem.Icon>
                            </MenuItem>
                        </ContextMenu>
                    </TreeView.ContextMenu>
                    <TreeView.ItemContainerStyle>
                        <Style TargetType="TreeViewItem">
                            <Setter Property="IsExpanded" Value="{Binding IsExpanded, Mode=TwoWay}"></Setter>
                            <EventSetter Event="TreeViewItem.PreviewMouseRightButtonDown" Handler="TreeViewItem_PreviewMouseRightButtonDown"/>
                        </Style>
                    </TreeView.ItemContainerStyle>
                    <TreeView.ItemTemplate>
                        <HierarchicalDataTemplate DataType="{x:Type local:TreeModel}"  ItemsSource="{Binding Children2}">
                            <WrapPanel  Margin="-2,0,0,0" Orientation="Horizontal" x:Name="staTree">
                                <CheckBox Visibility="{Binding  ElementName=UC,Path=CheckboxVisibility}"  ToolTip="{Binding ToolTip}" FontSize="14" FontFamily="微软雅黑"  IsChecked="{Binding IsChecked, Mode=TwoWay}" Click="treeNode_Click" >
                                  
                                </CheckBox>
                                <StackPanel Orientation="Horizontal">
                                    <local:GifImage VerticalAlignment="Center" Source="{Binding Icon}" ></local:GifImage>
                                    <TextBlock Text="{Binding Name}"></TextBlock>
                                    <StackPanel.ContextMenu>
                                        <ContextMenu>
                                            <MenuItem Name="menuSelectAllChild" IsEnabled="{Binding Path=IsShowCheckbox}"  Header="全部选中子项" Click="menuSelectAllChild_Click">
                                                <MenuItem.Icon>
                                                    <Image Source="Images/16_16/tick.png" />
                                                </MenuItem.Icon>
                                            </MenuItem>
                                        </ContextMenu>
                                    </StackPanel.ContextMenu>
                                </StackPanel>
                            </WrapPanel>
                            <HierarchicalDataTemplate.Triggers>
                                <DataTrigger Binding="{Binding IsChecked}" Value="true">
                                    <Setter TargetName="staTree" Property="Background" Value="White"/>
                                </DataTrigger>
                            </HierarchicalDataTemplate.Triggers>
                        </HierarchicalDataTemplate>
                    </TreeView.ItemTemplate>
                </TreeView>
            </Border>
        </DockPanel>
    </Grid>
</UserControl>


下面是使用的方法

加载

  private async void btnInit_Click(object sender, RoutedEventArgs e)
        {
            txtStatus.Text = "正在加载buckets...";

            ObservableCollection<TreeModel> rootTreeList = new ObservableCollection<TreeModel>();

            IStore store = new AliyunStore();
            IList<string> buckets = store.ListBucket();

            await Task.Factory.StartNew(() =>
            {
                buckets = store.ListBucket();
            });

            if (buckets.Count == 0)
            {
                txtStatus.Text = "就绪";
                return;
            }

            foreach (string bucket in buckets)
            {
                IList<StoreModel> models = new List<StoreModel>();
                await Task.Factory.StartNew(() =>
                {
                    models = store.ListObject(bucket, "");
                });

                foreach (StoreModel model in models)
                {
                    TreeModel node = new TreeModel();
                    node.Id = model.ID;
                    node.Name = model.Name;
                    node.IsExpanded = false;
                    if (model.IsDirectory)
                    {
                        node.GetChildren = GetChildren;
                        node.Icon = "/FYJ.Windows.Controls;component/Images/16_16/folder.png";
                    }
                    else
                    {
                        node.Icon = "/FYJ.Windows.Controls;component/Images/16_16/file.png";
                    }

                    node.Tag = model;

                    rootTreeList.Add(node);
                }
            }

            this.objectTree.ItemsSourceData = rootTreeList;

            objectTree.CheckboxVisibility = Visibility.Collapsed;
        }


获取子节点的委托

        private async void GetChildren2(TreeModel node, IList<TreeModel> list)
        {
            string icon = node.Icon;
            node.Icon = "/FYJ.Windows.Controls;component/Images/16_16/loading.gif";

            //await this.Dispatcher.BeginInvoke((Action)(() =>
            //{
            txtStatus.Text = "正在加载...";
            // }));

            IStore store = new AliyunStore();

            IList<StoreModel> models = new List<StoreModel>();
            await Task.Factory.StartNew(() =>
            {
                StoreModel model = node.Tag as StoreModel;
                if (model != null)
                {
                    models = store.ListObject(model.Bucket, model.Key);
                }
            });

            //await Task.Factory.StartNew(() =>
            //{
            //    //测试
            //    Thread.Sleep(1000);
            //});

            foreach (StoreModel model in models)
            {
                TreeModel childnode = new TreeModel();
                childnode.Id = model.ID;
                childnode.Name = model.Name;
                childnode.Icon = model.Icon;
                childnode.IsExpanded = false;
                if (model.IsDirectory)
                {
                    childnode.GetChildren = GetChildren;
                    childnode.Icon = "/FYJ.Windows.Controls;component/Images/16_16/folder.png";
                }
                else
                {
                    childnode.Icon = "/FYJ.Windows.Controls;component/Images/16_16/file.png";
                }
                childnode.Tag = model;
                childnode.IsExpanded = false;
                childnode.IsShowCheckbox = false;

                list.Add(childnode);
            }

            node.Icon = icon;
            txtStatus.Text = "就绪";
        }


        private IList<TreeModel> GetChildren(TreeModel node)
        {
            ObservableCollection<TreeModel> list = new ObservableCollection<TreeModel>();
            GetChildren2(node, list);

            return list;
        }



上一篇:多线程域名查询工具

下一篇:个人代码全部开源


0 评论

查看所有评论

给个评论吧