2015-04-19 13 views
5

Sto semplicemente cercando di aggiungere piccole campioni di colore al mio menu contestuale Ecco una versione Photoshopped di quello che sto cercando di realizzare (visualizzato tramite il TrackPopupMenu API.):Come misurare e visualizzare correttamente la voce del menu di scelta rapida del proprietario con un segno di spunta?

enter image description here

Per quanto mi capire il menu predefinito non lo supporta. Btw, il campione di cui sopra (senza campioni di colore) è stata generata in questo modo:

MENUITEMINFO mii = {0}; 
mii.cbSize = sizeof(mii); 
mii.fMask = MIIM_FTYPE | MIIM_ID | MIIM_STATE | MIIM_STRING; 
mii.fType = MFT_STRING; 
mii.wID = ID_1_MARKER_01 + m; 
mii.dwTypeData = L"Marker"; 
mii.cch = TSIZEOF(L"Marker"); 
mii.fState = m == 1 ? MFS_CHECKED : MFS_ENABLED; 
if(m == 2) 
    mii.fState |= MFS_GRAYED; 

VERIFY(::InsertMenuItem(hMenu, ID_1_BEFORE, FALSE, &mii)); 

Così ho scoperto che ho bisogno di usare MFT_OWNERDRAW stile per disegnare le voci di menu me stesso, ma è qui che cominciano i problemi.

ho cambiato il mio codice per visualizzare il menu come tale:

MENUITEMINFO mii = {0}; 
mii.cbSize = sizeof(mii); 
mii.fMask = MIIM_FTYPE | MIIM_ID | MIIM_STATE; 
mii.fType = MFT_OWNERDRAW; 
mii.wID = ID_1_MARKER_01 + m; 
mii.dwItemData = MARKER_ID_01 + m; 
mii.fState = m == 1 ? MFS_CHECKED : MFS_ENABLED; 
if(m == 2) 
    mii.fState |= MFS_GRAYED; 

VERIFY(::InsertMenuItem(hMenu, ID_1_BEFORE, FALSE, &mii)); 

poi ho bisogno di ignorare WM_MEASUREITEM e WM_DRAWITEM messaggi. Ma quando lo faccio con il codice che ti faccio vedere qui di seguito, ecco cosa ottengo:

enter image description here

Quindi, per favore abbiate pazienza con me. Ho molte domande su questo argomento:

1) Durante l'elaborazione WM_MEASUREITEM come faccio a conoscere le dimensioni del testo, se non forniscono né DCHWND per il menu? In altre parole, se faccio questo, la dimensione del menu è sbagliato:

#define TSIZEOF(f) ((sizeof(f) - sizeof(TCHAR))/sizeof(TCHAR)) 

//hwnd = HWND supplied in WM_MEASUREITEM notification 
HDC hDC = ::GetDC(hwnd); 
HGDIOBJ hOldFont = ::SelectObject(hDC, ::SendMessage(hwnd, WM_GETFONT, 0, 0)); 

SIZE szTxt = {0}; 
::GetTextExtentPoint32(hDC, 
    L"Marker", 
    TSIZEOF(L"Marker"), 
    &szTxt); 

//lpmis = MEASUREITEMSTRUCT* 
lpmis->itemWidth = szTxt.cx; 
lpmis->itemHeight = szTxt.cy; 

::SelectObject(hDC, hOldFont); 
::ReleaseDC(hwnd, hDC); 

2) Poi durante l'elaborazione WM_DRAWITEM come faccio a sapere l'offset per iniziare a disegnare il testo a sinistra? Se faccio questo, i miei menu non sono compensati abbastanza a destra (come si può vedere dalla schermata qui sopra):

int nCheckW = ::GetSystemMetrics(SM_CXMENUCHECK); 

//lpdis = DRAWITEMSTRUCT* 
::ExtTextOut(lpdis->hDC, 
    lpdis->rcItem.left + nCheckW, 
    lpdis->rcItem.top, 
    ETO_OPAQUE, 
      &lpdis->rcItem, 
    L"Marker", 
    TSIZEOF(L"Marker"), 
    NULL); 

3) E infine come faccio a disegnare quella casella di controllo di default sul lato sinistro della la voce del menu?

+2

Quando si utilizza elementi OwnerDraw di Windows disegna intero menu in stile classico senza l'utilizzo di temi. Perché non vuoi usare le icone dei colori invece delle voci di menu ownerdraw? Risolverà tutti i tuoi problemi. –

+0

@DenisAnisimov: Non sono quelle icone colorate a sinistra? Se è così, ho bisogno di spazio per quei segni di spunta da mostrare sulla sinistra. – c00000fd

+0

