In one of my VCL applications I'm using TSpeedButton components as an highlight effect visible when the component is hovered. I reproduced below a minimal example showing how I use a such component.
Main.h file:
#ifndef MainH
#define MainH
#include <System.Classes.hpp>
#include <Vcl.Controls.hpp>
#include <Vcl.StdCtrls.hpp>
#include <Vcl.Forms.hpp>
#include <Vcl.Buttons.hpp>
#include <Vcl.ExtCtrls.hpp>
class TMainForm : public TForm
{
__published:
TPanel *paBackground;
TLabel *laCaption;
TSpeedButton *btGlowEffect;
public:
__fastcall TMainForm(TComponent* pOwner);
};
extern PACKAGE TMainForm *MainForm;
#endif
Main.cpp file:
#include <vcl.h>
#pragma hdrstop
#include "Main.h"
#pragma package(smart_init)
#pragma resource "*.dfm"
//---------------------------------------------------------------------------
TMainForm *MainForm;
//---------------------------------------------------------------------------
__fastcall TMainForm::TMainForm(TComponent* pOwner)
: TForm(pOwner)
{}
//---------------------------------------------------------------------------
Main.dfm file:
object MainForm: TMainForm
Left = 0
Top = 0
Caption = 'MainForm'
ClientHeight = 321
ClientWidth = 678
Color = clBtnFace
Font.Charset = DEFAULT_CHARSET
Font.Color = clWindowText
Font.Height = -11
Font.Name = 'Tahoma'
Font.Style = []
OldCreateOrder = False
PixelsPerInch = 96
TextHeight = 13
object paBackground: TPanel
Left = 64
Top = 16
Width = 545
Height = 49
BevelOuter = bvNone
TabOrder = 0
object laCaption: TLabel
Left = 0
Top = 0
Width = 545
Height = 49
Align = alClient
Alignment = taCenter
Caption = 'This is a demo text'
Layout = tlCenter
ExplicitWidth = 90
ExplicitHeight = 13
end
object btGlowEffect: TSpeedButton
Left = 0
Top = 0
Width = 545
Height = 49
Align = alClient
Flat = True
ExplicitLeft = 104
ExplicitTop = 8
ExplicitWidth = 23
ExplicitHeight = 22
end
end
end
Here are the result I got on Windows 7 and Windows 10, when the component was hovered:
But on Windows 11, the result is the following:
Apparently the TSpeedButton background on hover became opaque on Windows 11. This is not acceptable for me, and I need a quick solution. How can I restore the component transparency, without if possible having to change the component, or to overload it?
In fact this is a RAD Studio XE7 bug in Windows 11, which was fixed in newer RAD Studio versions (I tested Alexandria). However I don't like the new visual, which is just a bevel frame around the button, so I publish below my own modification:
procedure TSpeedButton.Paint;
function DoGlassPaint: Boolean;
var
LParent: TWinControl;
begin
Result := csGlassPaint in ControlState;
if Result then
begin
LParent := Parent;
while (LParent <> nil) and not LParent.DoubleBuffered do
LParent := LParent.Parent;
Result := (LParent = nil) or not LParent.DoubleBuffered or (LParent is TCustomForm);
end;
end;
var
PaintRect: TRect;
DrawFlags: Integer;
Offset: TPoint;
LGlassPaint: Boolean;
Button: TThemedButton;
ToolButton: TThemedToolBar;
Details: TThemedElementDetails;
LStyle: TCustomStyleServices;
MemDC: HDC;
PaintBuffer: HPAINTBUFFER;
LCanvas: TCanvas;
overlay: TBitmap;
blendFunction: TBlendFunction;
begin
if not Enabled then
begin
FState := bsDisabled;
FDragging := False;
end
else if FState = bsDisabled then
if FDown and (GroupIndex <> 0) then
FState := bsExclusive
else
FState := bsUp;
Canvas.Font := Self.Font;
if ThemeControl(Self) then
begin
LGlassPaint := DoGlassPaint;
if LGlassPaint then
PaintBuffer := BeginBufferedPaint(Canvas.Handle, ClientRect, BPBF_TOPDOWNDIB, nil, MemDC)
else PaintBuffer := 0;
LCanvas := TCanvas.Create;
try
if LGlassPaint then
LCanvas.Handle := MemDC
else LCanvas.Handle := Canvas.Handle;
LCanvas.Font := Self.Font;
if not LGlassPaint then
if Transparent then
StyleServices.DrawParentBackground(0, LCanvas.Handle, nil, True)
else
PerformEraseBackground(Self, LCanvas.Handle)
else
FillRect(LCanvas.Handle, ClientRect, GetStockObject(BLACK_BRUSH));
if not Enabled then
Button := tbPushButtonDisabled
else
if FState in [bsDown, bsExclusive] then
Button := tbPushButtonPressed
else
if MouseInControl then
Button := tbPushButtonHot
else
Button := tbPushButtonNormal;
ToolButton := ttbToolbarDontCare;
if FFlat or TStyleManager.IsCustomStyleActive then
begin
case Button of
tbPushButtonDisabled:
Toolbutton := ttbButtonDisabled;
tbPushButtonPressed:
Toolbutton := ttbButtonPressed;
tbPushButtonHot:
Toolbutton := ttbButtonHot;
tbPushButtonNormal:
Toolbutton := ttbButtonNormal;
end;
end;
PaintRect := ClientRect;
if ToolButton = ttbToolbarDontCare then
begin
Details := StyleServices.GetElementDetails(Button);
StyleServices.DrawElement(LCanvas.Handle, Details, PaintRect);
StyleServices.GetElementContentRect(LCanvas.Handle, Details, PaintRect, PaintRect);
end
else
begin
Details := StyleServices.GetElementDetails(ToolButton);
if not TStyleManager.IsCustomStyleActive then
begin
if FFlat {and (Tag = 999)} and MouseInControl then
begin
// create an overlay bitmap
overlay := TBitmap.Create();
overlay.PixelFormat := pf32bit;
overlay.AlphaFormat := afDefined;
overlay.Transparent := True;
overlay.SetSize(PaintRect.Width, PaintRect.Height);
// fill it with blue color
overlay.Canvas.Brush.Color := clHighlight;
overlay.Canvas.Brush.Style := bsSolid;
overlay.Canvas.FillRect(PaintRect);
// initialize blend operation
blendFunction.BlendOp := AC_SRC_OVER;
blendFunction.BlendFlags := 0;
blendFunction.SourceConstantAlpha := 32;
blendFunction.AlphaFormat := 0;
// draw the hover state
AlphaBlend(LCanvas.Handle, 0, 0, PaintRect.Width, PaintRect.Height,
overlay.Canvas.Handle, 0, 0, PaintRect.Width, PaintRect.Height, blendFunction);
overlay.Free;
// enable this code to draw a rect around the selection
{
LCanvas.Pen.Color := clGradientActiveCaption;
LCanvas.MoveTo(0, PaintRect.Height - 1);
LCanvas.LineTo(0, 0);
LCanvas.LineTo(PaintRect.Width - 1, 0);
LCanvas.Pen.Color := clHighlight;
LCanvas.LineTo(PaintRect.Width - 1, PaintRect.Height - 1);
LCanvas.LineTo(0, PaintRect.Height - 1);
}
end
else
StyleServices.DrawElement(LCanvas.Handle, Details, PaintRect);
// Windows theme services doesn't paint disabled toolbuttons
// with grayed text (as it appears in an actual toolbar). To workaround,
// retrieve Details for a disabled button for drawing the caption.
if (ToolButton = ttbButtonDisabled) then
Details := StyleServices.GetElementDetails(Button);
end
else
begin
// Special case for flat speedbuttons with custom styles. The assumptions
// made about the look of ToolBar buttons may not apply, so only paint
// the hot and pressed states , leaving normal/disabled to appear flat.
if not FFlat or ((Button = tbPushButtonPressed) or (Button = tbPushButtonHot)) then
StyleServices.DrawElement(LCanvas.Handle, Details, PaintRect);
end;
StyleServices.GetElementContentRect(LCanvas.Handle, Details, PaintRect, PaintRect);
end;
Offset := Point(0, 0);
if Button = tbPushButtonPressed then
begin
// A pressed "flat" speed button has white text in XP, but the Themes
// API won't render it as such, so we need to hack it.
if (ToolButton <> ttbToolbarDontCare) and not CheckWin32Version(6) then
LCanvas.Font.Color := clHighlightText
else
if FFlat then
Offset := Point(1, 0);
end;
TButtonGlyph(FGlyph).FPaintOnGlass := LGlassPaint;
TButtonGlyph(FGlyph).FThemeDetails := Details;
TButtonGlyph(FGlyph).FThemesEnabled := True;
TButtonGlyph(FGlyph).FThemeTextColor := seFont in StyleElements;
TButtonGlyph(FGlyph).Draw(LCanvas, PaintRect, Offset, Caption, FLayout,
FMargin, FSpacing, FState, Transparent, DrawTextBiDiModeFlags(0));
if LGlassPaint then
BufferedPaintMakeOpaque(PaintBuffer, ClientRect);
finally
LCanvas.Handle := 0;
LCanvas.Free;
if LGlassPaint then
EndBufferedPaint(PaintBuffer, True);
end
end
else
begin
PaintRect := Rect(0, 0, Width, Height);
if not FFlat then
begin
DrawFlags := DFCS_BUTTONPUSH or DFCS_ADJUSTRECT;
if FState in [bsDown, bsExclusive] then
DrawFlags := DrawFlags or DFCS_PUSHED;
DrawFrameControl(Canvas.Handle, PaintRect, DFC_BUTTON, DrawFlags);
end
else
begin
if (FState in [bsDown, bsExclusive]) or
(FMouseInControl and (FState <> bsDisabled)) or
(csDesigning in ComponentState) then
DrawEdge(Canvas.Handle, PaintRect, DownStyles[FState in [bsDown, bsExclusive]],
FillStyles[Transparent] or BF_RECT)
else if not Transparent then
begin
Canvas.Brush.Color := Color;
Canvas.FillRect(PaintRect);
end;
InflateRect(PaintRect, -1, -1);
end;
if FState in [bsDown, bsExclusive] then
begin
if (FState = bsExclusive) and (not FFlat or not FMouseInControl) then
begin
Canvas.Brush.Bitmap := AllocPatternBitmap(clBtnFace, clBtnHighlight);
Canvas.FillRect(PaintRect);
end;
Offset.X := 1;
Offset.Y := 1;
end
else
begin
Offset.X := 0;
Offset.Y := 0;
end;
LStyle := StyleServices;
TButtonGlyph(FGlyph).FThemesEnabled := LStyle.Enabled;
TButtonGlyph(FGlyph).Draw(Canvas, PaintRect, Offset, Caption, FLayout, FMargin,
FSpacing, FState, Transparent, DrawTextBiDiModeFlags(0));
end;
end;
NOTE I modified directly the Vcl.Buttons.pas
file in my case, although I had good reasons to do that I know that this isn't a good solution, I don't recommend it. If you want to fix it once for all, either upgrade to a newer RAD Studio version which fix the bug, or if you don't like the new visual, as it's the case for me, implement the above solution in your own custom component inherited from TSpeedButton
.