作者:sanmao
下载源代码
总体效果图:
1. 前言
Button是唯一一个我们要做大量努力和时间去改变其形状和属性的控件;不过事实上要改变它的属性不是很难,难的是,我们要知道如何去把它绘制的好看,一个好的界面设计师能够开发出很好看的按钮。
如果你知道如何使用”Device Caps”,你可以做更多的按钮设计,这篇文章是关于如何绘制不规则按钮。
2. 创建自绘按钮的基本步骤
(1)到你的工程目录下,在工程名上点击右键,在弹出菜单上点击New Class选项。
(2) 定义新类名(CMyButton),并且选择它的基类为CButton。
(3) 在你的工程中,右键点击类的名字,并在弹出菜单中点击Add Virtual Function。
(4)将虚函数DrawItem(),PreSubclassWindow()添加到你的类中。
(5)将button的风格改为owner drawn,在PreSubclassWindow()中添加下面的代码
ModifyStyle(0,BS_OWNERDRAW)。(6)DrawItem()函数中带了一个LPDRAWITEMSTRUCT类型的参数lpDrawItemStruct。
lpDrawItemStruct->itemAction:
指定绘制行为,其值可能是以下值中的一个或多个的联合
ODA_DRAWENTRITE –当整个控件需要绘制时,设置该值。
ODA_FOCUS – 当控件得到或失去焦点时被绘制,设置此值,此时应对itemState成员进行检查,以确定控件是否具有焦点。
ODA_SELECT –当且仅当控件选择状态被改变时需要绘制,设置此值,此时应对itemState成员进行检查,以确定新的选择状态。
lpDrawItemStruct->itemState
指定当前绘制操作完成后,所绘项的可见状态。例如,如果菜单项为显示灰色,则可以指定ODS_GRAYED状态标志,其取值可以为下表当中的一个或多个的联合。
ODS_CHECKED – 当菜单项被选中,则设置此值,这个值仅适用于菜单。
ODS_DISABLED – 当控件被绘制成无用状态时,则设置此值。
ODS_FOCUS – 当控件有输入焦点时,则设置此值。
ODS_GRAYED – 当控件需要显示为灰色时,则设置此值,这个值仅适用于菜单。
ODS_SELECTED – 当控件的状态为选中时,设置此值。
ODS_COMBOBOXEDIT – 此绘制发生在自绘组合框的选择区域。
ODS_DEFAULT – 此项是默认项。
lpDrawItemStruct->hDC
指定绘图所需要的设备上下文,这个设备上下文必须在控件执行绘图操作时使用
4.虚函数DrawItem()
在此函数中绘制控件,下面的代码是从例子中抓取的
void CMyButton::DrawItem(LPDRAWITEMSTRUCT lpDrawItemStruct) { SetWindowRgn(rgn,TRUE); // Construct your buttons region. This wont reflect in your view. // But you can sense it by clicking the region area. CDC* pDC=CDC::FromHandle(lpDrawItemStruct->hDC); // Get dc for the button switch(lpDrawItemStruct->itemAction) { case ODA_SELECT: { } // no break; for this case case ODA_DRAWENTIRE: { if(lpDrawItemStruct->itemState & ODS_SELECTED) { pDC->FillRgn(CRgn::FromHandle(trgn), CBrush::FromHandle((HBRUSH)GetStockObject(GRAY_BRUSH))); } // Draw button down state else { pDC->FillRgn(CRgn::FromHandle(trgn), CBrush::FromHandle((HBRUSH)GetStockObject(BLACK_BRUSH))); HRGN r= ::CreateRectRgn(0,0,0,0); CombineRgn(r,trgn,0,RGN_COPY); OffsetRgn(r,2,2); pDC->FillRgn(CRgn::FromHandle(r), CBrush::FromHandle((HBRUSH)GetStockObject(GRAY_BRUSH))); } break; } case ODA_FOCUS: { pDC->FillRgn(CRgn::FromHandle(trgn), CBrush::FromHandle((HBRUSH)GetStockObject(LTGRAY_BRUSH))); } break; } // Draw button caption. }
5.示例
此示例工程演示自绘不规则按钮,你可以在dialog上面用鼠标绘制各种button,当鼠标左键弹起来的时候,控件会按照你绘制的形状创建,这个理念并无特别之处,用CDC绘制传进来的坐标坐标,其path是用BeginPath()和EndPath()来获得,路径就转化成了区域,使用那个区域,按钮就被创建和绘制(FillRgn)
因此,在dialog中,要创建一个成员变量DC,CDC *m_pdc;当你在dialog中绘制的时候,这个DC用来创建一个路径。
下面是绘图和制作路径的代码:
void CButtonSubDlg::OnLButtonDown(UINT nFlags, CPoint point) { #ifdef _SAMPLE1 m_pdc->BeginPath(); m_pdc->MoveTo(point); m_spoint=point; SetCapture(); #endif CDialog::OnLButtonDown(nFlags, point); } void CButtonSubDlg::OnMouseMove(UINT nFlags, CPoint point) { // Show mouse position CString mouse; mouse.Format("Irregular Buttons - X:%d Y:%d",point.x,point.y); SetWindowText(mouse); if(nFlags==MK_LBUTTON) { #ifdef _SAMPLE1 CClientDC dc(this); m_pdc->LineTo(point); dc.MoveTo(m_spoint); dc.LineTo(point); m_spoint=point; #endif } CDialog::OnMouseMove(nFlags, point); } void CButtonSubDlg::OnLButtonUp(UINT nFlags, CPoint point) { #ifdef _SAMPLE1 m_pdc->EndPath(); cRgn.DeleteObject(); cRgn.CreateFromPath(m_pdc); HRGN rrgn; rrgn=::CreateRectRgn(0,0,0,0); int res=CombineRgn(rrgn,cRgn.operator HRGN(),0,RGN_COPY); if(NULLREGION ==res) MessageBox("Null Region::Cannot create region for button"); else if(ERROR==res) MessageBox("Error::Cannot create region"); else { CRect rect; GetClientRect(rect); m_but= new CMyButton(); m_but->SetRgn(CRgn::FromHandle(rrgn)); m_but->Create("",WS_CHILD|WS_VISIBLE| WS_TABSTOP|BS_PUSHBUTTON, rect,this,1000+idcount++); } ReleaseCapture(); #endif CDialog::OnLButtonUp(nFlags, point); }注意:BeginPath()在OnLButtonDown()函数中调用,EndPath()在OnLButtonUp()函数中调用。
BOOL CButtonSubDlg::OnCommand(WPARAM wParam, LPARAM lParam) { if(HIWORD(wParam)==BN_CLICKED) { for(int i=0;i< idcount;i++) { if(LOWORD(wParam)==1000+i) { CString smsg;smsg.Format("Button %d Clicked",i+1); MessageBox(smsg); break; } } } return CDialog::OnCommand(wParam, lParam); }
void CButtonSubDlg::OnDestroy() { CDialog::OnDestroy(); for(int i=0;i< idcount;i++) { m_but=(CMyButton *)GetDlgItem(1000+i); if(m_but) delete m_but; } #ifdef _SAMPLE1 if(m_pdc) delete m_pdc; #endif }
void CMyButton::SetRgn(CRgn *region) { rgn=::CreateRectRgn(0,0,0,0); CombineRgn(rgn,region->operator HRGN(),NULL,RGN_COPY); trgn=::CreateRectRgn(0,0,0,0); CombineRgn(trgn,region->operator HRGN(),NULL,RGN_COPY); }
6. 关于区域
区域是这个演示程序关键值,你用手绘制的图形变成路径,路径将转为区域,最后通过这个区域创建一个button。
SetWindowRgn()函数将通过给定的区域设定特定形状的窗口。在自绘按钮中,SetWindowRgn()通常是设置按钮的区域。但是,从视觉上设置一个不规则按钮,这往往是不够的;为了更好的视觉效果,你需要每一个区域都绘制按钮。
在这个示例中,CombineRgn()将一个区域转变成一个变量,RGN_COPY选项使CombineRgn()函数复制一个区域到另外一个区域。
7. 关于示例部分
这个示例工程包含了两个部分,_SAMPLE1,_SAMPLE2;_SAMPLE1以上定义的工程部分,_SAMPLE2是MSDN中关于改变按钮文本色的示例代码。当要运行_SAMPLE2时,打开项目设置,在预处理项中,把_SAMPLE1改为_SAMPLE2。
8. 总结
我想可能没有一篇文章能完全达到你的预期,但是我想每篇文章都能让你的技术更进一步,我相信这篇文章也会是。