2011-07-16 75 views
3

如何獲取3狀態複選框以將不同狀態使用不同的位圖?MFC VC++自定義複選框圖片

我想改變我的三態複選框使用的圖像使用不同的;這些控件採用Win98風格,而且這種複選框的不確定狀態很難與禁用的複選框區分開來(這可能是他們爲什麼改變了WinXP風格控件的原因,但由於我的項目中有其他細節,我無法使用這些控件) 。

我正在使用Visual C++ 2010,並且在VS的資源編輯器中定義了一個8x8位圖。位圖的ID是IDB_INDET_CHECK

我不完全確定這樣的標準「技術」是什麼;我只是真的開始操縱Windows控件和MFC。

我的第一次嘗試是創建一個類,CTriButton,派生自CButton,覆蓋DrawItem函數,並嘗試自己繪製它。然後,我使用SubclassDlgItem將窗口中的其中一個複選框變成此類(我認爲?)。這種作品?複選框不再出現,如果我點擊它應該在的位置,會出現一個空的複選框框架,但沒有其他事情發生(並且我的代碼中的調試消息未被髮送)。

下面是相關的代碼,但我不確定任何這是正確的。首先,我的窗口的代碼是OnInitDialog

BOOL CAffixFilterDlg::OnInitDialog() // CAffixFilterDlg is my CDialog-derived window 
{ 
    CDialog::OnInitDialog(); // call basic version 

    // subclass a CButton-derived control with CTriButton 
    if (CBipedHead.SubclassDlgItem(IDC_HEAD, this)) // CBipedHead is a CTriButton member of CAffixFilterDlg, IDC_HEAD is a checkbox 
     SetWindowLong(CBipedHead.m_hWnd, GWL_STYLE, CBipedHead.GetStyle() | BS_OWNERDRAW); // set the ownerdraw style 
    else // subclassing didn't work 
     _ERROR("Subclassing failed."); // I do not see this error message, so SubclassDlgItem worked? 

    // initialization continues, but is not relevant... 
    UpdateWindow(); 
    Invalidate(); 

    return TRUE; 
} 

接下來,我的自定義按鈕的代碼爲DrawItem

void CTriButton::DrawItem(LPDRAWITEMSTRUCT lpDrawItemStruct) 
{ 
    _DMESSAGE("Drawing TriButton"); // never see this message 

    CDC dc; 
    dc.Attach(lpDrawItemStruct->hDC);  //Get device context object 
    int nWidth = GetSystemMetrics(SM_CXMENUCHECK); 
    int nMargin = (nWidth - 8)/2; 

    CRect textRt = lpDrawItemStruct->rcItem; 
    textRt.right = textRt.right - nWidth - nMargin; 

    CString text; 
    GetWindowText(text); 

    UINT textDrawState = DST_TEXT; 
    if (lpDrawItemStruct->itemState & ODS_DISABLED) 
     textDrawState |= DSS_DISABLED; 

    dc.DrawState(CPoint(textRt.left, textRt.top), textRt.Size(), text, textDrawState, TRUE, 0, (CBrush*)NULL); 

    CRect rt = lpDrawItemStruct->rcItem; // initial rect is for entire button 
    rt.left = rt.right - nWidth;   // set left margin 
    LONG center = (rt.bottom + rt.top)/2; 
    rt.top = center - nWidth/2; 
    rt.bottom = center + nWidth/2; 

    UINT checkDrawState = DFCS_BUTTONCHECK; 
    if (lpDrawItemStruct->itemState & ODS_DISABLED) 
     checkDrawState |= DFCS_INACTIVE; 

    if (lpDrawItemStruct->itemState & ODS_CHECKED) 
     checkDrawState |= DFCS_CHECKED; 

    else if (GetCheck() == BST_INDETERMINATE) { 
     _VMESSAGE("Indeterminate; custom draw."); 

     CBitmap indet_check = CBitmap(); 
     indet_check.LoadBitmap(IDB_INDET_CHECK); 

     CPoint pt = CPoint(rt.left + nMargin, rt.top + nMargin); 
     CSize sz = CSize(8, 8); 

     dc.DrawState(pt, sz, &indet_check, DST_BITMAP|DSS_NORMAL); 
    } 

    dc.DrawFrameControl(rt, DFC_BUTTON, checkDrawState); 
} 
+1

你是在哪裏啓用了所有者繪製窗口樣式的? – paludarium

+1

