VS2010自定义控件实现信号灯

总是会在论坛里看到类似这样的问题,“如何通过按钮更换一幅图片”,“怎样将图片显示在对话框中”,“MFC的PictureCtrl怎样操作”等等,不一而足。面对这类问题我一般都会建议通过CWnd派生一个自定义控件来自行处理,不过这话说起来容易,可是这个控件要如何实现呢?所以经常会想不妨做个例子和大家分享一下,当然如果大家有什么更好的办法我也可以从中学习借鉴。但问题又来了,这类例子简单实现其实就是一个函数的问题——OnPaint,但要做的精致些要处理的方面又太多,容易喧宾夺主。怎么才能找个折中的方案呢,什么样题材的例子更具代表性呢?这两天逛论坛一个帖子给了我启示,做个信号灯的控制,即可以说明问题又简单实用,大家还可以举一反三,这应该是个不错的主意,于是做了一个Demo,写了这篇文章。

      这回做了一个gif的效果图,我做了一个三态的状态灯,分别实现的正常(绿色)、警告(红色)和不可用(灰色)的状态表示。状态切换是通过单选按钮实现的,当然这个可以通过任何我们想要的方式控制。大家可以看得出来,这个例子做的比较粗糙,其实就是更换三张不同的图片,为了突出主要功能我没有添加不必要的修饰,比如镂空的处理等。

      落实到具体实现,正如前文所说我是通过CWnd派生出了一个CSignalLampCtrl来实现自定义控件,然后就是在这个类的OnPaint里绘制位图了。说到这我插一句,起初我刚做界面编程的时候每每遇到问题就会把需求往MFC的标准控件上靠,找一个最接近的重载自绘一下,如果没有接近的就统统重载CStatic实现。可后来发现,静态控件也有很多的特殊处理,为了实现“静态”static有很多处理是我们做一般控件时不需要的,所以在使用这种控件的时候就会产生很多不必要的麻烦。所以后来我开始尝试通过自定义控件解决问题,而且越来越适应这种方式。自定义控件虽然没有一些现成可用的消息,但是它给了我们最大的控制权和自由度,使我们可以做到随心所欲没有束缚。

      使用自定义控件只需要注意一个小细节,控件的属性编辑器里可以看到Class项,这里要填写控件的类名。同时这个类名要进行注册,所以在我的类中可以找到RegisterCtrlClass,它的具体实现代码为

[cpp]  view plain copy
  1. void CSignalLampCtrl::RegisterCtrlClass()   
  2. {   
  3.     HINSTANCE hInstance = AfxGetInstanceHandle();  
  4.   
  5.     WNDCLASS    wndclsCtrl;   
  6.     ZeroMemory(&wndclsCtrl, sizeof(WNDCLASS));  
  7.   
  8.     if(::GetClassInfo(hInstance, STR_CLASS_NAME, &wndclsCtrl))   
  9.         return;  
  10.   
  11.     //设置控件类信息   
  12.     wndclsCtrl.style            = CS_HREDRAW | CS_VREDRAW | CS_OWNDC;   
  13.     wndclsCtrl.lpfnWndProc        = ::DefWindowProc;   
  14.     wndclsCtrl.cbClsExtra        = 0;   
  15.     wndclsCtrl.cbWndExtra        = 0;   
  16.     wndclsCtrl.hInstance        = hInstance;   
  17.     wndclsCtrl.hIcon            = NULL;   
  18.     wndclsCtrl.hCursor            = AfxGetApp()->LoadStandardCursor(IDC_ARROW);   
  19.     wndclsCtrl.hbrBackground    = NULL;   
  20.     wndclsCtrl.lpszMenuName        = NULL;   
  21.     wndclsCtrl.lpszClassName    = STR_CLASS_NAME;  
  22.   
  23.     //注册控件类   
  24.     AfxRegisterClass(&wndclsCtrl);   
  25. }  

我通常将它放到控件的构造函数中以便使用时自动进行注册。

      关于这个例子其实也没有什么需要特别说明的,OnPaint函数很简单,就是绘制一张位图,我的位图都是放到资源中的,当然通过文件读进来显示也没有问题。而且通过CImage或GDI+我们也可以显示非位图的图像,这个有兴趣的读者可以自行尝试。OnPaint的代码如下

[cpp]  view plain copy
  1. void CSignalLampCtrl::OnPaint()   
  2. {   
  3.     CBitmap        bmLight;   
  4.     BITMAP        bmData;   
  5.     CPaintDC    dc(this);   
  6.     CDC*        pMemDC = new CDC;  
  7.   
  8.     bmLight.LoadBitmap(nIDBitmap);   
  9.     //获取位图数据   
  10.     bmLight.GetBitmap(&bmData);  
  11.   
  12.     //创建兼容DC   
  13.     pMemDC->CreateCompatibleDC(&dc);  
  14.   
  15.     //贴图   
  16.     CBitmap        *pOldBitmap    = pMemDC->SelectObject(&bmLight);   
  17.     dc.BitBlt(0, 0, bmData.bmWidth, bmData.bmHeight, pMemDC, 0, 0, SRCCOPY);   
  18.     pMemDC->SelectObject(pOldBitmap);  
  19.   
  20.     delete    pMemDC;   
  21. }  

可以注意到加载位图的时候是通过一个变量nIDBitmap实现的,这里存放欲显示的位图的资源ID,切换位图就是切换这个ID,我做了一个函数SetState来实现

[cpp]  view plain copy
  1. void CSignalLampCtrl::SetState(StateType nState)   
  2. {   
  3.     switch(nState)   
  4.     {   
  5.     case Normal:nIDBitmap = IDB_BITMAP_GREEN;break;   
  6.     case Warning:nIDBitmap = IDB_BITMAP_RED;break;   
  7.     case Disable:nIDBitmap = IDB_BITMAP_GRAY;break;   
  8.     }  
  9.   
  10.     Invalidate();   
  11. }  

而在radio消息中对它的调用也很简单

[cpp]  view plain copy
  1. void CSignalLampDlg::OnBnClickedRadioNormal()   
  2. {   
  3.     UpdateData();  
  4.   
  5.     m_slDemo.SetState((CSignalLampCtrl::StateType)m_nState);   
  6. }  

这里大家可以使用任何一种自己认为合理的切换图片的方式,如果通过OnTimer消息控制信号灯的状态切换就可以实现信号灯闪烁的动画效果。最后要提的一点是我在PreSubclassWindow中我对控件的大小做了限制,使其与图片的大小相同,具体代码为

[cpp]  view plain copy
  1. void CSignalLampCtrl::PreSubclassWindow()   
  2. {   
  3.     CWnd::PreSubclassWindow();  
  4.   
  5.     CRect        rectCtrl;   
  6.     CBitmap        bmLight;   
  7.     BITMAP        bmData;  
  8.   
  9.     bmLight.LoadBitmap(nIDBitmap);   
  10.     //获取位图数据   
  11.     bmLight.GetBitmap(&bmData);  
  12.   
  13.     GetWindowRect(rectCtrl);   
  14.     rectCtrl.bottom    = rectCtrl.top+bmData.bmHeight;   
  15.     rectCtrl.right    = rectCtrl.left+bmData.bmWidth;   
  16.     GetParent()->ScreenToClient(rectCtrl);   
  17.     MoveWindow(rectCtrl);   
  18. }  
 

       好了,关于这个例子就介绍完了,有兴趣的朋友可以下载示例源码看看,希望大家提出宝贵意见。由于水平有限例子功能过于简单,让大家见笑了。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值