回 帖 发 新 帖 刷新版面

主题:[讨论]控件【浅谈自制MFC控件】

摘自:----www.evget.com----慧都控件网,中国最大的控件代理商与技术支持商 

浅谈自制MFC控件

  首先说明。既然是原创,很多东西就是自己想出来了。虽然参考过一些网站和书籍,但不能保证全部正确,如果哪位读了在下的文章之后能够指出其中的问题,在下感激不尽。特设金币500报答将帮助我找出错误的网友。对我这个穷人已经是很大的数目了。

  首先说为什么使用控件,可能这是一句废话。只要你用MFC开一个窗口几乎就一定用到控件,它帮助你显示信息,处理数据,传递消息。

  平常大家见的到的控件无非是以下三类:1是VC自带的。VC的工具栏里包含了大部份,如果嫌不够,打开MSDN看CObject的Hierarchy Chart。CWnd下面的Control类就是你用的上的全部了。还不够用或者这些控件功能过于简单就得寻找其它途经了。这些控件用我的感觉来说就是作的很好,有的功能很少,是因为不想多作,以限制了适用者将来扩展功能的机会。同时十分结实。至少知道的问题以及解决方法四处都能查的到。是作一般界面的首选。

  十分可惜首选能选的东西不多。很多功能不够强大。第二类方法就是在网上找别人的自制控件。这类控件很多都是2000年左右写出来的,经过很多人测试的。同时大部份控件都是基于一个已有的MFC控件改编而来。代码不很复杂。是比较可靠同时比较容易维护的。

  第三类就是其它软件公司出品的要钱的控件。很多是以ActiveX或者library的形式销售的。如果是ActiveX的话可能还是别的语言写的。公开代码的不多。同时这些公司规模都不很大(至少和微软比),也就是说测试的深度不一定够,所以如果抱着全部信任去用而到要交活前一瞬间这些控件忽然发现它没听说的那么好用了不要惊讶。不是说这些控件不好,而是用的时候要小心,先检验其可靠性。

  在以上三类控件的基础上你可以根据自己的需要增加控件的功能以满足自己的特殊需要。这就是本文要写的自制控件了。当你看网上别人写的控件的时候一般会有一些说明为什么这么设计。通过这些设计思想你可以了解自己怎么下手。下面简要的说两个小例子:

  1。CListCtrl几乎是用的最广泛的控件了,但它提供的基本功能很少。如果我希望我的列表在用户每次点击一列的头的时候自动按这列排序,或者右击列表时弹出一个菜单。我可以作一个自制的控件,是CListCtrl控件derive出来的。在这个控件中管理LVN_COLUMNCLICK和ON_WM_CONTEXTMENU来实现这两个功能。这几乎是最简单的自制控件了。

  2。稍微复杂一点的。Tootip。MFC提供tootip,当你的鼠标从一个控件移到另一个控件,并且停留500毫秒的以后。如果实现这个控件已经被加了tooltip那么一个小tip的黄色窗口就会跳出来显示一下tip的一行字,你的鼠标再移动一下它就没了。这个功能好像有点太简单了。首先我希望我的tip不只一行,同时即使同一个控件,用户把鼠标放在不同的地方我也希望有不同的tip。例如我的控件是一张地图,鼠标放在北京上tip就显示“政治中心”,放在上海上就显示“经济中心”。这个tip怎么作呢?首先window本身的tool tip类只能显示一行,这个没法解决,所以自制的控件不该用CToolTipCtrl作base class。可以选择直接用CWnd。在当前的Dialog的PreTranslateMessage中加上判断:如果当前的message是鼠标的动作,就叫这个控件处理。好控件接到这个消息。进一步判断:如果是鼠标移动的消息,说明已经不在原来的位置上了,hide这个小CWnd,记下当前鼠标器的位置,然后SetTime(500)。当然如果此前已经有Timer了需要把那个timer杀掉。这个message就处理完了。以后有两种可能性:1。没到500毫秒,鼠标器又移动了,这个函数就又被叫了一次,前面的信息被冲掉,等待新的信息。2。500毫秒内鼠标没有移动,于是show当前的CWnd,有什么text都填进去,想写几行就几行。这就是这个多行动态的ToolTip的原理了。以后可以发上来给大家看看。

  以上就是自制MFC控件的基本思想。如果你一上来对自制控件还没有什么概念,不妨下载几个别人写好的先读一读,同时要对MFC基本控件有一定的了解。

  再说说哪些功能应该作到自制控件中。VC不把所有功能都作进控件就是不希望一些太特殊的行为限制了控件应用的广泛性。自制控件的时候也要切忌这一点。如果你在工作中写了这么一个控件,最好你们单位的所有人都用这个控件,以保持一个产品的风格统一。例如前面所说的ListCtrl的排序和右击菜单,我个人认为就十分不适合放到自制控件中,也就是说这是个很坏的例子。为什么呢?如果一个List不希望一点列头就排序,或者希望右击时跳出不同的菜单。用这个控件就没办法disable自己的功能了。MFC消息传递的机制是这样的。如果你在控件中什么都不加,让他的parent,也就是Dialog或者FormView之类的替它处理,那么这个信息会传给Dialog或者FormView。有他们的LVN_COLUMNCLICK和ON_WM_CONTEXTMENU来找到对应的控件处理。如果控件自己处理了这些信息,那么它们的parent就接不到这个信息了。也就没有办法轻易的改回来,让已经处理的结果复原了。总而言之,处理的信息应该是普遍适用于各种情况的。如果只是个特殊情况的话,这个信息最好由控件的parent来处理。

  最后谈一下控件和其parent的信息交换。这几乎是作复杂一点控件时免不了的。控件完成自己当前要处理阿信息之后如何让其父亲窗口处理剩下它该处理的部份呢?