你的問題是什麼?您在哪裏設置了複選框以將三種樣式或設置按鈕的狀態設置爲中間狀態? – Ajay

+0

@paludarium:謝謝,這有助於一些;我不知道這樣做。我更新了我的代碼,現在設置ownerdraw樣式;現在似乎並沒有被繪製出來,直到我點擊它應該在的位置,然後我纔得到一個空的複選框。我的繪圖代碼似乎沒有被調用(從來沒有看到調試信息)。我的問題已更新。感謝提到設置風格! – KRyan

回答

1

答案,但一個答案:這個習俗CCheckBox我發現更多或更少的使我想要的東西。默認情況下,它不允許3個狀態,但是我通過一些我自己的調整來解決這個問題。我不是100%肯定它可以用於開箱即用(我有一些問題,不是似乎是由於我的編輯,但我不能確定),但它是解決方案I已經使用了。儘管如此,我不打算稱這個答案,以防某人可以窺探我的代碼出了什麼問題並想要照亮我。

3

在OnInitDialog(),你需要改變窗口的風格,否則它不知道它需要重繪之後調用InvalidateRect()。在更改窗口樣式之後調用UpdateWindow()也是一個好主意。一些信息通常由公共控件緩存,在調用UpdateWindow()之前不會確認更改。

在DrawItem()中,您負責渲染控件的所有狀態。你不應該調用CButton :: DrawItem(),因爲它什麼都不做。你可以試試下面的:

void CTriButton::DrawItem(LPDRAWITEMSTRUCT lpDrawItemStruct) 
{ 
    CBitmap indet_check 

    _DMESSAGE("Drawing TriButton"); // I never see this message 
    int checkState = GetCheck(); 

    if (checkState == BST_CHECKED) 
    { 
     indet_check.LoadBitmap(IDB_INDET_CHECK); 
    } 
    else if (checkState == BST_UNCHECKED) 
    { 
     indet_check.LoadBitmap(IDB_INDET_UNCHECKED); 
    } 
    else if (checkState == BST_INDETERMINATE) 
    { 
     indet_check.LoadBitmap(IDB_INDET_INDETERMINATE); 
    } 

    // ... rest of your drawing code here ... 
    // don't forget to draw focus and push states too ;) 

} 

附錄:

我不能相信我身邊錯過了第一次,但你要SubclassDlgItem通話可能沒有預期的效果。此調用導致用於該按鈕的消息首先由控件父窗口處理。因爲在CWnd(CDialog的超類)中的DrawItem的默認實現不做任何事情,所以消息永遠不會傳遞給控件。

與下面的代碼段替換這一點,一切都應該是確定:

HWND hWndButton; 
GetDlgItem(IDC_HEAD, &hWndButton); 
CBipedHead.SubclassWindow(hWndButton); 

兩個方面指出此:

  1. 它通常不使用這兩個類相同的命名約定一個好主意,班級成員。它造成了一個令人困惑的閱讀。
  2. 我猜你總是編譯和運行在發佈模式。如果你是 - 不要。這可以防止斷言被拋出並讓你知道某些事情是錯誤的。
+0

'CButton :: DrawItem'不做任何事情?那真是奇怪。有什麼辦法可以加載默認的位圖作爲複選標記(一些默認的ID或者我可以使用'LoadBitmap'的東西嗎?另外,'DrawItem'函數還需要處理什麼,也就是說,我的代碼是否能夠獲得所有的對我來說,這裏的一個主要問題是,我不能看到'DrawItem'的實際定義來知道我應該模仿什麼......任何幫助將不勝感激。感謝有關'InvalidateRect'的註釋和'UpdateWindow';這是有道理的 – KRyan

+1

@DragoonWraith在MFC中沒有任何公共控件表示實現它們的DrawItem()方法渲染,它們通常包含一行並且是一個ASSERT()語句。[DRAWITEMSTRUC](http:// msdn.microsoft.com/en-us/library/92fhtw0f%28v=VS.80%29.aspx)結構包含一個名爲_itemState_的成員,它擁有關於如何繪製控件的各種標誌。對於您至少應該處理的所有控件** ODS_DEFAULT **,** ODS_DISABLED **,** ODS_FOCUS **和** ODS_SELECTE D **州。 AFAIK通用控件通常不依賴資源位圖來表示元素。 –

+0

謝謝!這非常有幫助。現在來看看如何做這些事情...我假設因爲我想要一個非默認的「外觀」到不確定的狀態,我將需要一個位圖? – KRyan