不规则按钮——让自绘按钮更简单

作者: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。
    DrawItem()就是控件绘制的处,要了解LPDRAWITEMSTRUCT这个类型,详细查询MSDN,上面有详细的介绍,下面我就介绍一下制作一个简单的控件所用到的成员。
3. LPDRAWITEMSTRUCT structure

    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
}

在CMyButton中的设置区域函数:
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. 总结
    我想可能没有一篇文章能完全达到你的预期,但是我想每篇文章都能让你的技术更进一步,我相信这篇文章也会是。

你可能感兴趣的:(不规则按钮——让自绘按钮更简单)