OEA 中 WPF 树型表格整体重构

为什么要重构



 

    上两个月主要做了一件事情,那就是把 OEA 框架中的 TreeGrid 控件,从结构上重新设计,并大量重构现有代码。而花较大精力做这件事的原因,主要是因为:

  • 业务中需要支持一系列新功能:整行编辑、上下箭头键进行导航、合计行、锁定列 等。
  • 控件显示性能较差,需要支持列虚拟化。
  • 和 OEA 元数据系统耦合,希望独立为单独的控件程序集,提高复用性。
  • 不支持 xaml 声明的格式。原控件直接在后台用 OEA 代码生成,本质上作为一个 WinForm 控件来用。
  • 整个 TreeGrid 控件混合了三个控件代码而成,包括:GridView、TreeView 以及自身的一些代码,内容复杂,维护较难。
  • OEA 的界面层十分依赖当前的 TreeGrid 控件的各项功能,特别是树型实体的展现。但是,在 WPF 环境下,一直没有找到比较好用的 TreeGrid。而我们的 TreeGrid,由于之前做得一直不彻底,代码比较乱,经常出现 BUG,修改起来也非常费时。(我记得,之前开发的项目,花了太多时间在修正这个半成品控件的问题上了。还是 B/S 好啊,ExtJS 中就有很强大的 TreeGrid,十分省事。)
  • 随着对 WPF 技术了解得更深入,希望做一个完全独立的 WPF 控件。(用了那么久 WPF,想留下点东西。:))

 

TreeGrid 重构设计



 

    先看下历史代码结构:

图 TreeGrid 历史代码结构

    可以看出,主要包含三大块:GridTreeView、ObjectTreeView、TreeGrid。当初为了实现树型表格控件,所以我们在网络上搜索了大量文章,以下两篇是当时觉得最有用的:《CodeProject A Versatile TreeView for WPF_ Free source code and programming help》及《GridTreeView: Show Hierarchy Data with Details in Columns》。我们的前两大块,GridTreeView 及 ObjectTreeView 中的一些代码分别来自这两篇文章。然后最终由 TreeGird.cs 整合起来。 
    虽然这只是一个简单的半成品,但是已经达到了让界面上显示树型表格、并同时支持 OEA 中的 ListObjectView 控制器控制的两个目的。但是,随着框架的应用场景越来越多、使用越来越频繁,它暴露出来的问题也就更多了。许多新的功能也不能支持,这个在前面已经列举了许多。 
    另外,在使用 TreeGrid 时,其实开发人员还是希望同时拥有 树 及 表格 的两套 API。而老版本的表格却只有 树 节点操作的 API。而我们的表格 API,也应该象 WPF 原生的 System.Windows.Controls.DataGrid 控件接口类似。例如:表格由行组成、行由格子组成、可以通过数据找到对应的行、再通过行找到对应的格子等。这里,我分析了一下 DataGrid 中,认为一些比较重要的 API:

图 DataGrid 重点API

    九、十月私下的时间,都在思考、设计、编码这玩意儿。经过N多天的努力……目前已经把所有代码完成。TreeGrid 不再依赖 TreeView、GridView,而是直接从 ItemsControl 上继承下来,自定义逻辑树、可视树结构,自定义绘制过程。代码有点多,看下最终的效果:

 

图 TreeGrid 现在的代码

其相应的可视树结构如下:

图 OEA TreeGrid 可视树结构图

    具体的设计,可以看之前写的一篇文章:《OEA 中 WPF 树型表格虚拟化设计方案》。 
    具体的效果其实还不错,这是最近用 OEA 框架编写的《个人计划管理工具》,已经可以通过样式、模板来定制表格中的各种显示了:

图 基于 OEA 的个人计划管理工具中的表格示例图

 

