1
votes

Edit: Scroll to question bottom to see answered working code.

I am trying to change the colour of the menu bar on a Form.

I found this site with some advice:

https://www.experts-exchange.com/questions/20150240/Color-on-the-MainMenu.html

I will paste the code itself below.

Unfortunately, it doesn't quite work as I would like. The shortcomings are:

  • The colour only applies to the menu items, the remaining space to the right of the last menu item is grey. I have set the Form colour to be the same as the menu, but it doesn't change this.

  • Some of the entries in each menu drop-down should be disabled, and if I don't apply the colouring code they are correctly shown disabled. Applying the colour changes removes this visual effect, and their colour is the same as all the other entries in the menu drop-down.

My questions are:

  1. Is there a pre-rolled menu object out there that will allow me to easily colour the menu bar, including the empty space to the right, and that preserves properties like showing disabled?

  2. If not, could someone point me in the right direction as to what additional changes I need to make to the code that could fix the problems above?

I am a total newbie to Delphi (and coding, really) but if I can get the names of things to look up then I can Google and take it from there.

I'm using Delphi 10.3.

Code copied from the link above:

type
  TForm1 = class(TForm)
    .....
    procedure FormCreate(Sender: TObject);
  public
    procedure DrawMenuItem(Sender: TObject; ACanvas: TCanvas;
      ARect: TRect; Selected: Boolean);
  end;

...

procedure TForm1.DrawMenuItem(Sender: TObject; ACanvas: TCanvas;
  ARect: TRect; Selected: Boolean);
var
  S: String;
begin
  with ACanvas do
  begin
    S := TMenuItem(Sender).Caption;
    if Selected then
      Brush.Color := clHighLight
    else
      Brush.Color := clLime;
    FillRect(ARect);
    DrawText(ACanvas.Handle, PChar(S), Length(S), ARect, DT_SINGLELINE or DT_VCENTER);
  end;

end;

procedure AllOnDrawTo(M: TMenuItem; P: TMenuDrawItemEvent);
var
  I: Integer;
begin
  M.OnDrawItem := P;
  for I := 0 to M.Count-1 do
    AllOnDrawTo(M.Items[I], P);
end;

procedure TForm1.FormCreate(Sender: TObject);
var
  I: Integer;
begin
  for I := 0 to MM.Items.Count -1 do
    AllOnDrawTo(MM.Items[I], DrawMenuItem);
end;

UPDATE: @tom-brunberg gave me the required additions in a comment. Below is the updated code to implement both items I requested. I have kept the original code because I think it is interesting to see the contrast between the two options.

type
  TForm1 = class(TForm)
    .....
    procedure FormCreate(Sender: TObject);
  public
    procedure AdvancedDrawMenuItem(Sender: TObject; ACanvas: TCanvas;
      ARect: TRect; State: TOwnerDrawState);
  end;

...

procedure TForm1.AdvancedDrawMenuItem(Sender: TObject; ACanvas: TCanvas;
  ARect: TRect; State: TOwnerDrawState);
var
  S: String;
begin
  with ACanvas do
  begin
    S := TMenuItem(Sender).Caption;
    // Set the highlight colour when the menu item is selected.  Grey highlight if disabled.
    if odSelected in State then
      if odDisabled in State then
        Brush.Color := clBtnFace
      else
        Brush.Color := clGradientActiveCaption
    else
      Brush.Color := clGradientInactiveCaption;
    // Set the colour of the menu item textm, grey if disabled
    if odDisabled in State then
      Font.Color := clGray
    else
      Font.Color := clBlack;

    // this line fill rest of the top of the form the same colour as the menu.  If its the LAST menu item fill rect all way to the right.  My example has 8 menu items
    if (Parent = nil) and (TMenuItem(Sender).MenuIndex = 8) and not (odSelected in State) then
      ARect.Right := Width;

    FillRect(ARect);
    DrawText(ACanvas.Handle, PChar(S), Length(S), ARect, DT_SINGLELINE or DT_VCENTER);
  end;

end;

procedure AdvancedAllOnDrawTo(M: TMenuItem; P: TAdvancedMenuDrawItemEvent);
var
  I: Integer;
begin
  M.OnAdvancedDrawItem := P;
  for I := 0 to M.Count-1 do
    AdvancedAllOnDrawTo(M.Items[I], P);
end;

procedure TForm1.FormCreate(Sender: TObject);
var
  I: Integer;
begin
  for I := 0 to MM.Items.Count -1 do
    AdvancedAllOnDrawTo(MM.Items[I], AdvancedDrawMenuItem);
end;
1
AFIK, you can use styles for this task. Look in the project options...Delphi Coder
Keep in mind that messing with/replacing/custom drawing of default window elements you most likely couteract accessibility (primarily color blindness). What looks good to you might be a burden to someone else.AmigoJack
I agree with @AmigoJack. Also, non-trivial custom drawing frequently introduces bugs and visual glitches and flickering.Andreas Rejbrand
To fill the complete menu bar look once again on the discussion from which you copied the code. Look for a line of code under a comment like: // this line fill rest of the top of the form. To draw the items with different colors when disabled use the OnAdvancedDrawItem() event instead of OnDrawItem(). It has a parameter State: TOwnerDrawState which offers several different states, including odDisabled. See Delphi Help for detailsTom Brunberg

1 Answers

0
votes

I don't have a full answer for you, but you did say that you can 'google from there'.

Your code applies a custom drawing routine to the menu items only. If you also want to draw the menu bar itself you need to have a custom drawing routine for that. The standard TMenu OwenerDraw allows you to receive events for the menu items. The Menu does have a Window Handle, which means you can paint to it, ideally you want it to stop itself from overpainting any changes you make. Have a look at the source code for the TMenu painting (I haven't had time to do that) and see if you can spot what you need to override to paint it.

TMenu wil be wrapping the generic Windows handling for a menu, so you may be able to find out how Windows allows you to draw the menu and then implement that. (That's a fair amount of googling!)

As for the enabled/disabled feedback You can draw anything you like in the on draw event. If you want to visually display something different when the TMenuItem is disabled, check if it's disabled and then draw what you want.