1) Per la misurazione, l'unico modo sicuro era ottenere 'LOGFONT' da' SystemParametersInfo (SPI_GETNONCLIENTMETRICS) 'che viene restituito in' lfMenuFont' e creare un font da esso usando 'CreateFontIndirect' e quindi usarlo per' SelectObject() ' e solo allora chiama 'GetTextExtentPoint32' per ottenere la dimensione del testo. So che sembra un sacco di passaggi, ma sembra darmi un vero risultato. – c00000fd

risposta

1

Mentre non uso i campioni di colore e, uso rigorosamente MFC, eseguo il rendering delle bitmap sulle voci di menu derivate. Dovresti essere in grado di adattare quanto segue alle tue esigenze.

Nel misurare il testo dell'articolo, utilizzo il desktop dc.

Insieme ad alcuni piccoli aggiustamenti attraverso la sperimentazione, ho trovato quello che mi serviva per le dimensioni.

Per rendere la bitmap e il testo effettivi, controllo se un tema è attivo e il rendering della voce di menu in uno dei due modi diversi. O come voce di menu a tema o voce di menu standard. Per rendere il testo, comincio con il rect che è stato passato tramite LPDRAWITEMSTRUCT. Quindi apporto la seguente regolazione prima di rendere il testo.

// adjust if non-themed. 
if (!IsThemeActive()) 
    rectt.left+= m_bmWidth+4; 
else 
    rectt.left+= BITMAP_ADJUSTMENT; 

Attraverso tentativi ed errori, ho scoperto che BITMAP_ADJUSTMENT impostato su 30 ha lavorato per me. Quindi, a seconda della voce dichiarata (disabilitata, selezionata), il rett. viene ulteriormente regolato.

// draw disabled text. 
if (disabled) 
    { 
    // draw selected text. 
    if (selected) 
     { 
     pDC->SetTextColor(::GetSysColor(COLOR_GRAYTEXT)); 
     pDC->DrawText(text, &rectt, format); 
     } 
    else 
     { 
     offset = rectt; 
     offset.left+= 1; 
     offset.right+=1; 
     offset.top+= 1; 
     offset.bottom+= 1; 
     pDC->SetTextColor(::GetSysColor(COLOR_BTNHILIGHT)); 
     pDC->DrawText(text, &offset, format); 
     pDC->SetTextColor(::GetSysColor(COLOR_GRAYTEXT)); 
     pDC->DrawText(text, &rectt, format); 
     } 
    } 
else 
    // draw normal text. 
    pDC->DrawText(text, &rectt, format); 

Infine, per rendere il segno di spunta, creo un elenco di immagini utilizzando una bitmap pre-definito (forse ho estratto da una dll di Microsoft). Di nuovo, la bitmap viene renderizzata in base allo stato dell'elemento.

// draw non-disabled bitmap. 
    if (!disabled) 
     { 
     bmp.GetBitmap(&bm); 
     m_bmWidth = bm.bmWidth; 
     imgList.Create(bm.bmWidth, bm.bmWidth, ILC_COLOR24|ILC_MASK, 1, 1); 
     imgList.Add(&bmp, COLOR_BITMAP_BACKGROUND); 

     if (checked) 
      { 
      if (!selected) 
       imgList.DrawEx(pDC, 0, CPoint(4,rect.top+4), CSize(bm.bmWidth, bm.bmWidth), COLOR_NOT_SELECTED, 0, ILD_NORMAL); 
      else 
       imgList.DrawEx(pDC, 0, CPoint(4,rect.top+4), CSize(bm.bmWidth, bm.bmWidth), 0, COLOR_SELECTED, ILD_SELECTED); 
      } 
     else 
      imgList.DrawEx(pDC, 0, CPoint(4,rect.top+4), CSize(bm.bmWidth, bm.bmWidth), 0, 0, ILD_TRANSPARENT); 
     } 
    else 
     // draw a disabled bitmap. 
     AfxDrawGrayBitmap(pDC, 4, rect.top+4, bmp, ::GetSysColor(COLOR_3DFACE)); 

La maggior parte del rendering è stata eseguita tramite un approccio iterativo che regola gli oggetti retti dopo ogni tentativo.

+0

Grazie per le informazioni. Sono stato in grado di trovare alcune di queste informazioni mentre stavo aspettando le risposte. Lo elenco sotto. – c00000fd

2

Modo alternativo con icone a colori.

Windows Vista +:

enter image description here

Windows XP:

enter image description here

function GetOptimalCheckColor(AColor: COLORREF): COLORREF; 
var 
    Gray: Integer; 
