浅谈自制MFC控件
首先说明。既然是原创,很多东西就是自己想出来了。虽然参考过一些网站和书籍,但不能保证全部正确,如果哪位读了在下的文章之后能够指出其中的问题,在下感激不尽。
首先说为什么使用控件。只要你用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怎么作呢?首先wind
ow本身的tool tip类只能显示一行,这个没法解决,所以自制的控件不该用CToolTipCtrl作base class。可以选择直接用CWnd。在当前的Dialog的PreTranslateMessage中加上判断:如果当前的message是鼠标的动作,就叫这个控件处理。好控件接到这个消息。进一步判断:如果是鼠标移动的消息,说明已经不在原来的位置上了,hide这个小CWnd,记下当前鼠标器的位置,然后SetTimer(500)。当然如果此前已经有Timer了需要把那个timer杀掉。这个message就处理完了。以后有两种可能性:1。没到500毫秒,鼠标器又移动了,这个函数就又被叫了一次,前面的信息被冲掉,等待新的信息。2。500毫秒内鼠标没有移动,于是show当前的CWnd,有什么text都填进去,想写几行就几行。这就是这个多行动态的ToolTip的原理了。以后可以发上来给大家看看。
以上就是自制MFC控件的基本思想。如果你一上来对自制控件还没有什么概念,不妨下载几个别人写好的先读一读,同时要对MFC基本控件有一定的了解。
再说说哪些功能应该作到自制控件中。VC不把所有功能都作进控件就是不希望一些太特殊的行为限制了控件应用的广泛性。自制控件的时候也要切忌这一点。如果你在工作中写了这么一个控件,最好你们单位的所有人都用这个控件,以保持一个产品的风格统一。例如前面所说的ListCtrl的排序和右击菜单,我个人认为就十分不适合放到自制控件中,也就是说这是个很坏的例子。为什么呢?如果一个List不希望一点列头就排序,或者希望右击时跳出不同的菜单。用这个控件就没办法disable自己的功能了。MFC消息传递的机制是这样的。如果你在控件中什么都不加,让他的parent,也就是Dialog或者F
ormView之类的替它处理,那么这个信息会传给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之间的方法。
最后介绍几个自制控件中常用的属性和函数。
1。owner drawactivex 控件
很多控件需要自制的原因就是要改变MFC控件的外观。MFC对答部份控件提供了owner draw功能,就是让你自己对控件区域进行重新规划。List Box, List Control,甚至menu, button都可以owner draw。以List Box为例。在resource里加入list box之后,在property中选owner draw fixed。就可以开始对自己的list box 进行加工了。
你可以重载CListBox::MeasureItem来改变每个Item的高度。重载CListBox::DrawItem来改变每个Item的具体样式,例如在Item显示的矩形内,你可以画CheckBox,Dropdown List等等。
一般的用于List Box, List Control或 Tree Control中的Item有两部份数据一个是显示用的数据,可以用SetItemText来改变。另一个不显示的可以用SetItemData来改变。SetItemText的东西一般不能改变,除非你想在DrawItem的时候把Item改的面目全非。SetItemData里面的数据可以用来存储关于这个Ite
m属性的信息,一般是想写什么就写什么。不过如果你作的控件是给很多人用的我还是建议你不要用SetItemData来存你想存的信息,
而把这个位置留给最终用你控件写程序的人。自己另起一块地方存你的信息。
2。PreSubclassWindow
这是你的控件在系统中被注册时候被调用的函数。有点想CDialog里面的OnInitDialog。如果你对你的控件有什么预先的设定的话可以在这里完成。
3。MessageMap
这里就是所有要处理的消息了。前面已经说过哪些消息建议在控件里处理,那些建议在parent里面处理,这里就不多说了。
说了这么多,应该举几个实例了。在论坛的精华里有一个我写的CListCtrl的简单例子,大家不妨参考一下。
欢迎大家多多指教。
版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系QQ:729038198,我们将在24小时内删除。
发表评论