Search code examples
delphiaction-menu

ActionMainMenuBar with 32x32 icon


Delphi Xe4. Form, ActionManager, ImageList (with 32x32 Icons), ActionMainMenuBar.

I can not ensure that the icons are displayed correctly. What should you do?

Pic1

At the same time, if I apply any vcl style of decoration, it displays fine. But if the style of "Windows" by default, the text moves out of the icon. Help.

Pic2

Pic3

Pic4

Sorry for the bad English.


Solution

  • This is a valid question, the TActionMainMenuBar is meant to be designed to be able to handle custom icon sizes as menu images, just as the native menus can handle them fine. One indication of that can be found in the comments in the code, f.i. in the below VCL code you can find the comment 16 is standard image size so adjust for larger images.

    The faulty code, I believe, is in TCustomMenuItem.CalcBounds in 'ActnMenus.pas'. Below excerpt is from D2007. Notice the line below I commented with some exclamation marks. After the ascendant class TCustomActionControl calculates the positioning of text and image in its CalcLayout method, the TCustomMenuItem ruins it with the hard-coded 24 in the said statement.

    procedure TCustomMenuItem.CalcBounds;
    var
      AWidth, AHeight: Integer;
      NewTextBounds: TRect;
      ImageSize: TPoint;
      ImageOffset: Integer;
    begin
      inherited CalcBounds;
      ImageSize := GetImageSize;
      AHeight := FCYMenu;
      if Separator then
        AHeight := FCYMenu div 3 * 2
      else
        // 16 is standard image size so adjust for larger images
        if ImageSize.Y > 16 then
          AHeight := ImageSize.Y + 4;
      if ActionClient = nil then exit;
      if ImageSize.X <= 16 then
        ImageOffset := 24
      else
        ImageOffset := ImageSize.X + 6;  // Leave room for an image frame
      NewTextBounds := TextBounds;
      OffsetRect(NewTextBounds, 24 - TextBounds.Left,          // <- !!!!!
        AHeight div 2 - TextBounds.Bottom div 2 - 1);
      TextBounds := NewTextBounds;
      ShortCutBounds := Rect(0,0,0,0);
      if ActionClient.ShortCut <> 0 then
      begin
        Windows.DrawText(Canvas.Handle, PChar(ActionClient.ShortCutText), -1,
          FShortCutBounds, DT_CALCRECT);
        // Left offset is determined when the item is painted to make it right justified
        FShortCutBounds.Top := TextBounds.Top;
        FShortCutBounds.Bottom := TextBounds.Bottom;
        AWidth := TextBounds.Right + FShortCutBounds.Right + ImageOffset + Spacing;
      end
      else
        AWidth := TextBounds.Right + TextBounds.Left;
      SetBounds(Left, Top, AWidth, AHeight);
    end;
    


    The 24 is an assumption based on images having 16 or less pixels width. What should be used instead is the ImageOffset value calculated just a few lines above. Replace

      OffsetRect(NewTextBounds, 24 - TextBounds.Left,
        AHeight div 2 - TextBounds.Bottom div 2 - 1);
    

    with

      OffsetRect(NewTextBounds, ImageOffset - TextBounds.Left,
        AHeight div 2 - TextBounds.Bottom div 2 - 1);
    

    and you'll have something like this:

    dropped menu

    You'll notice some other weirdness though, items not having images are still settling for a small image layout. IMO all menu items should have the same basic layout, but the design of action menus allow different layouts for individual items. One other weird thing is the checked state of an item with an image ('Action6'), although I'm not sure if I'm missing a setting here or if it would qualify as a bug otherwise.