begin 
    Gray := Round((0.30 * GetRValue(AColor)) + 
       (0.59 * GetGValue(AColor)) + 
       (0.11 * GetBValue(AColor))); 
    if Gray > 127 then Result := $000000 
       else Result := $FFFFFF; 
end; 

type 
    TBitmapInfo1 = packed record 
    bmiHeader: TBitmapInfoHeader; 
    Color0: DWORD {TRGBQuad}; 
    Color1: DWORD {TRGBQuad}; 
    end; 

function CreateMonochromeBitmap(ADC: HDC; AWidth, AHeight: Integer): HBITMAP; 
const 
    Alignment = 31; 
var 
    BitmapInfo: TBitmapInfo1; 
    Data: Pointer; 
begin 
    ZeroMemory(@BitmapInfo, SizeOf(BitmapInfo)); 
    with BitmapInfo, bmiHeader do 
    begin 
     biSize := SizeOf(bmiHeader); 
     biWidth := AWidth; 
     biHeight := -AHeight; 
     biPlanes := 1; 
     biBitCount := 1; 
     biCompression := BI_RGB; 
     biSizeImage := ((AWidth + Alignment) and not Alignment) div 8; 
     biClrUsed := 2; 
     biClrImportant := biClrUsed; 
     Color0 := $000000; 
     Color1 := $FFFFFF; 
    end; 
    Result := GDICheck(CreateDIBSection(ADC, PBitmapInfo(@BitmapInfo)^, DIB_RGB_COLORS, Data, 0, 0)); 
end; 


function CreateColorBitmap(AWidth, AHeight: Integer; AColor: COLORREF; ACheckBitmap: HBITMAP): HBITMAP; 
var 
    Bitmap: Windows.TBitmap; 
    BaseDC: HDC; 
    Brush: HBRUSH; 
    ACheckDC: HDC; 
    ACheckOldDCBitmap: HBITMAP; 
    CheckBitmap: HBITMAP; 
    CheckDC: HDC; 
    CheckOldDCBitmap: HBITMAP; 
    ResultDC: HDC; 
    ResultOldBitmap: HBITMAP; 
    TempBitmap: HBITMAP; 
    TempDC: HDC; 
    TempOldDCBitmap: HBITMAP; 
    Theme: HTHEME; 
begin 
    BaseDC := GDICheck(GetDC(0)); 
    try 
    if ACheckBitmap <> 0 then 
     GDICheck(GetObject(ACheckBitmap, SizeOf(Bitmap), @Bitmap)); 
    if (ACheckBitmap <> 0) and (Bitmap.bmWidth < AWidth) or (Bitmap.bmHeight < AHeight) then 
     begin 
     CheckBitmap := GDICheck(CreateMonochromeBitmap(BaseDC, AWidth, AHeight)); 
     try 
      CheckDC := GDICheck(CreateCompatibleDC(BaseDC)); 
      try 
      CheckOldDCBitmap := SelectObject(CheckDC, CheckBitmap); 
      try 
       Brush := GDICheck(CreateSolidBrush($FFFFFF)); 
       try 
       GDICheck(FillRect(CheckDC, Rect(0, 0, AWidth, AHeight), Brush)); 
       ACheckDC := GDICheck(CreateCompatibleDC(BaseDC)); 
       try 
        ACheckOldDCBitmap := SelectObject(ACheckDC, ACheckBitmap); 
        try 
        GDICheck(BitBlt(CheckDC, (AWidth - Bitmap.bmWidth) div 2 + 1, (AHeight - Bitmap.bmHeight) div 2, 
         Bitmap.bmWidth, Bitmap.bmHeight, ACheckDC, 0, 0, SRCCOPY)); 
        finally 
        SelectObject(ACheckDC, ACheckOldDCBitmap); 
        end; 
       finally 
        DeleteDC(ACheckDC); 
       end; 
       finally 
       DeleteObject(Brush); 
       end; 
      finally 
       SelectObject(CheckDC, CheckOldDCBitmap); 
      end; 
      finally 
      DeleteDC(CheckDC); 
      end; 
     except 
      DeleteObject(CheckBitmap); 
      raise; 
     end; 
     end 
    else 
     CheckBitmap := ACheckBitmap; 
    try 
     Result := GDICheck(CreateCompatibleBitmap(BaseDC, AWidth, AHeight)); 
     ResultDC := GDICheck(CreateCompatibleDC(BaseDC)); 
     try 
     ResultOldBitmap := SelectObject(ResultDC, Result); 
     try 
      Brush := GDICheck(CreateSolidBrush(AColor)); 
      try 
      GDICheck(FillRect(ResultDC, Rect(0, 0, AWidth, AHeight), Brush)); 
      finally 
      DeleteObject(Brush); 
      end; 

      if CheckBitmap <> 0 then 
      {if IsWindowsVistaOrLater and ThemeServices.Available and ThemeServices.Enabled then 
       begin 
       Theme := OpenThemeData(0, VSCLASS_MENU); 
       try 
        DrawThemeBackground(Theme, ResultDC, MENU_POPUPCHECK, MC_CHECKMARKNORMAL, Rect(0, 0, AWidth, AHeight), nil); 
       finally 
        CloseThemeData(Theme); 
       end; 
       end 
      else} 
       begin 
       TempBitmap := GDICheck(CreateCompatibleBitmap(BaseDC, AWidth, AHeight)); 
       try 
        TempDC := GDICheck(CreateCompatibleDC(BaseDC)); 
        try 
        TempOldDCBitmap := SelectObject(TempDC, TempBitmap); 
        try 
         Brush := GDICheck(CreateSolidBrush(GetOptimalCheckColor(AColor))); 
         try 
         GDICheck(FillRect(TempDC, Rect(0, 0, AWidth, AHeight), Brush)); 
         finally 
         DeleteObject(Brush); 
         end; 
         GDICheck(MaskBlt(ResultDC, 0, 0, AWidth, AHeight, TempDC, 0, 0, CheckBitmap, 0, 0, MAKEROP4($00AA0029, SRCCOPY))); 
        finally 
         SelectObject(TempDC, TempOldDCBitmap); 
        end; 
        finally 
        DeleteDC(TempDC); 
        end; 
       finally 
        DeleteObject(TempBitmap); 
       end; 
       end; 
     finally 
      SelectObject(ResultDC, ResultOldBitmap); 
     end; 
     finally 
     DeleteDC(ResultDC); 
     end; 
    finally 
     if (CheckBitmap <> 0) and (CheckBitmap <> ACheckBitmap) then 
     DeleteObject(CheckBitmap) 
    end; 
    finally 
    ReleaseDC(0, BaseDC) 
    end; 
