1. 程式人生 > >不規則按鈕——讓自繪按鈕更簡單

不規則按鈕——讓自繪按鈕更簡單

下載原始碼

總體效果圖:

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. 總結
    我想可能沒有一篇文章能完全達到你的預期,但是我想每篇文章都能讓你的技術更進一步,我相信這篇文章也會是。