Ribbon button items with large images and checkbox

2019-08-10 08:45发布

问题:

I've got a menu attached to a split ribbon button like this (VS2008, Feature Pack):

std::auto_ptr<CMFCRibbonButton> apBtn3(new CMFCRibbonButton(ID_RIBBON_BTN_3, _T("Split Button"), 2, 2));
apBtn3->SetMenu(IDR_RIBBON_MENU_1, TRUE);
apBtn3->SetAlwaysLargeImage();
apBtn3->RemoveSubItem(0);
std::auto_ptr<CMFCRibbonButton> apSubButton(new CMFCRibbonButton(ID_RIBBON_MBTN_1, _T("Item 1"), 2, 2));   
apSubButton->SetAlwaysLargeImage();
apBtn3->AddSubItem(apSubButton.release(), 0);
pPanel1->Add(apBtn3.release());

I want to put checkboxes in front of each menu item and have provided SetCheck() calls in the CN_UPDATE_COMMAND_UI handler but checkboxes will only show up, if I disable the large icons.

Is there any way to use checkboxes along with large icons in CMFCRibbonButton menus? If not, what would be the best possible workaround?

回答1:

I found a usable workaround to check large icons by alpha-blending a green circle with a checkmark in it over the icon -- the same solution Windows uses to mark the default audio device or printer in the control panel:

This is my CMFCRibbonButtonEx class declaration:

class CMFCRibbonButtonEx : public CMFCRibbonButton
{
// Construction
public:
    CMFCRibbonButtonEx();
    CMFCRibbonButtonEx(UINT nID, LPCTSTR lpszText, int nSmallImageIndex = -1, int nLargeImageIndex = -1, BOOL bAlwaysShowDescription = FALSE);
    CMFCRibbonButtonEx(UINT nID, LPCTSTR lpszText, HICON hIcon, BOOL bAlwaysShowDescription = FALSE, HICON hIconSmall = NULL, BOOL bAutoDestroyIcon = FALSE, BOOL bAlphaBlendIcon = FALSE);

// Overridden
    void SetCheck(BOOL bCheck = TRUE);
    void DrawImage(CDC* pDC, RibbonImageType type, CRect rectImage);

// Attributes
private:
    BOOL m_bChecked;

// Helper
private:
    void DrawCheckmark(CDC* pDC, int CheckmarkResourceBitmapID, RECT *r);
    void PremultiplyBitmapAlpha(HDC hDC, HBITMAP hBmp);
};

Here are the class function definitions:

CMFCRibbonButtonEx::CMFCRibbonButtonEx() : CMFCRibbonButton() { }
CMFCRibbonButtonEx::CMFCRibbonButtonEx(UINT nID, LPCTSTR lpszText, int nSmallImageIndex, int nLargeImageIndex, BOOL bAlwaysShowDescription)
    : CMFCRibbonButton(nID, lpszText, nSmallImageIndex, nLargeImageIndex, bAlwaysShowDescription) { }
CMFCRibbonButtonEx::CMFCRibbonButtonEx(UINT nID, LPCTSTR lpszText, HICON hIcon, BOOL bAlwaysShowDescription, HICON hIconSmall, BOOL bAutoDestroyIcon, BOOL bAlphaBlendIcon)
    : CMFCRibbonButton(nID, lpszText, hIcon, bAlwaysShowDescription , hIconSmall, bAutoDestroyIcon, bAlphaBlendIcon) { }