end; 

procedure AddMarkerMenuItem(AMenu: HMENU; AColor: COLORREF; AID: UINT; AChecked: Boolean; AEnabled: Boolean = True); 
var 
    MI: MENUITEMINFO; 
    Bitmap: Windows.TBitmap; 
    W, H: Integer; 
    Theme: HTHEME; 
    CheckBitmap: HBITMAP; 
    CheckDC: HDC; 
    CheckOldDCBitmap: HBITMAP; 
begin 
    ZeroMemory(@MI, SizeOf(MI)); 
    MI.cbSize := SizeOf(MI); 
    MI.fMask := MIIM_FTYPE or MIIM_ID or MIIM_STATE or MIIM_STRING; 
    MI.fType := MFT_STRING; 
    MI.wID := AID; 
    MI.dwTypeData := 'Marker'; 
    MI.cch := Length(MI.dwTypeData); 
    if AEnabled then MI.fState := MFS_ENABLED 
       else MI.fState := MFS_DISABLED; 
    if AChecked then 
    MI.fState := MI.fState or MFS_CHECKED; 

    CheckBitmap := GDICheck(LoadBitmap(0, PChar(OBM_CHECK))); 
    try 
    GDICheck(GetObject(CheckBitmap, SizeOf(Bitmap), @Bitmap)); 

    if IsWindowsVistaOrLater then 
     begin 
     MI.fMask := MI.fMask or MIIM_BITMAP; 
     W := GetSystemMetrics(SM_CXSMICON); 
     H := GetSystemMetrics(SM_CYSMICON); 
     if AChecked then 
      begin 
      CheckBitmap := GDICheck(LoadBitmap(0, PChar(OBM_CHECK))); 
      try 
       MI.hbmpItem := CreateColorBitmap(W, H, AColor, CheckBitmap); 
      finally 
       DeleteObject(CheckBitmap) 
      end; 
      end 
     else 
      MI.hbmpItem := CreateColorBitmap(W, H, AColor, 0); 
     end 
    else 
     begin 
     MI.fMask := MI.fMask or MIIM_CHECKMARKS; 
     MI.hbmpChecked := CreateColorBitmap(Bitmap.bmWidth - 2, Bitmap.bmHeight, AColor, CheckBitmap); 
     MI.hbmpUnchecked := CreateColorBitmap(Bitmap.bmWidth - 2, Bitmap.bmHeight, AColor, 0); 
     end; 
    finally 
    DeleteObject(CheckBitmap) 
    end; 

    InsertMenuItem(AMenu, GetMenuItemCount(AMenu), True, MI); 
end; 
+1

Sì, questo è un buon approccio. Grazie. – c00000fd

Problemi correlati