前言:从这学期开始就一直在学习自绘控件(mfc),目标是做出一款播放器界面,主要是为了打好基础,因为我基础实在是很烂....说说我自己心得体会以及自绘控件的方法吧,算是吐槽吧,说的不对和不全的地方,或者有更好的方法,请不吝赐教。
我的机器环境是:Windows7旗舰版 Service Pack 1,Visual studio 2005
1).重绘某个控件时,强烈推荐使用子类化方法,比如想自绘Button控件, 首先添加自己的类CMYButton 继承自 CButton ,声明一个CMYButton 对象,然后使用 SubclassDlgItem(UINT nID, CWnd* pParent ); // 第一个参数表示控件ID,第二个参数表示指向父窗口对象指针,一般用this表示(如果不想用SubclassDlgItem。那么可以使用CMYButton自身提供的Create方法 动态创建一个Button),这样子就可以在自己类中添加重写WindowProc()这个窗口过程函数了,非常,非常,重要 ,其他控件自绘都参考这一条.
2).我入手的第一个控件是 Button,我终于知道我的基础有多烂,很多基本的函数如GetDlgItem() , SubclassDlgItem() 都不知道,查资料,看源码 ,费了不少时间才基本完成Button的自绘,另外自绘
的按钮默认情况下是不能响应键盘按下Enter的,需要额外做一些处理。(关键词:BS_OWNERDRAW ,DrawItem),(在后期我仿造qq登陆的Button加了个效果,Hover和Leave时是渐变的,只在设置
对话框里面的Button使用了)
3).然后是 RadioButton ,CheckBox 其实和Button异曲同工的,推荐了解3个API函数CheckRadioButton(),SetCheck(),GetCheck().
4).另外对于不规则按钮实现需要掌握 SetWindowRgn(),CombineRgn(),SelectClipRgn() 3个API函数,其他不规则窗口,控件也可以参考这个方法。
5).然后是Edit控件自绘,不算是完全自绘,只重绘了非客户区(如果没特殊需要也没必要重绘客户区),和改变背景颜色,改变字体,不过后期我加了个效果,鼠标在Edit上和离开Edit时边框是渐变的(关
键词:CtlColor,WM_NCPAINT),RichEdit也可以用这个方法
6).然后是ToolTip(气泡提示控件),微软提供了NM_CUSTOMDRAW这个通告消息,以WM_NOTIFY形式发送,可以用MFC类向导添加到自己的派生类中,不过我推荐重写OnPaint函数,完全自绘(难点:
需要根据文本内容计算出控件的大小,显示位置等)
在后期我实现了,淡入,点击/超时 淡出的效果(需要映射TTN_POP 和TTN_SHOW两个通告消息),不过挪开淡出效果没能实现,求指导。
7).然后是Sliderctrl, 微软提供了NM_CUSTOMDRAW这个通告消息,以WM_NOTIFY形式发送,可以用MFC类向导添加到自己的派生类中,前期我是用的这种方法,不过后期发现这种方法局限性很大,
推荐重写OnPaint函数,完全自绘(关键点:在PreSubclassWindow 里面把 Thumb(拇指按钮),Channel(凹槽),以及整个控件大小保存起来,以便在OnPaint里面绘制)
8).然后是Staic控件,这个比较简单,重写OnPaint函数 画上文本,把DC设为透明模式就行了,有人会说直接在CtlColor直接SetBkMode(TRANSPARENT)就行了,不用在OnPaint处理,但这是有个问
题的, 如果要求文本一直变化,旧的文本没有擦除,新设置的文本又盖上了。所以根据这个控件的用途,自己选择适合的方法吧。
9).然后是Menu,这个较难,严格来说Menu 并不算控件,他是派生自CObject类的,微软提供了MeasureItem,DrawItem两个虚函数类供自绘 ,MeasureItem作用是计算出菜单的高度和宽度,系统
会自动根据文本内容最长那项来作为Menu的宽度。DrawItem作用顾名思义就是画了,但是有个致命的问题,自绘出来的Menu 有个系统默认的边框,十分邪恶和难看,(ModifyStyle和
SetWindowLong去不掉边界的)这时到自己派生的CMYMenu里面 发现微软只给咱们提供了仅仅5个虚函数,没有提供WindowProc()这个窗口过程函数,这不是坑爹嘛.......这时一般做法都是派生自
CWnd 自己实现菜单的功能.不过查了下资料任然可以自绘的:需要使用钩子 替换菜单的窗口过程,在WM_CREATE时 去掉边界Stytle 有兴趣的朋友可以Google一下。(难点:替换菜单窗口过程)
10). 然后是Combobox控件,这个较难, 微软提供了CompareItem,DeleteItem,DrawItem,MeasureItem 4个虚函数供自绘。我只用了后2个,(如果只加了 CBS_SORT 必须重写CompareItem这个
函数,除非使用了CBS_HASSTRINGS | CBS_SORT就可以不重写CompareItem()), 别以为这样子就完了,运行后,打开Combobox 显示的 List 有系统默认的边框!!!ModifyStyle和SetWindowLong
去不掉边界.老规矩查资料去,不看不知道,一看吓一跳,Combobox 是由3个控件组合成成的(难怪叫组合框),分别是Edit,Listbox,和combo本身(除去Edit ,Listbox剩下那部分),当时我就震惊了,迷茫
了! 这时需要添加OnCtlColor这个函数,在里面 使用SubclassWindow()这个API函数子类化 ListBox 和 Edit(在这之前 你还需要准备自绘好的 ListBox控件 和Edit控件)Combobox 有3种样式
CBS_SIMPLE, CBS_DROPDOWNLIST,CBS_DROPDOWN,第一种不说了不常用,第二种是不能输入只能点击选择,第三种可以输入可点击选择. 我的程序里面使用的是CBS_DROPDOWNLIST样式
11).Combobox 在 Windows7 下疑惑:关闭滑动打开组合框特效 ,自绘Combobox是可以去掉边界并进行窗口剪切的(圆角矩形) , 如果是 开启滑动打开组合框特效,系统会加上边框, 剪切的圆角又
变成直角了, 跟踪调试发现是在WM_WINDOWPOSCHANGING 消息里面搞的鬼 ,有兴趣的朋友可以对比看看,暂时为找到解决方案。。。
开启/关闭 滑动打开组合框特效 :计算机-右键属性-高级系统设置-高级-性能设置-视觉效果
12).然后是 TabCtrl ,微软提供了DrawItem,MeasureItem 2 个虚函数供自绘,需要加 TCS_OWNERDRAWFIXED这个Stytle,表明这个控件需要自绘,不过我没有用这种方式,我直接重写了OnPaint函数
完全自绘(难点:需要自己计算每个标签大小,位置,以及与之绑定的Dialog显示位置)
13).最后是窗体框架绘制(非客户区),这个较难,看了很多例子源码,也花了不少时间,WM_MOVE,WM_PAINT ,WM_NCPAINT,WM_NCACTIVATE,这4个消息自绘成功的关键 , 在绘制时候还需要
计算出边框/标题栏的大小和位置(Win7 和Xp 下 GetSystemMetrics()返回值是不同的)。
// 给出框架绘制不闪烁的关键代码 ,完全原创。
if(message == WM_NCACTIVATE && !wParam) // wParam=0, deactive
{
return 1; // 必须返回1,处理默认消息(如果不返回1,一切弹出的窗口(模态,非模态)不能点击)
}
if(message == WM_NCACTIVATE && wParam) // wParam =1, active
{
return 0; // 这个随便返回(0和1都行)
}
if(message==WM_NCPAINT)
{
return 0; // 阻止默认框架绘制(An application returns zero if it processes this message 摘自MSDN)
}
这种方式是保留了边界和标题栏,其实就是盖住了原来的画上自己的,只要不闪烁就是成功的。当然也可以去掉系统默认的边界 和 标题栏,在客户区算出一个边界和标题栏,处理一些消息,能实现更好
的框架自绘,做出更漂亮的界面。
14)可能是我这个人比较蛋疼吧,想着既然做了个播放器界面为什么不给他实现一个播放的功能呢,微软提供了 MCI—媒体控制接口,自己封装了一个播放类,实现了一些播放基本功能。这样子第一个
版本就算完成了吧。
---测试环境: 6台Win7 和 2台Xp
---界面测试:Win7 和 Xp 运行均正常(有个缺陷见第11条)
---播放测试: 我的电脑上可以 播放rmvb,RM ,AVI,MP4,WMV,FLV ,部分测试Win7电脑6个格式全部能播放,但部分测试的Win7电脑只能播放 AVI 和MP4,WMV格式(为什么?求解答...).
---另外WMV格式增加播放速度和减少播放速度,貌似是不行的。
---在XP系统 下能打开视频文件但只能听见声音看不见图像,求解答?
这结果确实比较蛋疼,大家可以测试下看看界面是否正常,播放功能是否正常
15)鉴于MCI版本播放测试不是很令人满意,我又再一次蛋疼了,因为一个前辈说让我用Activex 控件试试。好吧,花了2天时间,研究了下Activex 控件, 用OleView研究了Aplayer这个控件,
APlayer_001.dll 这个DLL文件就是Aplayer Activex控件(Activex 控件使用前必须先注册,如果只是在代码里面注册了APlayer_001.dll,程序可以运行但是播放不了文件,因为在播放文件的时候
Aplayer控件 还会根据播放文件类型 再加载 一些DLL文件和AX文件,那些文件加起来有80多M,坑爹啊这是....)其实Aplaer 是迅雷看看播放器的一个组件, 如果安装了迅雷的话,在 C:\
\Program Files\\Common Files\\Thunder Network 可以找到 Aplaer 这个文件夹,如果没有安装迅雷或者没有Aplayer这个组件程序是不能运行的。
备注:这个APlayer本身有个缺陷,在播放 Rmvb和mkv文件时,点击定位不准确。对于大的文件,很难发现这个缺陷,可以找个 一两分钟的短视频文件用迅雷看看打开 点击定位试试,缺陷非常明显。
由于Aplayer播放过程 和MCI不太一样,所以花了点时间做了第二个版本。(据说这个Aplaer 组件是国外一个组织开发的,貌似迅雷买了版权的)
---测试环境:6台Win7 和 2台Xp
---界面测试:Win7 和 Xp 运行均正常(有个缺陷见第11条)
---播放测试:Win7上可以播放RMVB,RM, AVI,MP4,WMV,FLV ,MKV, MP3, WMA, WAV
---在XP系统下 打开文件任然只能听见声音看不见图像,对于XP系统这个播放问题,还未找到解决方案,求指导(难道是因为我注册的是Win7的APlayer?)。
---另外程序有个缺陷 :在第一次 点击Aplayer控件的时候会缩小,调试发现根本没有进入 WM_LBUTTONDOWN,直接 WM_WINDOWPOSCHANGING,WM_WINDOWPOSCHANGED,WM_SIZE
,不知道这个消息从哪里发过来的....(我用了个不是很好的方法解决了,既然是在第一次点击的时候才会缩小,我在PreSubclassWindow里面 :PostMessage(WM_LBUTTONDOWN,MK_LBUTTON,
(LPARAM)&UserDown);PostMessage(WM_LBUTTONUP,MK_LBUTTON,(LPARAM)&UserDown);)这2个消息,貌似没再出现了缩小情况了,但昨天我运行的时候又出现这个问题了,出现的概率很低....
无语啊)
16)
程序的测试全部由自己完成,很多功能没来得及测试,所以程序可能会出现这样那样的问题,一个人力不从心啊,希望拍砖温柔点啊。
程序热键,做的不好,不是全局和后台的,必须窗口获得焦点才能响应.
在后期增加了托盘功能。
增加了播放列表功能,最多支持10个文件,大于10个覆盖第10个,双击列表中的文件名,就可以播放这个文件。不过播放列表我没有单独做个窗口是放在设置对话框第3个标签的。
应同学的强烈要求增加了拖拽打开文件功能(我过滤了一些文件扩展名,不是每个文件类型拖拽都有效,mci版本和Activex版本支持的格式不同 ,过滤情况也不一样)
程序最初叫IKAN Player 但是发现 PPLive 已经用了这个名字, 改成了ICAN Player....
下面是对上面的补充:
1).补充个高级可重载函数PreSubclassWindow(),我的理解是允许用户在子类化之前再做一额外些处理 ,这个重载函数也是非常重要的,要引起相当的注意。可以在这里改变控件的大小,位置,窗口样式
,字体 ,等等.....你能想到的能改的,都可以在这里改.
2).关于Edit的补充说明:我最初的自绘方法是利用 WM_NCPAINT 里面处理的非客户区只是自己画了边界,以实现Hover和Leave不同的边界。不过我后来发现由于非客户区太小了边界也就2像素,如果
鼠标移动很快有时 系统不能检测到鼠标当前的状态,所以程序里面的Edit是在OnPaint里面做的绘制,不过有个核心API -Default() 下面看代码
void CEditEx::OnPaint()
{
Default(); // 关键
if(!m_bHover)
DrawBoder(); // 画自己的边界
},这才是程序里面的自绘Edit使用的方法.
3).对于控件的Hover和Leave效果,简单的说 Hover就是鼠标现在浮于控件上面,Leave就是鼠标离开了控件,那么这个效果要怎么实现呢?我直接给源码吧
以Edit控件为列
头文件中加入
afx_msg void OnMouseMove(UINT nFlags, CPoint point);
afx_msg LRESULT OnMouseLeave(WPARAM wParam, LPARAM lParam);
afx_msg LRESULT OnMouseHover(WPARAM wParam, LPARAM lParam);
CPP中加入:
BEGIN_MESSAGE_MAP(CEditEx, CEdit)
ON_MESSAGE(WM_MOUSELEAVE, OnMouseLeave)
ON_MESSAGE(WM_MOUSEHOVER, OnMouseHover)
ON_WM_MOUSEMOVE()
END_MESSAGE_MAP()
注:ON_WM_MOUSEMOVE() 可以用类向导添加,不过OnMouseLeave,和OnMouseHover是需要手动添加的
然后再CPP中定义:
void CEditEx::OnMouseMove(UINT nFlags, CPoint point)
{
if (!m_bHover)
{
TRACKMOUSEEVENT tme;
tme.cbSize = sizeof(tme);
tme.hwndTrack = m_hWnd;
tme.dwFlags = TME_LEAVE | TME_HOVER;
tme.dwHoverTime = 1;
m_bHover= _TrackMouseEvent(&tme); // m_bHover: BOOL型成员变量
}
CEdit::OnMouseMove(nFlags, point);
}
LRESULT CEditEx::OnMouseLeave(WPARAM wParam, LPARAM lParam)
{
m_bHover = false;
//
做相应的操作
//
return 0;
}
LRESULT CEditEx::OnMouseHover(WPARAM wParam, LPARAM lParam)
{
//
做相应的操作
//
return 0;
}
绝大多数控件可以用这种方法,不过某些控件,可能需要你在OnMouseMove里面完全模拟出 Hover和Leave的情况,比如程序里面的TabCtrl...
4).我把以前我看过的帖子整理了下供大家学习参考(由于时间久了,很多帖子都忘记了):
进度条自绘:http://www.codeproject.com/KB/miscctrl/cprogressctrlst.aspx(有项目源码)
透明控件(多个控件)实现:http://www.codeguru.com/cpp/controls/buttonctrl/advancedbuttons/article.php/c15603/General-Solution-for-a-Transparent-Control.htm(有项目源码)
透明窗体:http://msdn.microsoft.com/en-us/library/ms997507(Menu、窗体、Combobox 都可以参考这种方式实现任意透明度,我也是参考这种方法)
不规则按钮实现:http://www.codeguru.com/cpp/controls/buttonctrl/non-rectangularbuttons/article.php/c2085/Universal-Button---beauty-of-HRGN.htm
自绘按钮2篇帖子:
http://www.vckbase.com/document/viewdoc/?id=551
http://www.vckbase.com/document/viewdoc/?id=561
Custom draw 和 Owner draw 的区别(是全英文,不过要是读懂了对你自绘的思想很有帮助,有时间帮大家翻译下):
http://blog.csdn.net/xiexievv/article/details/6279219
WM_DRAWITEM与DrawItem()的讨论,对控件自绘很有帮助:
http://blog.csdn.net/xiexievv/article/details/6259194
下面这几篇帖子仔细阅读定有意想不到的收获,不只是自绘控件,完全可以让我们对MFC的整体认识都会提升1个等级
MFC中OnDraw与OnPaint的区别:
http://blog.csdn.net/xiexievv/article/details/6271153
深度剖析消息反射机制:
http://blog.csdn.net/xiexievv/article/details/6282205
PreTranslateMessage和TranslateMessage区别:
http://blog.csdn.net/xiexievv/article/details/6299027
WindowProc和DefWindowProc的区别:
http://blog.csdn.net/xiexievv/article/details/6299016
CWnd中PreCreateWindow、PreSubclassWindow、SubclassWindow的区别:
http://blog.csdn.net/xiexievv/article/details/6233423
同时推荐几个很好的学习网站:
http://www.codeproject.com(英文)
http://www.codeguru.com(英文)
http://www.pudn.com
http://www.vckbase.com/document/index.asp
http://www.hackchina.com
就写这么多吧,个人觉得最有用的资料还是MSDN当然还有强大CSDN,每个控件的自绘都不是固定有规律可循的,不要硬搬乱套,要活学活用。也许你现在才开始学,完全看不懂,没关系大家都是那么过来的啦。