方法1是用CALLBACK函数。书上说这是比较建议的方法,因为这比起后面讲的Post message而言传递的参数都有类型,也就是说信息更准确。CALLBACK function在MFC中用的很多,可以随便找个差不多的函数参考一下其格式,例如SetTimer()。
方法2就是post message,最好不要用send message,如果真的需要发出的信息立刻就受到结果也有别的解决方法。post message 的劣势就是传递的参数没有类型,需要在WPARAM wParam, LPARAM lParam中自行提取信息。不过我倒觉得post message没那么差,或者说因为整个windows都是这么运作的,你的控件不post messageVC其它的地方post message 的地方多的是。这一个地方提高了可靠性没什么意义。干脆都post,用的时候小心点就是了。 post message的好处是程序写的很干净。控件把消息post 出去了就完成任务了。写parent class的人爱接不接,不需要处理的时候可以不管这个message。没必要一定预备这一个接受函数等着处理。这是我比较喜欢的方法。
方法3比较土,干脆再建立一个class 里面写一堆空函数,让parent class来从它这里继承。
例如我的控件叫CMyList,需要管理列表的scroll。处理完之后通知其parent我处理完了。
那么就再建立一个class 叫 CMyListHolder. 里面就一个空函数:
class CMyListHolder
{
public:
    virtual void ListScrollDone(){};
}

  我用到这个list的时候我的Dialog就从这个CMyListHolder继承

  class CMyDialog : public CDialog, public CMyListHolder。
在CMyList中处理完OnScroll的信息后去call m_pParentWnd->ListScrollDone()。
在CMyDialog中如果愿意处理点什么就把自己的这个override函数填上就是了。这方法整体而言不是很干净,但变量还算有类型。是个届于方法1与方法2之间的方法。

  说了这么多,还没有实例,过一阵贴一些控件的实例具体说明一下。
欢迎大家多多指教。

QQ:903506412

回复列表 (共1个回复)

沙发


感谢楼主这么辛苦发的好贴。这样的好东西大家都来项啊!

我来回复

您尚未登录,请登录后再回复。点此登录或注册