zoukankan      html  css  js  c++  java
  • 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)

  • 相关阅读:
    C# List转换成DataTable
    表达式计算
    通过GitHub高级条件组合精确搜索开源项目学习
    今天开通博客啦 随便记录一下东西
    VSCode开发Vue-代码格式化最完美设置
    C# DataTable 行转列 列转行 同时转换
    [转载]DevExpress GridControl 使用方法技巧 总结 收录整理
    控件已成功添加到工具箱中,但未在活动设计器中启用
    js-beautify 不换行
    tomcat端口修改后在Eclipse中启动无效问题解决
  • 原文地址:https://www.cnblogs.com/leptonation/p/2704895.html
Copyright © 2011-2022 走看看