问题
When I try to disable a Button on a styled VCL from using the follwing line of code
TButton(Sender).enabled:= False;
I get the this result (Button disabled at runtime)
instead of this!! (Button disabled at design time)
It's really confusing to have two or more Buttons with the same color beside each other, one is disabled and the other is enabled
回答1:
The cause of this issue is located in the Paint method of the TButtonStyleHook (in the Vcl.StdCtrls unit) style hook class.
Locate this code in the method
if FPressed then
Details := StyleServices.GetElementDetails(tbPushButtonPressed)
else if MouseInControl then //this condition is triggered even if the button is disabled
Details := StyleServices.GetElementDetails(tbPushButtonHot)
else if Focused then //this condition is triggered even if the button is disabled
Details := StyleServices.GetElementDetails(tbPushButtonDefaulted)
else if Control.Enabled then
Details := StyleServices.GetElementDetails(tbPushButtonNormal)
else
Details := StyleServices.GetElementDetails(tbPushButtonDisabled);
And replace for this code
if FPressed then
Details := StyleServices.GetElementDetails(tbPushButtonPressed)
else if MouseInControl and Control.Enabled then
Details := StyleServices.GetElementDetails(tbPushButtonHot)
else if Focused and Control.Enabled then
Details := StyleServices.GetElementDetails(tbPushButtonDefaulted)
else if Control.Enabled then
Details := StyleServices.GetElementDetails(tbPushButtonNormal)
else
Details := StyleServices.GetElementDetails(tbPushButtonDisabled);
Another option is rewrite the Style hook for the TButton :
Check this code based in this article Fixing a VCL Style bug in the TButton component. The advantage of ths code is which you are fixing two issues 103708 and 103962.
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 and Control.Enabled then
LDetails := StyleServices.GetElementDetails(tbPushButtonHot)
else
if Focused and Control.Enabled 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;
initialization
TStyleManager.Engine.RegisterStyleHook(TButton, TButtonStyleHookFix);
回答2:
It's clearly a bug in the VCL. The problem appears to be that modifying the Enabled
property of a button from an event handler attached to that button does not change the visual appearance of the button. The button's behaviour is changed (you cannot click it if you set Enabled
to False
this way), but the visuals do not indicate it.
I submitted QC#103962 and no doubt a future update will fix the problem. In the meantime I offer the following workaround:
procedure TMyForm.Button1Click(Sender: TObject);
begin
Button1.Enabled := False;
Button1.Perform(CM_RECREATEWND, 0, 0);
end;
This will force the button's window handle to be recreated and this seems to be enough to get the visuals sorted. There are probably alternative ways to work around this but this was all I have found so far.
来源:https://stackoverflow.com/questions/9580563/disabling-tbutton-issue-on-a-vcl-styled-form