自定义控件相关知识



 

    以下总结一下,本次控件设计中,觉得比较重要的几个知识点:

  • 控件逻辑与布局、渲染的分离。

    在 WPF 中,界面最终的渲染效果,是由可视树决定的。而每个可视树元素的测量、布局等行为,则是依赖于元素本身的数据,通过元素本身的算法决定。

    元素的逻辑行为与渲染是分离的:

    在元素发生诸如点击、拖动、选择等逻辑行为时,其实只变更了它内部的状态数据。同时,这些行为也可以调用 InvalidateMeasure 来标记该元素的状态为需要重新测量。而查看该方法源码,可以看到本质上也是修改元素的内部状态属性 MeasureDirty。

    当界面线程执行完逻辑处理后,会调用布局系统进行布局。布局系统会检测之前所有标记为需要重新测量的元素,并分别调用它们的 Measure 方法。然后,再按类似的逻辑来调用 Arrange 和 Render。

    界面线程会在需要时不断地调用 Measure,我们可以把自定义控件中很多重要的逻辑都可以在 MeasureOverride 中实现。例如,界面虚拟化代码就是在 Measure 过程中编写,先添加必要的可视树元素,然后再对这些新生成的元素进行测量。通过添加一些 bool 类型的防止重入的字段,Measure 中可以做所有逻辑操作之后、渲染之前的控件构造、刷新、替换、状态变更,并对最终确定的可视树子元素进行测量。如:

