UPDATE
A Updated and improved version of the code shown on this article can be found in the Vcl.Styles.OwnerDrawFix unit which is part of the Vcl Styles Utils.
The Issue
When you uses the Vcl Styles, you expect which at least all the standard (and common) controls (TListBox, TEditBox, TListView, TMemo, Treeview, and so on) are skinned according to the style selected, but maybe you are observed some minor issues in controls like TListBox, TListView and TTreeView.
Check the next image, which had a form with a TListBox and a TListView
As you can see the highlight color and the checkboxes doesn’t use the Vcl Styles elements.
The Explanation
So why this happen? is a Vcl Style bug? well let me answer both questions :
First exist basically two ways how the vcl styles skin a control, if the control doesn’t have a windows handle (like the TLabel), the control is draw (usually) in the paint method using the properties and procedures of the StyleServices (TCustomStyleServices) class, otherwise if the control is a TWinControl descendent then use the Style Hooks , the styles hooks handles the windows messages of the controls wrapped by the VCL and use Windows messages and WinApi calls to draw directly over the Canvas of the control or set the properties of the controls (when is possible) like the background or foreground color using the SendMessage function.
In this point the windows messages are the key, some Windows controls doesn’t fire some messages at least which the control was in an owner draw (or Custom Draw) mode.
for example if you want to change the highlight color of a listview
1) You must receive the WM_NOTIFY message
2) then check the NM_CUSTOMDRAW notification code
3) after check for the current drawing stage (CDDS_ITEMPREPAINT in this case)
4) to finally pass a NMLVCUSTOMDRAW record with the new colors to use.
So in this case if the list view has the OwnerDraw property set to false these messages never will sent to our application. Because that is not possible implement a Style hook as there are not windows messages to process.
Note : Is technically possible write a Style hook for receive such owner draw messages, but that will implies create a style hook which need modify the ownerdraw property and then full draw the control.
The Fix
So how the style hooks are discarded, in this case we can owner draw the contols using the Vcl Styles classes and functions. (I don’t spend much time writing these routines , so can be incomplete)
OnDrawItem implementation for a TListbox
procedure TFrmMain.ListBox1DrawItem(Control: TWinControl; Index: Integer; Rect: TRect; State: TOwnerDrawState); Var LListBox : TListBox; LStyles : TCustomStyleServices; LDetails : TThemedElementDetails; begin LListBox :=TListBox(Control); LStyles :=StyleServices; //check the state if odSelected in State then LListBox.Brush.Color := LStyles.GetSystemColor(clHighlight); //get the details (states and parts) to use LDetails := StyleServices.GetElementDetails(tlListItemNormal); LListBox.Canvas.FillRect(Rect); Rect.Left:=Rect.Left+2; //draw the text LStyles.DrawText(LListBox.Canvas.Handle, LDetails, LListBox.Items[Index], Rect, [tfLeft, tfSingleLine, tfVerticalCenter]); //draw the Highlight rect using the vcl styles colors if odFocused In State then begin LListBox.Canvas.Brush.Color := LStyles.GetSystemColor(clHighlight); LListBox.Canvas.DrawFocusRect(Rect); end; end;
OnDrawItem implementation for a TListView
procedure TFrmMain.ListView1DrawItem(Sender: TCustomListView; Item: TListItem; Rect: TRect; State: TOwnerDrawState); var r : TRect; rc : TRect; ColIdx : Integer; s : string; LDetails : TThemedElementDetails; LStyles : TCustomStyleServices; BoxSize : TSize; Spacing : Integer; LColor : TColor; begin Spacing:=4; LStyles:=StyleServices; //get the color text of the items if not LStyles.GetElementColor(LStyles.GetElementDetails(ttItemNormal), ecTextColor, LColor) or (LColor = clNone) then LColor := LStyles.GetSystemColor(clWindowText); //get and set the backgroun color Sender.Canvas.Brush.Color := LStyles.GetStyleColor(scListView); //set the font color Sender.Canvas.Font.Color := LColor; Sender.Canvas.FillRect(Rect); r := Rect; inc(r.Left, Spacing); //iterate over the columns for ColIdx := 0 to TListView(Sender).Columns.Count - 1 do begin r.Right := r.Left + Sender.Column[ColIdx].Width; if ColIdx > 0 then s := Item.SubItems[ColIdx - 1] else begin BoxSize.cx := GetSystemMetrics(SM_CXMENUCHECK); BoxSize.cy := GetSystemMetrics(SM_CYMENUCHECK); s := Item.Caption; if TListView(Sender).Checkboxes then r.Left:=r.Left+BoxSize.cx+3; end; if ColIdx = 0 then begin if not IsWindowVisible(ListView_GetEditControl(Sender.Handle)) and ([odSelected, odHotLight] * State <> []) then begin if ([odSelected, odHotLight] * State <> []) then begin rc:=Rect; if TListView(Sender).Checkboxes then rc.Left:=rc.Left+BoxSize.cx+Spacing; if not TListView(Sender).RowSelect then rc.Right:=Sender.Column[0].Width; Sender.Canvas.Brush.Color := LStyles.GetSystemColor(clHighlight); //draw the highlight rect using the current the vcl styles colors Sender.Canvas.FillRect(rc); end; end; end; if TListView(Sender).RowSelect then Sender.Canvas.Brush.Color := LStyles.GetSystemColor(clHighlight); //draw the text of the item LDetails := StyleServices.GetElementDetails(tlListItemNormal); Sender.Canvas.Brush.Style := bsClear; LStyles.DrawText(Sender.Canvas.Handle, LDetails, s, r, [tfLeft, tfSingleLine, tfVerticalCenter, tfEndEllipsis]); //draw the check box if (ColIdx=0) and TListView(Sender).Checkboxes then begin rc := Rect; rc.Top := Rect.Top + (Rect.Bottom - Rect.Top - BoxSize.cy) div 2; rc.Bottom := rc.Top + BoxSize.cy; rc.Left := rc.Left + Spacing; rc.Right := rc.Left + BoxSize.cx; if Item.Checked then LDetails := StyleServices.GetElementDetails(tbCheckBoxUncheckedNormal) else LDetails := StyleServices.GetElementDetails(tbCheckBoxcheckedNormal); LStyles.DrawElement(Sender.Canvas.Handle, LDetails, Rc); end; if ColIdx=0 then r.Left:=Sender.Column[ColIdx].Width + Spacing else inc(r.Left, Sender.Column[ColIdx].Width); end; end;
After of apply the above code , this is the result
Check the source of the demo project on Github.
March 15, 2012 at 3:57 am
Excellent solution! Thank you very much! Very useful in my project!
March 15, 2012 at 2:08 pm
Thank you very much!!
March 15, 2012 at 2:50 pm
Great, I have same problem with TStatusBar.OnDrawItem. When i use VCL Styles this event was not fired.
March 16, 2012 at 12:05 am
Excellent information!
March 26, 2012 at 11:06 pm
When I use VCL Style,CheckBox and RadioButton cannot change color and fontcolor,How can I do?
March 26, 2012 at 11:16 pm
Try this article https://theroadtodelphi.wordpress.com/2012/02/06/changing-the-color-of-edit-controls-with-vcl-styles-enabled/, you can took some ideas to fix your issue.
March 30, 2012 at 4:08 am
Thank you for your answer,and it can modify components like TEdit,TRichedit etc.,but CheckBox and RadioButton need override the Paint method.When I write the code,it can’t work.
May 4, 2012 at 3:45 am
Could you add support for ImageList drawing to the ListView fix? Can’t seem to get LStyles.DrawIcon to work. Thanks!
October 24, 2012 at 6:54 am
It really works, but, the checkstate can not be changed by mouse anymore, only respond to keyboard.
June 14, 2013 at 9:38 pm
You can use the OnMouseDown method to detect the mouse clicks, try this sample.
October 11, 2013 at 5:34 pm
Do you have an idea how to do the same with TEdit, TMemo and TCheckBox?
October 11, 2013 at 5:36 pm
I mean, the highlight color
October 11, 2013 at 7:59 pm
Unfortunately is not possible change the highlight color of these controls. even using owner draw.
October 18, 2013 at 2:34 pm
HI Rodrigo,
I have a Question and also I have an odd problem.
¿ How do you detect that there are not windows themes enabled, and how do you draw items (like check-boxes) without a theme ?
I’m using a similar solution to draw check-boxes in a DBGrid, it seems to work with themes, but when I execute the program through a Remote Desktop Connection, check marks are not displayed at all.
October 18, 2013 at 2:56 pm
If the Themes are not active in the system you can use the DrawFrameControl function to draw the checkbox. and to check if the themes are enabled you can use the StyleServices.Enabled function.
October 18, 2013 at 6:49 pm
Hi Rodrigo,
Thanks for your answer, DrawFrameControl and StyleServices.Enable do the work, thanks. But I still have the problem with Remote Desktop, in both cases with and without themes the owner drawed checkboxes are not displayed, but running the program in my computer works well, any ideas ?
October 18, 2013 at 6:55 pm
This only happen when you uses the VCL Styles? or is related to the VCL?
October 18, 2013 at 7:50 pm
Hi Rodrigo,
I’m using Delphi XE2 ( Is this an Issue ? should I use a newer version ? ) in a Windows 7 environment.
The problem with RD happen with and without VCL Styles, so i think the problem is VCL, what do you think ?.
I Also downloaded and compiled your code and it behaves weird with both VCL Styles and Windows Visual Effects disabled /enabled ( in my regular environment not the RD one ).
Because I haven’t read your previous post I assume that this could be an issue with XE2, Am’I right ? sorry about that.
I realized that you speaks Spanish , Don’t you ?.
October 19, 2013 at 12:32 am
Rafa, por supuesto que hablo español (soy de Chile), el blog esta en ingles solo para hacer el contenido mas accesible a todos. Ahora volviendo a tu pregunta ya que el problema aparece incluso cuando los VCL Styles estan desactivados, entonces la causa debe ser relativa a la VCL. Si quieres prepara una aplicacion de ejemplo para reproducir el problema y enviame el codigo a mi correo.
Pingback: VCL Styles Utils – New feature | The Road to Delphi - a Blog about programming