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:
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?
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;
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.
Related
I am trying to write custom date picker(calendar). The dates will be displayed on the stringgrid. I am trying to fill the clicked cell with a custom color and make that selected celltext bold.
Here is my code:
type
TStringGrid = Class(Vcl.Grids.TStringGrid)
private
FHideFocusRect: Boolean;
protected
Procedure Paint;override;
public
Property HideFocusRect:Boolean Read FHideFocusRect Write FHideFocusRect;
End;
TfrmNepaliCalendar = class(TForm)
...
...
...
end;
procedure TfrmNepaliCalendar.StringGridDrawCell(Sender: TObject; ACol, ARow: Integer;
Rect: TRect; State: TGridDrawState);
begin
if gdSelected in State then begin
StringGrid.Canvas.Brush.Color := $00940A4B;
StringGrid.Canvas.FillRect(Rect);
StringGrid.Canvas.Font.Style := [fsBold];
StringGrid.Canvas.Font.Color := clHighlightText;
StringGrid.Canvas.TextOut(Rect.Left + 3, Rect.Top + 5, StringGrid.Cells[ACol,ARow]);
StringGrid.HideFocusRect := True;
end;
end;
{ TStringGrid }
procedure TStringGrid.Paint;
var
LRect: TRect;
begin
inherited;
if HideFocusRect then begin
LRect := CellRect(Col,Row);
if DrawingStyle = gdsThemed then InflateRect(LRect,-1,-1);
DrawFocusrect(Canvas.Handle,LRect)
end;
end;
The output, I am getting:
Problem #1: I need to hide that unwanted rectangle appearing as border for the selected cell
Problem #2: Avoid the cell background clipping
In the OnDrawCell procedure add just before FillRect
Rect.Left := Rect.Left-4;
Seems to work.
An alternative
The above doesn't fully solve the focus issue even with your paint procedure addon. Sometimes a white line is visible just inside the cell borders.
But the following is an alternative, that solves both your issues. It requires a little more coding, but not so much. On the other hand, subclassing TStringGrid is not needed, neither the Rect adjustment
The basis is to disable default drawing, so set the grids property DefaultDrawing := false;
and then add to the OnDrawCell event:
procedure TForm1.StringGridDrawCell(Sender: TObject; ACol, ARow: Integer;
Rect: TRect; State: TGridDrawState);
begin
if gdFixed in State then
begin
StringGrid.Canvas.Brush.Color := clGradientInactiveCaption;
StringGrid.Canvas.Font.Style := [];
StringGrid.Canvas.Font.Color := clBlack;
end
else
if gdSelected in State then
begin
StringGrid.Canvas.Brush.Color := $00940A4B;
StringGrid.Canvas.Font.Style := [fsBold];
StringGrid.Canvas.Font.Color := clHighlightText;
end
else
begin
StringGrid.Canvas.Brush.Color := $00FFFFFF;
StringGrid.Canvas.Font.Style := [];
StringGrid.Canvas.Font.Color := clWindowText;
end;
StringGrid.Canvas.FillRect(Rect);
StringGrid.Canvas.TextOut(Rect.Left + 3, Rect.Top + 5, StringGrid.Cells[ACol,ARow]);
end;
With default drawing disabled, the grid draws the grid frame and the grid lines, but leaves all other drawing to the programmer. The caveat is that you have to add fancy themed drawing yourself if you need it.
With above coding I get this result:
I assume you (want to) use the default DefaultDrawing = True setting, otherwise your question does not exist.
To get rid of the focus rect, you need to draw it again (because it is a XOR-operation, the focus rect will disappear), or prevent it from being drawn.
Drawing again is done by utilizing the OnDrawCell event:
procedure TForm1.StringGrid1DrawCell(Sender: TObject; ACol, ARow: Integer;
Rect: TRect; State: TGridDrawState);
begin
if gdFocused in State then
DrawFocusRect(StringGrid1.Canvas.Handle, Rect);
end;
Preventing it from drawing at all e.g. is done by disabling the possibility to set focus to the StringGrid. I assume you do not use its editor, so that should give no further usability concerns.
type
TStringGrid = class(Vcl.Grids.TStringGrid)
public
function CanFocus: Boolean; override;
end;
function TStringGrid.CanFocus: Boolean;
begin
Result := False;
end;
This actually is a bit strange working solution, because you are still able to tab into the control and it keeps responding to keyboard events.
I cannot reproduce your cliping problem with this code (XE2 here):
procedure TForm1.StringGrid1DrawCell(Sender: TObject; ACol, ARow: Integer;
Rect: TRect; State: TGridDrawState);
begin
if gdSelected in State then
begin
StringGrid1.Canvas.Brush.Color := $00940A4B;
StringGrid1.Canvas.FillRect(Rect);
StringGrid1.Canvas.Font.Style := [fsBold];
StringGrid1.Canvas.Font.Color := clHighlightText;
StringGrid1.Canvas.TextOut(Rect.Left + 3, Rect.Top + 5,
StringGrid1.Cells[ACol, ARow]);
end;
end;
The Rect will be and ís the correct CellRect. The cliping effect is due to something else elsewhere.
But if there really is a spurious +4 in the source code of XE8 like Tom Brunberg mentions, which is easily overcome with -4, then that obviously is a bug and should be reported.
I am creating my own OnAdvancedDrawItem to change the color of the MainMenu. It works well but I get an annoying white line at the bottom.
It disappears when running the mouse over the menu but comes back when another application is selected. How can I get rid of it?
Here is my basic code for the background coloring.
unit MenMain;
interface
uses Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,Dialogs, Menus, ImgList, StdCtrls;
type
TForm1 = class(TForm)
MainMenu1: TMainMenu;
File2: TMenuItem;
Edit1: TMenuItem;
Window1: TMenuItem;
procedure Window1AdvancedDrawItem(Sender: TObject; ACanvas: TCanvas; ARect: TRect; State: TOwnerDrawState);
private
public
end;
var
Form1: TForm1;
implementation
{$R *.dfm}
procedure TForm1.Window1AdvancedDrawItem(Sender: TObject; ACanvas: TCanvas; ARect: TRect; State: TOwnerDrawState);
begin
with TMenuItem(Sender) do
begin
with ACanvas do
begin
Brush.Color := clMoneyGreen;
Inc(ARect.Bottom,1);
FillRect(ARect);
Font.Color := clBlue;
DrawText(ACanvas.Handle, PChar(Caption),Length(Caption),ARect, DT_SINGLELINE or DT_VCENTER);
end;
end;
end;
end.
The ARect parameter of the OnAdvancedDrawItem event handler is the rcItem of the DRAWITEMSTRUCT that's passed to the WM_DRAWITEM message. The documentation has this to say about the rectangle:
A rectangle that defines the boundaries of the control to be drawn.
This rectangle is in the device context specified by the hDC member.
The system automatically clips anything that the owner window draws in
the device context for combo boxes, list boxes, and buttons, but does
not clip menu items. When drawing menu items, the owner window must
not draw outside the boundaries of the rectangle defined by the rcItem
member.
So although the device context is not clipped to the rectangle, you're responsible for not drawing outside of it. That happens when you execute Inc(ARect.Bottom,1); before filling the rectangle.
You can change the color of the grey area. Use this in OnCreate and OnCanResize
global var - fMenuBrushHandle: THandle;
var
lMenuInfo: TMenuInfo;
lMenuColor: TColor;
begin
lMenuColor := clRed;
DeleteObject(fMenuBrushHandle);
fMenuBrushHandle := CreateSolidBrush(ColorToRGB(lMenuColor));
FillChar(lMenuInfo, SizeOf(lMenuInfo), 0);
lMenuInfo.cbSize := SizeOf(lMenuInfo);
lMenuInfo.hbrBack := fMenuBrushHandle;
lMenuInfo.fMask := MIM_BACKGROUND;
SetMenuInfo(MainMenu1.Handle, lMenuInfo);
end;
or
global var - FBrush: TBrush;
var
lMenuInfo: TMenuInfo;
begin
if not Assigned(FBrush) then
FBrush := TBrush.Create;
FBrush.Color := clRed;
FBrush.Style := bsSolid;
lMenuInfo.cbSize := SizeOf(lMenuInfo);
lMenuInfo.fMask := MIM_BACKGROUND;
lMenuInfo.hbrBack := FBrush.Handle;
SetMenuInfo(MainMenu1.Handle, lMenuInfo);
end;
or even draw bitmap
global var
fMenuHandle:THandle;
fBitmap:Tbitmap;
var
lMenuInfo:TMenuInfo;
begin
if Assigned(fBitmap) then
fBitmap.Free;
fBitmap:=TBitmap.Create;
fBitmap.Width:=21;
fBitmap.Height:=Form1.Width;
DeleteObject(fMenuHandle);
fMenuHandle:=CreatePatternBrush(fBitmap.Handle);
Fillchar(lMenuInfo,SizeOf(lMenuInfo),0);
lMenuInfo.cbSize:=SizeOf(lMenuInfo);
lMenuInfo.fMask:=MIM_BACKGROUND;
lMenuInfo.hbrBack:=fMenuHandle;
SetMenuInfo(MainMenu1.Handle,lMenuInfo);
end;
Is is possible to change the item selection focus color and text color in a TListBox?
When themes are not enabled in the project, or the list box style is set to owner-draw, the selection around the item is painted blue, which I believe is globally defined by the system's appearance settings.
I would like to change the color of selected items to a custom color.
So an example, the result would be something like this:
Note the last listbox has been modified in Paint to illustrate the example.
try this:
procedure TForm1.ListBox1DrawItem(Control: TWinControl; Index: Integer;
Rect: TRect; State: TOwnerDrawState);
begin
with (Control as TListBox).Canvas do
begin
if odSelected in State then
Brush.Color := $00FFD2A6;
FillRect(Rect);
TextOut(Rect.Left, Rect.Top, (Control as TListBox).Items[Index]);
if odFocused In State then begin
Brush.Color := ListBox1.Color;
DrawFocusRect(Rect);
end;
end;
end;
I saw, Style property has to be lbOwnerDrawFixed
This helped me do something I needed to do also, namely, eliminate any visible selection. I modified the code above very slightly to accomplish this:
procedure TForm1.OnDrawItem(Control: TWinControl; Index: Integer;
Rect: TRect; State: TOwnerDrawState);
begin
with (Control as TListBox).Canvas do
begin
if odSelected in State then
begin
Brush.Color := clWhite;
Font.Color := clBlack;
end;
FillRect(Rect);
TextOut(Rect.Left, Rect.Top, (Control as TListBox).Items[Index]);
if odFocused In State then begin
Brush.Color := ListBox1.Color;
DrawFocusRect(Rect);
end;
end;
end;
Made the selected item's background color white, and it's font color black, which did what I needed. Thanks so much!
Is there any way to change the color of the nodes in a TTreeView. I want to color my treeview with a dark color and then I can't see the nodes.
alt text http://rigo.ro/temp/ChangeTreeViewNodeColor.png
It is not easily evident that you only wanted to change the line color.
Anyway, there's a message for that in the API;
uses
commctrl;
procedure TForm1.FormCreate(Sender: TObject);
begin
SendMessage(TreeView1.Handle, TVM_SETLINECOLOR, 0, ColorToRGB(clYellow));
end;
maybe you can use OnCustomDrawItem event of TTreeView:
procedure TForm1.TreeView1CustomDrawItem(Sender: TCustomTreeView;
Node: TTreeNode; State: TCustomDrawState; var DefaultDraw: Boolean);
begin
with Sender as TCustomTreeView do
begin
Canvas.Brush.Color := clBlack;
Canvas.Font.Color := clBlack
end;
end;
in a label i can add a new line like this
Label.Caption:='First line'+#13#10+'SecondLine';
can this be done in a TListView?
listItem:=listView.Items.Add;
listItem.Caption:='First line'+#13#10+'SecondLine';
thanks
It is possible to have multiline strings in a standard TListView in vsReport style, but AFAIK it doesn't support varying row heights. However, if you have all rows with the same number of lines > 1 you can achieve that quite easily.
You need to set the list view to OwnerDraw mode, first so you can actually draw the multiline captions, and second to get a chance to increase the row height to the necessary value. This is done by handling the WM_MEASUREITEM message, which is sent only for owner-drawn list views.
A small example to demonstrate this:
type
TForm1 = class(TForm)
ListView1: TListView;
procedure FormCreate(Sender: TObject);
procedure ListView1DrawItem(Sender: TCustomListView; Item: TListItem;
Rect: TRect; State: TOwnerDrawState);
private
procedure WMMeasureItem(var AMsg: TWMMeasureItem); message WM_MEASUREITEM;
end;
{ TForm1 }
procedure TForm1.FormCreate(Sender: TObject);
begin
ListView1.ViewStyle := vsReport;
ListView1.OwnerDraw := True;
ListView1.OwnerData := True;
ListView1.Items.Count := 1000;
with ListView1.Columns.Add do begin
Caption := 'Multiline string test';
Width := 400;
end;
ListView1.OnDrawItem := ListView1DrawItem;
end;
procedure TForm1.ListView1DrawItem(Sender: TCustomListView;
Item: TListItem; Rect: TRect; State: TOwnerDrawState);
begin
if odSelected in State then begin
Sender.Canvas.Brush.Color := clHighlight;
Sender.Canvas.Font.Color := clHighlightText;
end;
Sender.Canvas.FillRect(Rect);
InflateRect(Rect, -2, -2);
DrawText(Sender.Canvas.Handle,
PChar(Format('Multiline string for'#13#10'Item %d', [Item.Index])),
-1, Rect, DT_LEFT);
end;
procedure TForm1.WMMeasureItem(var AMsg: TWMMeasureItem);
begin
inherited;
if AMsg.IDCtl = ListView1.Handle then
AMsg.MeasureItemStruct^.itemHeight := 4 + 2 * ListView1.Canvas.TextHeight('Wg');
end;
I know this is an old thread, and I can't take credit for figuring this out, but to adjust the row height in a TListView you can add an image list for the StateImages and then specify the image height by expanding the StateImages item in the properties window. You don't need to load any actual images.
Sorry I can't credit the actual person who figured it out - it was on a forum I visited a while back.
I don't seem to be able to achieve this using the TListView. But using the TMS TAdvListView, you can use HTML in the item text so this will put the caption onto 2 lines:
with AdvListView1.Items.Add do
begin
Caption := '<FONT color="clBlue">Line 1<BR>Line 2</font>';
end;