先上图
后面是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; }