代码改变世界

MVVM打造无限级TreeView

2010-09-02 21:55 囧月 阅读(...) 评论(...) 编辑 收藏

    自从了解了MVVM,就迷恋上了它,基本上抛弃了传统的开发方式~

    前些天无意中看到一篇关于用MVVM打造TreeView的文章(http://www.codeproject.com/KB/WPF/TreeViewWithViewModel.aspx),感觉比较美妙,遂拿来简单改造一下,使它支持无限级。

    首先,定义一个实体类:

    public class TreeItem
    {
        readonly List<TreeItem> _children = new List<TreeItem>();
        public List<TreeItem> Children
        {
            get { return _children; }
        }

        public string Name { get; set; }
        public int ID { get; set; }
        public int ParentID { get; set; }
    }

 

    接下来,把TreeViewItemViewModel、以及PropertyChangedBase直接搬过来。

    然后定义一个ViewModel:

	public class TreeItemViewModel : TreeViewItemViewModel
	{
		private TreeItem item;

		public TreeItemViewModel(TreeItem item) :
			this(item, null)
		{

		}

		public TreeItemViewModel(TreeItem item, TreeItemViewModel parent) :
			base(parent, true)
		{
			this.item = item;
		}

		public string Name
		{
			get { return item.Name; }
			set
			{
				item.Name = value;
				this.NotifyPropertyChanged(p => p.Name);
			}
		}
		
		protected override void LoadChildren()
		{
			var loadedData = TreeItemDemoData.LoadChildrens(item.ID);
			if (loadedData.Count > 0)
			{
				Children.Clear();
				foreach (var t in loadedData)
				{
					Children.Add(new TreeItemViewModel(t, this));
				}
			}
		}
	}

 

    以及相关Demo数据:

	public class TreeItemDemoData
	{
		private static readonly List<TreeItem> data;

		static TreeItemDemoData()
		{
			data = new List<TreeItem>();
			for (int i = 0; i < 10; i++)
			{
				data.Add(new TreeItem
		         {
		         	Name = string.Format("Name {0}", i.ToString()),
		         	ID = i,
		         	ParentID = i - 1
		         });
			}
		}

		public static List<TreeItem> LoadChildrens(int parentId)
		{
			return data.Where(t => t.ParentID == parentId).ToList();
		}
	}

 

    定义界面:

<Window x:Class="TreeViewDemo.Window1"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:vm="clr-namespace:TreeViewDemo.ViewModel"
        Title="TreeViewDemo" Height="300" Width="300"
>
	<StackPanel>
		<TreeView ItemsSource="{Binding}">
			<TreeView.ItemContainerStyle>
				<Style TargetType="{x:Type TreeViewItem}">
					<Setter Property="IsExpanded" Value="{Binding IsExpanded, Mode=TwoWay}" />
					<Setter Property="IsSelected" Value="{Binding IsSelected, Mode=TwoWay}" />
					<Setter Property="FontWeight" Value="Normal" />
					<Style.Triggers>
						<Trigger Property="IsSelected" Value="True">
							<Setter Property="FontWeight" Value="Bold" />
						</Trigger>
					</Style.Triggers>
				</Style>
			</TreeView.ItemContainerStyle>
			<TreeView.Resources>
				<HierarchicalDataTemplate
					DataType="{x:Type vm:TreeItemViewModel}"
					ItemsSource="{Binding Children}">
					<StackPanel Orientation="Horizontal">
						<TextBlock Text="{Binding Name}" />
					</StackPanel>
				</HierarchicalDataTemplate>
			</TreeView.Resources>
		</TreeView>
	</StackPanel>
</Window>

 

    最后,绑定数据:

	public partial class Window1 : Window
	{
		public Window1()
		{
			InitializeComponent();
			DataContext = new ObservableCollection<TreeItemViewModel>(
				TreeItemDemoData.LoadChildrens(0).Select(t => new TreeItemViewModel(t))
			);
		}
	}

    OK,完成,发现HierarchicalDataTemplate挺神奇的~_~。

 

    相信TreeView应用范围还是相当广的,比如某些应用的数据N级分类。

    当然,如果是数据库应用,可能会有一定延迟,造成界面的假死,在此对本文例子稍加改造,使用多线程提高UI的响应速度。

    对TreeItemViewModel进行改造:

		protected override void LoadChildren()
		{
			PreLoad();
		}
		
		private string originalName;
		private Dispatcher uiThreadDispatcher;
		private void PreLoad()
		{
			originalName = Name;
			Name = Name + " (expanding...)";
			uiThreadDispatcher = Dispatcher.CurrentDispatcher;			
			ThreadPool.QueueUserWorkItem(Loading);
		}
		
		private void Loading(object state)
		{
			Thread.Sleep(500);//模拟延迟
			var loadedData = TreeItemDemoData.LoadChildrens(item.ID);
			if (loadedData.Count > 0)
			{
				uiThreadDispatcher.BeginInvoke(DispatcherPriority.Background,
				                               new Action(() => Children.Clear()));
				foreach (var t in loadedData)
				{
					uiThreadDispatcher.BeginInvoke(DispatcherPriority.Background,
					                               new Action<TreeItem>(vm => Children.Add(new TreeItemViewModel(vm, this))),
					                               t);
				}
			}
			Name = originalName;
		}

    只是简单的小技巧~本文就到这里。

    DEMO地址