先上图

后面是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;
}
珂珂的个人博客 - 一个程序猿的个人网站