翻译自:http://www.codeproject.com/wtl/docviewwtl.asp
Download demo project - 157 Kb Download source files - 7 Kb 我不知道你学习WTL的感受,但我感觉优如 literally pumped 。我不建议大家立即抛弃MFC选择WTL,因为任何人在选择一种类库工具的时候都有他自己的考虑。
大家知道WTL没有实现document-view 体系,即DVA,直到现在,还有很多人在批评这一点。虽然如此作为MFC重要的地位之一的DVA技术我也没有发现它有多少灵活性和通用性,况且没有实现DVA的WTL并没有破坏视觉表示与其业务逻辑分离的想法。因此我将把注意力关注在DVA上面,但本文讲述过程稍有不同,首先也是最重要的,我们要做一些比从应用程序中分离UI更重要的工作,然后我们再在这些工作的基础之上实现Document-View的分离。
DVA技术并不算是一门新技术,类似的如著名的publish-subscribe, event sources and event listeners, connection points, delegates等技术,所有这些技术都有一个统一的编程范式:对象A的动作引起对象B做出相应的反应。你可能会认为这两个对象之间存在着连接,这让人想到Connectable Objects,OK。这个名字不是我的发明,Connectable Objects是COM借用而来。我不想让大家与这些新术语混淆起来,我们将使用更加熟悉的名字:connection points, sink objects一些属于ATL家族的名称. 这里 有这些术语更详细的描述.
对于实现这个库最优先考虑的因素是
1.强类型支持
这意味着,不使用 eventCode, notificationCode, LPARAM 参数表示事件,而直接使用事件接口。
2.对已存在部分的代码影响最小
尽力使用"to inherit a bananas get the whole gorilla" 方法。
3.容易理解和使用
使其成为c++语言的一种惯用法。
4.高效率
5.更多的模仿ATL编码方式,以可以使用ATL 的RTTI 和多继承等好处。我认为MFC类库在设计的时候没有考虑这样的特性。这不是很重要,但我觉得应该提一下。
我们没有考虑多线程串行化访问,所以如果你的 sink object 是运行在多个线程的可识别的对象的话,请特别注意这一点。 不好意思,为了强类型支持和高效率的考虑,我们值得这么做。如果你需要用到多线程你可以自己实现访问的串行化保护,这样对于不需要多线程的应用可以保持代码量简洁和最小化。
一个简单的例子 你可能觉得我有点罗唢,那好的,我们切入正题,给大家简洁明了的剖析一个使用连接点的简单的例子。
在遇到两个对象需要连接的情况你是如何做的?
在这儿,我们需要三个类:
1.subject (主题对象自己产生事件,是一个事件源)
2.observer (或者叫sink object,这个对象用于收到上面第一个介绍的对象的通知)
3.sink interface (observers必须实现这个接口,以方便subject对象调用)
以下就是大致的代码:
class CSinkInterface {
virtual void onEvent1(int p1, LPCTSTR p2) = 0;
.... // other events
};
class CObserver : public CSinkInterface {
COSinkOwner m_sinkOwner;
virtual void onEvent1(int p1, LPCTSTR p2)
{
... // react somehow
}
};
class CSubject : public COConnectionPointContainer
{
...
CO_BEGIN_CONNECTIONPOINTS(CSubject)
CO_CONNECTIONPOINT(CSinkInterface)
CO_END_CONNECTIONPOINTS()
void method1()
{
...
CO_LOOP(CSinkInterface)
eventSink->onEvent1(p1, p2);
CO_ENDLOOP
}
};
最后我们如何连接subject和observe对象呢?请看:
CSubject subject; CObserver observer;
COConnectionPointImpl *cp;
if(subject.coFindConnectionPoint(&cp))
cp->Advise(&observer, &observer.m_sinkOwner);
为了与类库名字有所区别,我有意使用了不同于类库的类名(CSubject and CObserver) 。而且为了避免与COM名字冲突,我在类名前使用CO前缀代替使用C作为类的前缀,在可重载的方法名前用co前缀以避免与你使用的方法名冲突。
简短的描述 上面是最简单的示例,使用方法几乎与所有人都知道 COM 的连接点类似,唯一例外的用到了一个新术语 COSinkOwner。 这个类负责当sink对象析构的时候关闭连接。另一方面,如果事件源消亡这个连接被隐藏在CO_CONNECTIONPOINT下面的对象破坏。
我们也可以让CObserver直接从 COSinkOwner 继承,而不是组合一个COSinkOwner 类型的成员m_sinkOwner,这样做可以让我们有重载一对方法(确切的说就是coOnAdvised 和coOnUnadvised 方法),也就是当我们连接到另外的对象的时候CObserver可以可以做点什么,CSubject 类也可以继承coOnConnection 和 coOnDisconnection 方法以在CSubject类中在连接和解除连接的时候执行一个入口。
将sink对象和sink owner 分开是非常重要,比方我们可以得到一个sink owner以控制sink 对象。它还可以帮助我们使用类似Java' inner 类以解决多继承下的名称冲突。
仅仅需要记住一件事情,如果sink owner在sink 对象之前消亡,你要特别注意别忘记自己调用Unadvise .要比然...对了 Dr' Watson takes over the conversation.
我想,有必要提起一个对象可以暴露多个连接点的情形,这样可以连接到很多事件源,做法就象COM 连接点一样。
Q&A 既然与COM如此相象,为什么我们不直接使用 COM? 因为不是每个类适合成为COM对象的类 ,也不是每个sink 接口需要申明为COM类型库,我想connectable objects的想法本身很有价值,因为它能帮助我们组合更紧密的连接对,也因此更容易复用。 Document-View? 使用connectable objects在这里还有许多东西可以写。UI分离是如此显而易见的想法,并且被广泛使用,本文的第二部分将专门介绍它。我想这个主题是如此重要和复杂,值得分两部分进行介绍。
有一件事情可以看出,它与 MFC 的doc-view越来越不一样。 如何与我联系 如果你有任何的问题和建议,可以与我联系,我的ICQ号码是 11411966 (在ICQ加为好友的请求认证中只需填写 "co" 两个字母,因为我不接收 不在好友列表的消息),或者通过 e-mail 联系我。 更新更详细查看 这个 连接。