if(this._needBuildVisualTree){ 
    this._needBuildVisualTree = false; 
    this.BuildVisualTree(); 

    理解以上过程,将有助于更好地进行自定义控件的设计。

 

  • 元素与元素之间应该是松耦合的。

    在查看 WPF 源码时,可以经常看到一些代码,在通过可视树关系查找指定类型的元素后,再要对元素的可空性进行判断。而经常做这些可空性检测的原因是,WPF 控件的设计要求,各控件互相之间没有必然的联系。控件的设计者不会知道该控件会被上层开发人员把它放在哪个控件里。例如,ListBoxItem 并不一定要放在 ListBox 中才能显示。所以,在开发自定义控件时,尽量不要把控件的可视树关系要求得过于严格。当没有指定的可视树关系时,也不应该抛出异常。而是应该检测,如果在有指定的元素的情况下,才表现出具体的行为,否则将没有行为。

 

  • 关于 OnApplyTemplate 与 Measure 的关系。

    ApplyTemplate 是应用模板的意思,所以我们一般在 OnApplyTemplate 中查找应用模板后的指定的可视元素。那么,可以写在别的地方吗?

    系统本身对 ApplyTemplate 方法的调用,其实是放在 Measure 过程中的。查看源码,发现在 FrameworkElement.MeasureCore 方法实现中,第一行就是调用 ApplyTemplate。也就是说,如果还没有应用模板的元素,会在第一次测量之初,会应用它对应的模板。而 ApplyTemplate 方法内部则会通过一个 bool 类型的状态值来检测是否已经应用过模板,以防止重入。

    OnApplyTemplate 只会在 ApplyTemplate 方法第一次执行时被调用。我们经常会重写控件的这个方法,在其中查找指定的可视树元素。其本质,与在 MeasureOverride 方法中以防止重入的方式来编写这些代码是一致的。

时间: 2024-10-30 14:56:11

OEA 中 WPF 树型表格整体重构的相关文章

OEA 中 WPF 树型表格虚拟化设计方案

    最近用 OEA 做的仓库管理系统中,许多界面的都需要使用表格控件来显示数据.一是这些表格的列非常多,有的甚至达到了 200 列,而且一个模块的界面中可能同时显示好几个表格.这导致界面的速度比较慢,特别是较多数据需要展现时.经检测,表现虽然表格的行已经做了虚拟化,但是由于列非常多,最终还是造成可视树中的元素过多,而导致界面布局代码运行过慢.假设只有 30 行,一个单元格仅生成 5 个可视元素,200 列的单元格都会产生 3W 个可视元素,而布局系统的 Measure 方法需要对可视树中的每

技术总结:自动扩张WPF树型表格列宽

问题描述     今天测试人员提了一个易用性的BUG,主要是说系统目前使用的树型控件不支持自动扩张列的宽度.其实客户那边已经对这个问题提了多次,不过由于对WPF只是入门级,所以一直都没改.这两天项目比较闲,就花了些时间把这个问题改了.原问题如下:   图1 问题描述   背景     树型控件在GIX4系统中已经被大量使用.这个控件是一年前其它同事在网上搜索到,再引入的.     一开始的时候,要解决这个问题,想到的最直接的方案是这样的:找到第一列中的Expander控件(加号:),然后监听它的

oracle中的树型查询

oracle         在项目开发中树型结构是经常被使用的,通常情况下我们对查询的结果集使用一些免费的js文件就能达到很好的显示效果,比如         但是有时候我们需要对下拉列表也提供树型结构(比如论坛的板块跳转),这个时候我们就可以使用oracle9i以上版本提供的CONNECT BY 子句了.  SELECT kindid,kindname,fatherid      FROM kn_kind      CONNECT BY PRIOR kindid=fatherid     

树型结构在ASP中的简单解决

解决|树型结构 树型结构在我们应用程序中还是很常见的,比如文件目录,BBS,权限设置,部门设置等.这些数 据信息都采用层次型结构,而在我们现在的关系型数据库中很难清淅表达.那么要在程序中遇到树型 结构问题该如何处理呢? 最近笔者通过一个ASP权限管理的程序轻松解决了一这问题,现在将其整理出来以飨读者. 首先,要将层次型数据模型转化为关系型数据模型.也就是说如何在我们的ACCESS,SQL SERVER ,ORACLE等关系型数据库中设计这个数据结构. 拿个实例来讲吧,譬如下面一个数据: 文档管理

Rafy 领域实体框架 - 树型实体功能(自关联表)

在 Rafy 领域实体框架中,对自关联的实体结构做了特殊的处理,下面对这一功能进行讲解.   场景 在开发数据库应用程序时,往往会遇到自关联表的场景.例如,分类信息.组织架构中的部门.文件夹信息等,都是不限制层级的.如下图中操作系统的文件夹: 在开发这类程序时,往往是设计一张表,表中的一个可空的外键直接引用这张表本身.对应的实体如下图: 而针对这样的场景,许多ORM框架都不做默认的处理,开发者往往每次都要做重复的工作:建立类似结构的表,编写关系处理代码,编写查询代码--而这种场景经常会出现,所以

OEA中的AutoUI重构(2)- 评审会议前的总体设计

 本次重构主要是针对OEA框架中的AutoUI部分.这个任务在月初时计划在一个月内完成,包括问题分析.设计新的结构.编写设计文档.开展设计评审.代码实现.提交评审.本系列文章用于记录整个过程中的关键项.     本篇文章主要记录了在设计评审会议前我所做的工作,包括: 历史状况分析 重构目标 逻辑设计方案 结构关系图   历史状况分析     由于是重构,所以我们需要弄清楚当前的问题是哪些,历史代码的结构,为什么这样的结构会造成这些问题.历史代码的结构原来已经写过相关的文章:<OpenExpres

一道 SQL 题 ... (关于树型结构的在关系表中的存储及其应用处理)

树型结构 相关讨论连接:http://expert.csdn.net/Expert/TopicView1.asp?id=1477009原题:表:Tree (ID [Integer],ParentID [Integer],Remark [varchar]) INSERT INTO Tree (ID,ParentID)        SELECT 1,0    UNION ALL       SELECT 2,1    UNION ALL       SELECT 3,1    UNION ALL 

.NET中C#实现C/S架构下的TREEVIEW只需要输入表名,父ID,节点ID,节点名就可以得到树型结构

treeview|架构|树型结构 调用时如下:         /// <param name="newTreeView">树型控件名称</param>        /// <param name="TreeViewName">一层的功能名称</param>        /// <param name="TableName">数据库中的表名</param>       

VC程序中树型控件节点拖动的完美实现

Visual C++中提供的MFC类CtreeCtrl(树型控件)用来显示具有一定层次结构的数据项时 方便.直观,所以它已经被广泛地应用在各种软件中,如资源管理器中的磁盘目录就用的是 树型控件,我们在编程中也会经常用到这个控件,但是这个控件也有缺陷,那就是它并不直 接支持拖动节点等高级特性,这使得程序员在编程时使用它受到了很大限制,同时又给软件 用户带来了一些不便.为此,本实例通过从 CTreeCtrl 中派生了一个类 CXTreeCtrl ,实现 树型控件中节点的拖动.这个类具有如下的功能:⑴