C# WPF MVVM 实战 – 3 – 树结构

树结构放在 WPF ,有大家熟悉的 TreeView、Menu / MenuItem 等等,自定义的话它是 HierarchicalDataTemplate。

用上 MVVM 模式,视图与数据分离,意味着你不再需要管 UI ,不用再在 TreeView 内上上下下跑来跑去找控件了。MVVM 不是把树结构变成不是一颗树,只是,你操作的,是一个具树结构的集合而已。我很怕搞 UI,我觉得,这是个解脱,起码对我是那样。

我说,如果你发现自己在纠结 TreeView 内怎样找控件,或者在研究它单一个元素的结构(Grid 包裹着 Border、Border又包裹着 TextBox、最后,哦,找到 TextBox 的 Text 了,手起刀落,改它… 之类),与其纠结下去,不如收手吧,试试用下面方式,你会喜欢的。

我觉得本来 WPF 的设计就是给你这样用的。

TreeView

使用 WPF + MVVM,特别是当你从 WinForm 转过来,你需要一个重大的思路改变。UI 是用来「显示」数据,并非「暂存」数据。它只是个与用户交互的媒介。当对数据操作,你要从数据本身下手,而不是从 UI 找。

image

举个例子,主菜单,是左侧显示,外层 Expander,里面的内容每一单元放一个 TreeView,TreeView 内每一项的结构是左边显示图示,右边显示文字标题,整项都可以双击打开某某功能的界面。这很普通吧。但要求是模块加载后初始化时可以动态插入项,插入逻辑是提供上一层菜单的标题时,插在它下一级。没有提供上一级时候,加在最顶层。

不是绑定的话,写一开始的菜单是很简单,麻烦在于要开放方法出来,接受上级菜单标题 string、图示 URI 、标题 string、和需要打开的界面引用。这方法的代码,需要在菜单结构中找出所谓的上级是哪个项。上级没有的话,加进去 Expander,有上级就纠结了,要在 TreeView 的结构中找,在 UI 找,这时你必须清楚 TreeView 内项目的结构,比如内容是 Grid 你要在里面找出 TextBlock 控件的文字是什么,比较一下,符合时按照已定的结构加 node。

image

花了点时间,写完。客户说 Expander 不好用,通通改为 TreeView,你懂的。另一个客户,说除了左侧菜单外,希望上面有些传统菜单,额,你又改。设计师哪天看到 Dev 说好,我们改吧,那你又改吧。。。

这些问题,源于算法与 UI 结构紧扣在一起,特别是 XAML 界面,你要多复杂,有多复杂,然后你的插入算法也跟着复杂。而且 UI 变,你也要改。但这世界可以更美好的。

数据结构

为求简单,这结构只有标题。

 

public class MyMenuItem : INotifyPropertyChanged {

        public MyMenuItem() {
            Childs =new ObservableCollection<MyMenuItem>();
        }

        private string text;
        public string Text {
            get {
                return text;
            }
            set {
                text = value;
                if (PropertyChanged !=null)
                    PropertyChanged(this, new PropertyChangedEventArgs("Text"));
            }
        }

        private ObservableCollection<MyMenuItem> childs;
        public ObservableCollection<MyMenuItem> Childs {
            get {
                return childs;
            }
            set {
                childs = value;
                if (PropertyChanged !=null)
                    PropertyChanged(this, new PropertyChangedEventArgs("Childs"));
            }
        }

        #region INotifyPropertyChanged Members
        public event PropertyChangedEventHandler PropertyChanged;

        #endregion
    }

数据结构是树结构,你就要把它写成树结构,不用考虑 UI 那边怎样。

要对于结构操作,搜索标题然后加项的话,这类菜单我选择 Breadth First。扩展方法有时候觉得用的机会不多吧,来一个玩玩看。

internal static class MenuItemExtension {
        internal static  MyMenuItem Search(
            this MyMenuItem node, 
            Predicate<MyMenuItem> match) {

            Queue<MyMenuItem> queue =new Queue<MyMenuItem>();
            queue.Enqueue(node);
            while (queue.Count >0) {
                MyMenuItem thisNode = queue.Dequeue();
                if (match(thisNode))
                    return thisNode;
                foreach (MyMenuItem child in thisNode.Childs)
                    queue.Enqueue(child);
            }
            return null;
        }
    }

实际插菜单,对外开放的加菜单功能,大概这样实现咯。

public class MenuService {

        private MyMenuItem MainMenu;

        public MenuService() {
            //... 一些拿到主菜单的代码,比如从容器中 Resolve        }
        public void Add(string MenuText, string ParentText) {
            if (ParentText ==null) {
                this.MainMenu.Childs.Add(new MyMenuItem {
                    Text = MenuText
                });
            } else {
                MyMenuItem result =this.MainMenu.Search(x => {
                    return x.Text == ParentText;
                });
                if (result !=null) {
                    result.Childs.Add(new MyMenuItem {
                        Text = MenuText
                    });
                } else {
                    throw new ArgumentOutOfRangeException("ParentText");
                }
            }
        }
    }

一切都很合理,没有了奇怪的 UI 结构在算法内,任何形式的菜单,都能用这结构和方法。喜欢直接 TreeView 的就 TreeView,复杂起来的界面用 HierarchicalDataTemplate。

绑定写法请自己查 MSDN 或看书,不写出来了。下面源码有些超简单示例。

点击下载源代码:Lepton_Practical_MVVM_3.zip 

MVVM 大神 Josh Smith 在 Code Project 写了一篇相当经典的,关于 MVVM 与 TreeView 的做法,点击这里打开。我极力推荐。学习 WPF 和 Silverlight 的同学们,Josh Smith 在 wordpress 写了些博文,应该一篇不漏的看一遍(貌似要FQ)。

 

后记:2012-09-27 10:35 PM 关于Lepton_Practical_MVVM_3.zip,不好意思我上网抄了个 ViewModelBase 后,心痒,把它的 DisplayName 属性删除后忘记了改 #IF DEBUG 内的代码,请在这后记编写前下载了代码的朋友,在 VS 用 Release Build 运行,或者自行修改。当前版本已修正此问题。

我在这群里,欢迎加入交流:
开发板玩家群 578649319开发板玩家群 578649319
硬件创客 (10105555)硬件创客 (10105555)

posted @ 2012-09-26 23:35  Lepton  阅读(3590)  评论(0编辑  收藏  举报