void CMFCRibbonButtonEx::SetCheck(BOOL bCheck)
{
    m_bChecked = bCheck;
}
void CMFCRibbonButtonEx::DrawImage(CDC* pDC, RibbonImageType type, CRect rectImage)
{
    CMFCRibbonButton::DrawImage(pDC, type, rectImage);
    if (type == RibbonImageLarge && m_bChecked) 
        DrawCheckmark(pDC, IDB_BIG_ICON_CHECKMARK, &rectImage);
}
void CMFCRibbonButtonEx::DrawCheckmark(CDC* pDC, int CheckmarkResourceBitmapID, RECT *r)
{
    HDC  hdc;
    CDC  *dc;
    CDC dcMem;
    CBitmap cbm;

    VERIFY(hdc = pDC->m_hDC);
    VERIFY(dc = pDC);

    dcMem.CreateCompatibleDC(dc);

    cbm.LoadBitmap(CheckmarkResourceBitmapID);
    PremultiplyBitmapAlpha(dcMem.m_hDC, cbm);
    SelectObject(dcMem.m_hDC, cbm.m_hObject);

    BLENDFUNCTION bf;
    bf.BlendOp = AC_SRC_OVER;
    bf.BlendFlags = 0;
    bf.SourceConstantAlpha = 255;
    bf.AlphaFormat = AC_SRC_ALPHA;
    ::AlphaBlend(hdc, r->left, r->top, r->right-r->left, r->bottom-r->top, dcMem, 0, 0, 32, 32, bf);

    VERIFY(dcMem.DeleteDC());
}
void CMFCRibbonButtonEx::PremultiplyBitmapAlpha(HDC hDC, HBITMAP hBmp)
{
   BITMAP bm = { 0 };
   GetObject(hBmp, sizeof(bm), &bm);
   BITMAPINFO* bmi = (BITMAPINFO*) _alloca(sizeof(BITMAPINFOHEADER) + (256 * sizeof(RGBQUAD)));
   ::ZeroMemory(bmi, sizeof(BITMAPINFOHEADER) + (256 * sizeof(RGBQUAD)));
   bmi->bmiHeader.biSize = sizeof(BITMAPINFOHEADER);
   BOOL bRes = ::GetDIBits(hDC, hBmp, 0, bm.bmHeight, NULL, bmi, DIB_RGB_COLORS);
   if( !bRes || bmi->bmiHeader.biBitCount != 32 ) return;
   LPBYTE pBitData = (LPBYTE) ::LocalAlloc(LPTR, bm.bmWidth * bm.bmHeight * sizeof(DWORD));
   if( pBitData == NULL ) return;
   LPBYTE pData = pBitData;
   ::GetDIBits(hDC, hBmp, 0, bm.bmHeight, pData, bmi, DIB_RGB_COLORS);
   for( int y = 0; y < bm.bmHeight; y++ ) {
      for( int x = 0; x < bm.bmWidth; x++ ) {
         pData[0] = (BYTE)((DWORD)pData[0] * pData[3] / 255);
         pData[1] = (BYTE)((DWORD)pData[1] * pData[3] / 255);
         pData[2] = (BYTE)((DWORD)pData[2] * pData[3] / 255);
         pData += 4;
      }
   }
   ::SetDIBits(hDC, hBmp, 0, bm.bmHeight, pBitData, bmi, DIB_RGB_COLORS);
   ::LocalFree(pBitData);
}

IDB_BIG_ICON_CHECKMARK is a 32bit bmp with alpha channel: Download here

Sadly, in OnCmdMsg only a fake CMFCRibbonButton created by the ribbon framework is being maintained, so SetCheck() doesn't have any effect on an IsChecked() call in DrawImage(). You just have to find the real CMFCRibbonButtonEx in the subitems of the menu like this:

BOOL CEasyCashView::OnCmdMsg(UINT nID, int nCode, void* pExtra, AFX_CMDHANDLERINFO* pHandlerInfo) 
{
    ...

    else if (nID >= ID_MY_ITEMS_BASE && nID < ID_ITEMS_BASE+MAX_MY_ITEMS)
    {
        if (nCode == CN_COMMAND)
        {
            OnMyItemsCommand(nID);      
        }
        else if (nCode == CN_UPDATE_COMMAND_UI)
        {

            ((CCmdUI*)pExtra)->SetCheck(myItemsCheckedArray[nID-ID_MY_ITEMS_BASE] == TRUE); 
            // this won't have any effect, use code below

            CMFCRibbonButton* pMyMainMenuButton;
            if (pMyMainMenuButton = ((CMainFrame*)AfxGetMainWnd())->m_pMyMainMenuButton)
            {
                int i;
                for (i = 0; i < pMyMainMenuButton->GetSubItems().GetCount(); i++)
                    if (pMyMainMenuButton->GetSubItems()[i]->GetID() == nID)
                    {
                        ((CMFCRibbonButtonEx*)pMyMainMenuButton->GetSubItems()[i])->SetCheck(myItemsCheckedArray[nID-ID_MY_ITEMS_BASE] == TRUE);
                        break;
                    }
            }

            return TRUE;
        }
    }

Should anyone know how to connect the fake CMFCRibbonButton (or CMFCRibbonBaseElement) maintained by CCmdUI to the originally created CMFCRibbonButtonEx, please leave me a comment.



回答2:

"checkboxes in front of each menu item"

Is the below picture what you're looking for? If yes, this can be achieved by editing the ribbon xml file.