A few weeks ago a fellow Delphi developer ask me via email about a workaround for a bug in the TButton component, when the ImageAlignment property has a value different than iaLeft (btw I just submit this bug to Quality Central).
Tipically when you uses this property in a TButton without a Vcl Style applied you had a result like this.
Now if you apply any VCL Style, this is the result
As you can see the images are not aligned properly. This is due which the TButtonStyleHook class (used by the TButton component) ignore the value of the ImageAlignment property. So to fix that we must patch the code of the Paint method TButtonStyleHook style hook.
Check the next code for a workaround
Uses Winapi.CommCtrl, Vcl.Themes, Vcl.Styles; type TCustomButtonH=class(TCustomButton); //we need this helper to access some strict private fields TButtonStyleHookHelper = class Helper for TButtonStyleHook protected function Pressed : Boolean; function DropDown: Boolean; end; //to avoid writting a lot of extra code we are to use TButtonStyleHook class and override the paint method TButtonStyleHookFix = class(TButtonStyleHook) protected procedure Paint(Canvas: TCanvas); override; end; { TButtonStyleHookFix } procedure TButtonStyleHookFix.Paint(Canvas: TCanvas); var LDetails : TThemedElementDetails; DrawRect : TRect; pbuttonImagelist : BUTTON_IMAGELIST; IW, IH, IY : Integer; LTextFormatFlags : TTextFormatFlags; ThemeTextColor : TColor; Buffer : string; BufferLength : Integer; SaveIndex : Integer; X, Y, I : Integer; BCaption : String; begin if StyleServices.Available then begin BCaption := Text; if Pressed then LDetails := StyleServices.GetElementDetails(tbPushButtonPressed) else if MouseInControl then LDetails := StyleServices.GetElementDetails(tbPushButtonHot) else if Focused then LDetails := StyleServices.GetElementDetails(tbPushButtonDefaulted) else if Control.Enabled then LDetails := StyleServices.GetElementDetails(tbPushButtonNormal) else LDetails := StyleServices.GetElementDetails(tbPushButtonDisabled); DrawRect := Control.ClientRect; StyleServices.DrawElement(Canvas.Handle, LDetails, DrawRect); if Button_GetImageList(handle, pbuttonImagelist) and (pbuttonImagelist.himl <> 0) and ImageList_GetIconSize(pbuttonImagelist.himl, IW, IH) then begin if (GetWindowLong(Handle, GWL_STYLE) and BS_COMMANDLINK) = BS_COMMANDLINK then IY := DrawRect.Top + 15 else IY := DrawRect.Top + (DrawRect.Height - IH) div 2; //here the image is drawn properly according to the ImageAlignment value case TCustomButton(Control).ImageAlignment of iaLeft : begin ImageList_Draw(pbuttonImagelist.himl, 0, Canvas.Handle, DrawRect.Left + 3, IY, ILD_NORMAL); Inc(DrawRect.Left, IW + 3); end; iaRight : begin ImageList_Draw(pbuttonImagelist.himl, 0, Canvas.Handle, DrawRect.Right - IW -3, IY, ILD_NORMAL); Dec(DrawRect.Right, IW - 3); end; iaCenter: begin ImageList_Draw(pbuttonImagelist.himl, 0, Canvas.Handle, (DrawRect.Right - IW) div 2, IY, ILD_NORMAL); end; iaTop : begin ImageList_Draw(pbuttonImagelist.himl, 0, Canvas.Handle, (DrawRect.Right - IW) div 2, 3, ILD_NORMAL); end; iaBottom: begin ImageList_Draw(pbuttonImagelist.himl, 0, Canvas.Handle, (DrawRect.Right - IW) div 2, (DrawRect.Height - IH) - 3, ILD_NORMAL); end; end; end; if (GetWindowLong(Handle, GWL_STYLE) and BS_COMMANDLINK) = BS_COMMANDLINK then begin if pbuttonImagelist.himl = 0 then Inc(DrawRect.Left, 35); Inc(DrawRect.Top, 15); Inc(DrawRect.Left, 5); Canvas.Font := TCustomButtonH(Control).Font; Canvas.Font.Style := []; Canvas.Font.Size := 12; LTextFormatFlags := TTextFormatFlags(DT_LEFT); if StyleServices.GetElementColor(LDetails, ecTextColor, ThemeTextColor) then Canvas.Font.Color := ThemeTextColor; StyleServices.DrawText(Canvas.Handle, LDetails, BCaption, DrawRect, LTextFormatFlags, Canvas.Font.Color); SetLength(Buffer, Button_GetNoteLength(Handle) + 1); if Length(Buffer) <> 0 then begin BufferLength := Length(Buffer); if Button_GetNote(Handle, PChar(Buffer), BufferLength) then begin LTextFormatFlags := TTextFormatFlags(DT_LEFT or DT_WORDBREAK); Inc(DrawRect.Top, Canvas.TextHeight('Wq') + 2); Canvas.Font.Size := 8; StyleServices.DrawText(Canvas.Handle, LDetails, Buffer, DrawRect, LTextFormatFlags, Canvas.Font.Color); end; end; if pbuttonImagelist.himl = 0 then begin if Pressed then LDetails := StyleServices.GetElementDetails(tbCommandLinkGlyphPressed) else if MouseInControl then LDetails := StyleServices.GetElementDetails(tbCommandLinkGlyphHot) else if Control.Enabled then LDetails := StyleServices.GetElementDetails(tbCommandLinkGlyphNormal) else LDetails := StyleServices.GetElementDetails(tbCommandLinkGlyphDisabled); DrawRect.Right := 35; DrawRect.Left := 3; DrawRect.Top := 10; DrawRect.Bottom := DrawRect.Top + 32; StyleServices.DrawElement(Canvas.Handle, LDetails, DrawRect); end; end else if (GetWindowLong(Handle, GWL_STYLE) and BS_SPLITBUTTON) = BS_SPLITBUTTON then begin Dec(DrawRect.Right, 15); DrawControlText(Canvas, LDetails, Text, DrawRect, DT_VCENTER or DT_CENTER); if DropDown then begin LDetails := StyleServices.GetElementDetails(tbPushButtonPressed); SaveIndex := SaveDC(Canvas.Handle); try IntersectClipRect(Canvas.Handle, Control.Width - 15, 0, Control.Width, Control.Height); DrawRect := Rect(Control.Width - 30, 0, Control.Width, Control.Height); StyleServices.DrawElement(Canvas.Handle, LDetails, DrawRect); finally RestoreDC(Canvas.Handle, SaveIndex); end; end; with Canvas do begin Pen.Color := StyleServices.GetSystemColor(clBtnShadow); MoveTo(Control.Width - 15, 3); LineTo(Control.Width - 15, Control.Height - 3); if Control.Enabled then Pen.Color := StyleServices.GetSystemColor(clBtnHighLight) else Pen.Color := Font.Color; MoveTo(Control.Width - 14, 3); LineTo(Control.Width - 14, Control.Height - 3); Pen.Color := Font.Color; X := Control.Width - 8; Y := Control.Height div 2 + 1; for i := 3 downto 0 do begin MoveTo(X - I, Y - I); LineTo(X + I + 1, Y - I); end; end; end else begin //finally the text is aligned and drawn depending of the value of the ImageAlignment property case TCustomButton(Control).ImageAlignment of iaLeft, iaRight, iaCenter : DrawControlText(Canvas, LDetails, BCaption, DrawRect, DT_VCENTER or DT_CENTER); iaBottom : DrawControlText(Canvas, LDetails, BCaption, DrawRect, DT_TOP or DT_CENTER); iaTop : DrawControlText(Canvas, LDetails, BCaption, DrawRect, DT_BOTTOM or DT_CENTER); end; end; end; end; { TButtonStyleHookHelper } function TButtonStyleHookHelper.DropDown: Boolean; begin Result:=Self.FDropDown; end; function TButtonStyleHookHelper.Pressed: Boolean; begin Result:=Self.FPressed; end;
And this is the result after of aply the above style hook
TStyleManager.Engine.RegisterStyleHook(TButton, TButtonStyleHookFix);
February 26, 2012 at 11:27 am
Very nice thank you rodrigo
February 28, 2012 at 1:57 pm
really good.
August 2, 2012 at 3:23 pm
Rodrigo, the fix for aligment worked. But there is another bug in the VCL Style.
If you set different images on the properties “HotImageIndex”, “PressedImageIndex” and “SelectedImageIndex”, the images are not altered according to the action. He keeps a picture that is on the property “ImageIndex.”
August 8, 2012 at 10:07 pm
I just uploaded a new unit to the vcl style utils project called Vcl.Styles.Fixes, this unit contains the TButtonStyleHook style hook which fix this bug http://code.google.com/p/vcl-styles-utils/source/browse/trunk/Common/Vcl.Styles.Fixes.pas