1、基本想法
(1)和树节点显示相关的数据载入对象模型,这些数据一部分在CTree的节点CNode里,一部分在CDetailList里的Detail结构里,与相应的数据表的设计相对应。
(2)当用户通过用户界面改动数据的时候,一杆子捅到底,直接到数据库里改,改完再到界面上显示出来。
(3)类之间消息的传递:通过一个全局的CMsgMate变量进行。
解释:
(1)用类还是用结构,前面有个讨论。最后决定CNode用类,因为我看到的开源的扩充树类的代码都用了自己的Node类,我想这些前辈可能都是考虑过的,应该有它的道理;Detail用结构,这主要是心痒想试试zhao支的招,增加点见识。
(2)数据的变动没有像zhao建议的那样用内存标记。有2个原因,1则我觉得变动时我一般只操作有限的几个节点(常常是1个),所以这时候性能的差异估计可忽略;2则我还有细节子窗体用于显示细节相关的数据,有些数据在树模型里,有些不在,到时候一部分从内存取、一部分从树模型里取,还要保证数据一致性,感觉逻辑太复杂,维护性差。
要注意的:
(1)尽量通过消息的传递而避免直接调用类的公用方法来解耦;
(2)在实现数据变动相关的方法时注意封装,注意考虑日后也许会想转到zhao的方法时的可替换性。
2、Classes
(1)CDetailList,缩写dlst。一个CDetailList对象对应一个细节表。它包含一个NODE_DETAIL类型的数组,每一个NODE_DETAIL结构对应一条细节记录。CDetailList对象初始化时会加载相应的细节表的所有数据。
(2)CTreeData,缩写trd。一个CTreeData对象对应一个节点表。它包含一个CNodeData对象的集合。CTreeData对象初始化时会加载相应的节点表的所有数据。
(3)CNodeData,缩写cnd。一个CNodeData对象对应一条节点记录。
(4)CTreeCtl,缩写trc。一个CTreeCtl对象对应一个treeview控件。它知道自己是属于哪个树窗,也知道自己表现的是哪个节点表的数据。
(5)CTreeWnd,缩写twd。 一个CTreeWnd对象对应一个树窗。所谓树窗,是指含有树控件的窗体。它含有用于显示细节的子窗体和listview,它懂得在树控件上的当前节点发生变化时,相应地调整自己的显示。它还负责一些罗嗦的初始化工作,像放置控件的位置、设置imagelist、设置event procedure、设置快捷菜单等。
(6)CDtlSfr:缩写cdf。一个CDtlSfr对象对应一个子窗体。
全局有个两个集合变量,一个是CDetailList的集合gcolDlsts,一个是CTreeData的集合gcolTrds。这也就是zhao一直以来所建议的“把数据放在内存里”的具体实现。大家要用数据的时候都去访问这两个集合。
除了上面的几个主要的类外,还有如下辅助类和模块:
(1)CMsgMate:用于发送自定义消息
(2)CTabStrip
(3)ChkCnd:这是一个测试类,专用于测试node相关数据的。
(4)basTree:树相关的全局函数。前面那两个集合变量,还有一些常数都在这里。
(5)basTest:测试相关的。生成随机数的一系列函数等。
(6)basAPI:API相关的一些声明。
3、数据的装载和卸载
细节表的数据是一次性的loaddetails,这需要每个窗体在load时都调用这个函数。这个函数会判断,如果details已经load就不重复load。当然也可以强制reload,只要把参数blnReload设为真就行。节点表的数据是用哪个load哪个,用GetTrd函数。这个函数也是会判断的,如果已经load就不重复load。
Unload的话,由CTreeWnd负责,如果系统内只剩一个CTreeWnd实例,那么它unload的时候就负责把所有detail和node数据卸载。
4、类间消息的传递
如上图,gcmm是一个全局的CMsgMate对象。所有需要收发消息的类都含有一个指针指向gcmm,这样它们就可以互通有无了。比如,当一个CTreeData对象的数据发生变化时,它就通过gcmm发一个消息,说自己变了。剩下的是CTreeCtl响应它还是CTreeWnd响应它就都没关系,爱咋咋,反正我发了通知了。