Adding a background image to a delphi form is a topic very well covered by many articles, but most of them doesn’t work or another produces a lot of flicker when the vcl styles are enabled. The common techniques to set a background image in a form includes :
- Use a TImage component with the align property set to alClient
- Use the OnPaint event of the Form and handle the WM_ERASEBKGND message
- use the Canvas.Brush.Bitmap property of the form to assign a bitmap
- and so on.
Since the introduction of the vcl styles, now you can use the style hooks to handle the paint operations of the TWinControl descendant, the main advantage about the styles hookz is which you can override the core paint methods directly, avoiding the flicker and in most cases you do not even need to handle the windows messages. So now I will how you how you can create a style hook to add a custom background image or color to a form including the non client area.
The first step is create a new Style hook descendant of the TFormStyleHook class and override the PaintNC and PaintBackground methods.
So the class definition will look like this
type TFormStyleHookBackround=class(TFormStyleHook) strict private type TSettings = class strict private FColor: TColor; FImageLocation: string; FBitmap: TBitmap; FUseColor: Boolean; FUseImage: Boolean; FEnabled: Boolean; procedure SetColor(const Value: TColor); procedure SetImageLocation(const Value: string); procedure SetUseColor(const Value: Boolean); procedure SetUseImage(const Value: Boolean); public property UseImage : Boolean read FUseImage write SetUseImage; property UseColor : Boolean read FUseColor write SetUseColor; property Color : TColor read FColor write SetColor; property ImageLocation : string read FImageLocation write SetImageLocation; property Bitmap : TBitmap read FBitmap; property Enabled : Boolean read FEnabled write FEnabled; constructor Create; destructor Destroy;override; end; class var FNCSettings: TSettings; class var FBackGroundSettings: TSettings; class var FMergeImages: boolean; protected procedure PaintNC(Canvas: TCanvas); override; procedure PaintBackground(Canvas: TCanvas); override; class constructor Create; class destructor Destroy; public class property MergeImages: boolean read FMergeImages write FMergeImages; class property NCSettings : TSettings read FNCSettings; class property BackGroundSettings : TSettings read FBackGroundSettings; end;
Note: the above class definition includes some additional elements to store the settings of the style hook like Bitmaps and Colors used.
Painting the background
The PaintBackground method, paints the background of the form filling the control area with the current vcl style background color, in this case we are use a bitmap or a custom color depnding of the setting of the hook.
procedure TFormStyleHookBackround.PaintBackground(Canvas: TCanvas); var LRect : TRect; RBitmap : TRect; L,H : Integer; begin //if the option is not enabled use the default inherited PaintBackground method if not BackGroundSettings.Enabled then inherited else begin //get he bounds of the control (form) LRect := Rect(0, 0, Control.ClientWidth, Control.ClientHeight); //use a custom color for the background? if BackGroundSettings.UseColor then begin Canvas.Brush.Color:=BackGroundSettings.Color; Canvas.FillRect(LRect); end else //use a bitmap begin //check the size of the bitmap against the control bounds to detrine how the bitmap is drawn if (BackGroundSettings.Bitmap.Width<LRect.Width) or (BackGroundSettings.Bitmap.Height<LRect.Height) then begin Canvas.Brush.Bitmap := BackGroundSettings.BitMap; Canvas.FillRect(LRect); end else begin //check if the the background bitmap must be merged with non client area bitmap if not FMergeImages then Canvas.CopyRect(LRect,BackGroundSettings.Bitmap.Canvas,LRect) else begin RBitmap:=LRect; H:=_GetBorderSize.Top; L:=_GetBorderSize.Left; RBitmap.SetLocation(L, H); Canvas.CopyRect(LRect,BackGroundSettings.Bitmap.Canvas,RBitmap); end; end; end; end; end;
The above code will produce results like these
Using a custom color background
Using a custom image background
Painting the Non client area
To handle the paint operations over the non client area in the old versions of windows (before to windows vista) you must handle the WM_NCPAINT windows message or since Windows Vista using the DWM API you can accomplish this task. But if you uses the vcl styles only you must need override the PaintNC method.
This is the implementation of the PaintNC method to use a custom color or image in the non client area.
procedure TFormStyleHookBackround.PaintNC(Canvas: TCanvas); var LDetail: TThemedWindow; LDetails, CaptionDetails, IconDetails : TThemedElementDetails; R, R1, DrawRect, ButtonRect, TextRect: TRect; CaptionBuffer: TBitmap; FButtonState: TThemedWindow; TextFormat: TTextFormat; LText: string; SrcBackRect : TRect; begin //if the setting is not enabled use the original PaintNC method if not NCSettings.Enabled then begin inherited ; exit; end; //check the border style of the form if Form.BorderStyle = bsNone then begin MainMenuBarHookPaint(Canvas); Exit; end; {init some parameters} _FCloseButtonRect := Rect(0, 0, 0, 0); _FMaxButtonRect := Rect(0, 0, 0, 0); _FMinButtonRect := Rect(0, 0, 0, 0); _FHelpButtonRect := Rect(0, 0, 0, 0); _FSysMenuButtonRect := Rect(0, 0, 0, 0); _FCaptionRect := Rect(0, 0, 0, 0); if not StyleServices.Available then Exit; R := _GetBorderSize; {draw caption} if (Form.BorderStyle <> bsToolWindow) and (Form.BorderStyle <> bsSizeToolWin) then begin if _FFormActive then LDetail := twCaptionActive else LDetail := twCaptionInActive end else begin if _FFormActive then LDetail := twSmallCaptionActive else LDetail := twSmallCaptionInActive end; CaptionBuffer := TBitmap.Create; CaptionBuffer.SetSize(_FWidth, R.Top); {draw caption border} DrawRect := Rect(0, 0, CaptionBuffer.Width, CaptionBuffer.Height); LDetails := StyleServices.GetElementDetails(LDetail); //used for draw text in the caption //check if a must use a custom color or a bitmap if FNCSettings.UseColor then begin //use the select color to fill the background of the canvas CaptionBuffer.Canvas.Brush.Color:=FNCSettings.Color; CaptionBuffer.Canvas.FillRect(DrawRect); end else begin //use the bitmap to fill the canvas SrcBackRect.Left:=0; SrcBackRect.Top:=0; SrcBackRect.Width:=DrawRect.Width; SrcBackRect.Height:=DrawRect.Height; //SrcBackRect.SetLocation(FNCSettings.Bitmap.Width-DrawRect.Width, 0); //SrcBackRect.SetLocation(_GetBorderSize.Width, 0); CaptionBuffer.Canvas.CopyRect(DrawRect, FNCSettings.Bitmap.Canvas,SrcBackRect); end; TextRect := DrawRect; CaptionDetails := LDetails; {draw icon} if (biSystemMenu in TCustomFormHack(Form).BorderIcons) and (Form.BorderStyle <> bsDialog) and (Form.BorderStyle <> bsToolWindow) and (Form.BorderStyle <> bsSizeToolWin) then begin IconDetails := StyleServices.GetElementDetails(twSysButtonNormal); if not StyleServices.GetElementContentRect(0, IconDetails, DrawRect, ButtonRect) then ButtonRect := Rect(0, 0, 0, 0); R1 := Rect(0, 0, GetSystemMetrics(SM_CXSMICON), GetSystemMetrics(SM_CYSMICON)); RectVCenter(R1, ButtonRect); if ButtonRect.Width > 0 then DrawIconEx(CaptionBuffer.Canvas.Handle, R1.Left, R1.Top, _GetIconFast.Handle, 0, 0, 0, 0, DI_NORMAL); Inc(TextRect.Left, ButtonRect.Width + 5); _FSysMenuButtonRect := ButtonRect; end else Inc(TextRect.Left, R.Left); {draw buttons} if (biSystemMenu in TCustomFormHack(Form).BorderIcons) then begin if (Form.BorderStyle <> bsToolWindow) and (Form.BorderStyle <> bsSizeToolWin) then begin if (_FPressedButton = HTCLOSE) and (_FHotButton = HTCLOSE) then FButtonState := twCloseButtonPushed else if _FHotButton = HTCLOSE then FButtonState := twCloseButtonHot else if _FFormActive then FButtonState := twCloseButtonNormal else FButtonState := twCloseButtonDisabled; end else begin if (_FPressedButton = HTCLOSE) and (_FHotButton = HTCLOSE) then FButtonState := twSmallCloseButtonPushed else if _FHotButton = HTCLOSE then FButtonState := twSmallCloseButtonHot else if _FFormActive then FButtonState := twSmallCloseButtonNormal else FButtonState := twSmallCloseButtonDisabled; end; LDetails := StyleServices.GetElementDetails(FButtonState); if not StyleServices.GetElementContentRect(0, LDetails, DrawRect, ButtonRect) then ButtonRect := Rect(0, 0, 0, 0); StyleServices.DrawElement(CaptionBuffer.Canvas.Handle, LDetails, ButtonRect); if ButtonRect.Left > 0 then TextRect.Right := ButtonRect.Left; _FCloseButtonRect := ButtonRect; end; if (biMaximize in TCustomFormHack(Form).BorderIcons) and (biSystemMenu in TCustomFormHack(Form).BorderIcons) and (Form.BorderStyle <> bsDialog) and (Form.BorderStyle <> bsToolWindow) and (Form.BorderStyle <> bsSizeToolWin) then begin if Form.WindowState = wsMaximized then begin if (_FPressedButton = HTMAXBUTTON) and (_FHotButton = HTMAXBUTTON) then FButtonState := twRestoreButtonPushed else if _FHotButton = HTMAXBUTTON then FButtonState := twRestoreButtonHot else if _FFormActive then FButtonState := twRestoreButtonNormal else FButtonState := twRestoreButtonDisabled; end else begin if (_FPressedButton = HTMAXBUTTON) and (_FHotButton = HTMAXBUTTON) then FButtonState := twMaxButtonPushed else if _FHotButton = HTMAXBUTTON then FButtonState := twMaxButtonHot else if _FFormActive then FButtonState := twMaxButtonNormal else FButtonState := twMaxButtonDisabled; end; LDetails := StyleServices.GetElementDetails(FButtonState); if not StyleServices.GetElementContentRect(0, LDetails, DrawRect, ButtonRect) then ButtonRect := Rect(0, 0, 0, 0); if ButtonRect.Width > 0 then StyleServices.DrawElement(CaptionBuffer.Canvas.Handle, LDetails, ButtonRect); if ButtonRect.Left > 0 then TextRect.Right := ButtonRect.Left; _FMaxButtonRect := ButtonRect; end; if (biMinimize in TCustomFormHack(Form).BorderIcons) and (biSystemMenu in TCustomFormHack(Form).BorderIcons) and (Form.BorderStyle <> bsDialog) and (Form.BorderStyle <> bsToolWindow) and (Form.BorderStyle <> bsSizeToolWin) then begin if (_FPressedButton = HTMINBUTTON) and (_FHotButton = HTMINBUTTON) then FButtonState := twMinButtonPushed else if _FHotButton = HTMINBUTTON then FButtonState := twMinButtonHot else if _FFormActive then FButtonState := twMinButtonNormal else FButtonState := twMinButtonDisabled; LDetails := StyleServices.GetElementDetails(FButtonState); if not StyleServices.GetElementContentRect(0, LDetails, DrawRect, ButtonRect) then ButtonRect := Rect(0, 0, 0, 0); if ButtonRect.Width > 0 then StyleServices.DrawElement(CaptionBuffer.Canvas.Handle, LDetails, ButtonRect); if ButtonRect.Left > 0 then TextRect.Right := ButtonRect.Left; _FMinButtonRect := ButtonRect; end; if (biHelp in TCustomFormHack(Form).BorderIcons) and (biSystemMenu in TCustomFormHack(Form).BorderIcons) and ((not (biMaximize in TCustomFormHack(Form).BorderIcons) and not (biMinimize in TCustomFormHack(Form).BorderIcons)) or (Form.BorderStyle = bsDialog)) then begin if (_FPressedButton = HTHELP) and (_FHotButton = HTHELP) then FButtonState := twHelpButtonPushed else if _FHotButton = HTHELP then FButtonState := twHelpButtonHot else if _FFormActive then FButtonState := twHelpButtonNormal else FButtonState := twHelpButtonDisabled; LDetails := StyleServices.GetElementDetails(FButtonState); if not StyleServices.GetElementContentRect(0, LDetails, DrawRect, ButtonRect) then ButtonRect := Rect(0, 0, 0, 0); if ButtonRect.Width > 0 then StyleServices.DrawElement(CaptionBuffer.Canvas.Handle, LDetails, ButtonRect); if ButtonRect.Left > 0 then TextRect.Right := ButtonRect.Left; _FHelpButtonRect := ButtonRect; end; {draw text} TextFormat := [tfLeft, tfSingleLine, tfVerticalCenter]; if Control.UseRightToLeftReading then Include(TextFormat, tfRtlReading); LText := Text; StyleServices.DrawText(CaptionBuffer.Canvas.Handle, CaptionDetails, LText, TextRect, TextFormat); _FCaptionRect := TextRect; {draw caption buffer} Canvas.Draw(0, 0, CaptionBuffer); CaptionBuffer.Free; {draw menubar} MainMenuBarHookPaint(Canvas); {draw left border} DrawRect := Rect(0, R.Top, R.Left, _FHeight - R.Bottom); if DrawRect.Bottom - DrawRect.Top > 0 then //use a color? if FNCSettings.UseColor then begin Canvas.Brush.Color:=FNCSettings.Color; Canvas.FillRect(DrawRect); end else begin if (DrawRect.Height<=FNCSettings.BitMap.Height) and (DrawRect.Width<=FNCSettings.BitMap.Width) then Canvas.CopyRect(DrawRect,FNCSettings.Bitmap.Canvas,DrawRect) else Canvas.StretchDraw(DrawRect, FNCSettings.BitMap); end; {draw right border} DrawRect := Rect(_FWidth - R.Right, R.Top, _FWidth, _FHeight - R.Bottom); if DrawRect.Bottom - DrawRect.Top > 0 then //use a color? if FNCSettings.UseColor then begin Canvas.Brush.Color:=FNCSettings.Color; Canvas.FillRect(DrawRect); end else begin if (DrawRect.Height<=FNCSettings.BitMap.Height) and (Control.Width<=FNCSettings.BitMap.Width) then Canvas.CopyRect(DrawRect,FNCSettings.Bitmap.Canvas,DrawRect) else Canvas.StretchDraw(DrawRect, FNCSettings.BitMap); end; {draw Bottom border} DrawRect := Rect(0, _FHeight - R.Bottom, _FWidth, _FHeight); if DrawRect.Bottom - DrawRect.Top > 0 then //use a color? if FNCSettings.UseColor then begin Canvas.Brush.Color:=FNCSettings.Color; Canvas.FillRect(DrawRect); end else begin if (DrawRect.Height<=FNCSettings.BitMap.Height) and (Control.Width<=FNCSettings.BitMap.Width) then Canvas.CopyRect(DrawRect,FNCSettings.Bitmap.Canvas,DrawRect) else begin SrcBackRect.Left:=0; SrcBackRect.Top:=0; SrcBackRect.Width:=DrawRect.Width; SrcBackRect.Height:=DrawRect.Height; SrcBackRect.SetLocation(FNCSettings.BitMap.Width-DrawRect.Width, 0); Canvas.CopyRect(DrawRect, FNCSettings.BitMap.Canvas,SrcBackRect); end; end; end;
And the result is
Using a custom color in the non client area
Using a custom image in the non client area
Putting it all together
Finally if you mix both methods (the background and the non client area) you can get very nice results
Using the class
The full source code of the TFormStyleHookBackround class is available here and is part of the vcl style utils project. to use it just include a reference to the Vcl.Styles.FormStyleHooks unit in your project and register the style hook like so.
TStyleManager.Engine.RegisterStyleHook(TFrmMain, TFormStyleHookBackround);
To modify the background image of the form, use this code
TFormStyleHookBackround.BackGroundSettings.UseImage := True; TFormStyleHookBackround.BackGroundSettings.ImageLocation := 'image.png'; //set the location of the image
To modify the background color of the form, use this code
TFormStyleHookBackround.BackGroundSettings.UseColor := True; TFormStyleHookBackround.BackGroundSettings.Color := clRed;//set the colot
After of modify background color or image you must repaint the form sending the WM_PAINT message
Check this sample
Var LIndex: Integer; begin for LIndex := 0 to ComponentCount - 1 do if Components[LIndex] is TWinControl then begin TWinControl(Components[LIndex]).Invalidate; TWinControl(Components[LIndex]).Perform(WM_PAINT, 0, 0); end; Self.Invalidate; Self.Perform(WM_PAINT, 0, 0); end;
To customize the color of the non client area of the form use this code
TFormStyleHookBackround.NCSettings.UseColor:= True; TFormStyleHookBackround.NCSettings.Color:= clGreen; //set the color
To assign an image to the non client area of the form use this code
TFormStyleHookBackround.NCSettings.UseImage := True; TFormStyleHookBackround.NCSettings.ImageLocation := 'image.png'; //set the location of the image
After of modify color or image of the non client area you must repaint the NC using the WM_NCPAINT message
SendMessage(Handle, WM_NCPAINT, 0, 0);
Download the demo application (binaries) from here.
March 27, 2012 at 5:03 am
Hello,
Man, you’re doing great job with the VCL Styles, even better than guys from EMB are. Keep it up, it’s top notch!
Much love,
Patryk.
March 27, 2012 at 2:24 pm
Thanks for you comments. I’m just extending the vcl styles and looking for new ways to use it, but the full credit is for Embarcadero and this great new feature called Vcl Styles.
March 27, 2012 at 9:35 am
Interesting article – many thanks for sharing this useful technique.
Great for those of us who fancy dressing up our apps without having to make the move to FireMonkey until we (and it) are ready.
March 27, 2012 at 2:20 pm
Jon, thanks for your comments.
March 27, 2012 at 2:00 pm
Wow!
Keep it coming Rodrigo!
:)
March 27, 2012 at 2:20 pm
François, thanks for your support.
March 28, 2012 at 7:32 pm
Well done Rodrigo!!! That’s the idea allow the community to extend the do great stuffs like what you are doing!!!
Pingback: Delphi XE3? - Seite 55 - Delphi-PRAXiS
February 18, 2013 at 12:57 pm
hola!!!! gracias por el articulo.
no se si entiendes el Español pero mi ingles es muy malo
quiero hacerte una consulta ya no soy experto
como implementar esta clase en una aplicación MDI
te comento con mas detalles al mostrar el formulario Main con WindowState = wsNormal este pinta el el fondo del color del estilo
pero al mostrar el Main con WindowState = wsMaximized es pinta el fondo diferente como puedo hacer el efecto de pintar el fondo
February 24, 2013 at 3:08 pm
Thank you for your great work.
I have used your TFormStyleHookNC it worked great but if you add a TMainMenu the MainMenu will disappear if you disable the vcl style in NC areas.
I used your code from your WMIDelphiCodeGenerator.
Any idea to solve it?
February 25, 2013 at 12:01 pm
Try the answer to this question http://stackoverflow.com/questions/11963441/tmainmenu-is-not-shown-when-the-vcl-styles-is-removed-from-the-nc-area
March 3, 2014 at 6:20 am
Please HELP!!! I can’t make it work!!! The Project compiles, but nothing happens. The windows style remains the standard one!!
This is my sample project source:
program Project1;
uses
Vcl.Forms,
Vcl.Themes,
Vcl.Styles,
Vcl.Styles.FormStyleHooks in ‘Vcl.Styles.FormStyleHooks.pas’,
Winapi.Windows, Winapi.Messages,
Unit1 in ‘Unit1.pas’ {Form1};
{$R *.res}
begin
Application.Initialize;
Application.MainFormOnTaskbar := True;
Application.CreateForm(TForm1, Form1);
TStyleManager.Engine.RegisterStyleHook(TForm1, TFormStyleHookBackground);
Application.Run;
end.
unit Unit1;
interface
uses
Winapi.Windows, Winapi.Messages, System.SysUtils, System.Variants, System.Classes, Vcl.Graphics,
Vcl.Controls, Vcl.Forms, Vcl.Dialogs, Vcl.Styles.FormStyleHooks, Vcl.StdCtrls;
type
TForm1 = class(TForm)
Button1: TButton;
procedure Button1Click(Sender: TObject);
private
{ Private declarations }
public
procedure UPD;
{ Public declarations }
end;
var
Form1: TForm1;
implementation
{$R *.dfm}
procedure TForm1.Button1Click(Sender: TObject);
begin
TFormStyleHookBackground.BackGroundSettings.Enabled := true;
TFormStyleHookBackground.BackGroundSettings.UseImage := True;
TFormStyleHookBackground.BackGroundSettings.ImageLocation := ‘C:\Progetti\RADStudioXE2\TestStyles\titanium-texture.bmp’;
TFormStyleHookBackground.NCSettings.Enabled := true;
TFormStyleHookBackground.NCSettings.UseImage := True;
TFormStyleHookBackground.NCSettings.ImageLocation := ‘C:\Progetti\RADStudioXE2\TestStyles\titanium-texture.bmp’;
Form1.UPD;
SendMessage(Form1.Handle, WM_NCPAINT, 0, 0);
end;
procedure TForm1.UPD;
Var
LIndex: Integer;
begin
for LIndex := 0 to ComponentCount – 1 do
if Components[LIndex] is TWinControl then
begin
TWinControl(Components[LIndex]).Invalidate;
TWinControl(Components[LIndex]).Perform(WM_PAINT, 0, 0);
end;
Self.Invalidate;
Self.Perform(WM_PAINT, 0, 0);
end;
end.
March 17, 2014 at 11:40 am
Try enabling the style hook in the initialization part of the unit1
March 18, 2016 at 3:20 pm
Hola tengo un problema. porque no pinta el area non client area de los cuadros de dialogos(mensajes que muestra mi Formulario) solo pinta el area non client del Formulario.(que me falta agregar a mi aplicacion)
procedure TForm1.FormCreate(Sender: TObject);
begin
TFormStyleHookBackground.NCSettings.Enabled:=true;
end;
Initialization
TStyleManager.Engine.RegisterStyleHook(TForm, TFormStyleHookBackground);
TFormStyleHookBackground.NCSettings.UseColor := True;
TFormStyleHookBackground.NCSettings.Color :=clWebBlue;
March 23, 2016 at 12:27 am
Hola, este tipo de style hook solo funciona en formularios que decienden del TForm, asi que debes verificar la clase del dialogo que estas tratando de modificar.
June 22, 2016 at 4:22 pm
Hello, excellent article.
I want to know if you can change the colors of TComboBox (especially button) and round buttons.
If so, this tool is perfect.
Thank you. Santiago.
P / D: Sorry for my English.
June 22, 2016 at 4:29 pm
Hola, excelente artículo.
Quiero saber si es posible modificar los colores del TComboBox (especialmente el del botón) y redondear los botones.
Si es así, esta herramienta es perfecta.
Gracias. Santiago.
Navegando por el Post me dí cuenta que eras de Chile. Un abrazo.
June 22, 2016 at 5:08 pm
Hola Santiago, para modificar la apariencia del TComboBox de la forma que tu deseas, debes escribir un nuevo Style Hook basado en la clase TComboBoxStyleHook, luego de eso puedes manipular la apariencia del control como tu lo desees.
June 23, 2016 at 7:01 am
Mil gracias. No lo he visto aún, pero ¿habrá algún ejemplo de esto entre las Demos que vienen en el paquete?
Gracias.
June 23, 2016 at 11:02 am
Hola, No existe un demo con exactamente lo que necesitas, pero como introducion al style hook que usa el combobox puedes ver esta respuesta http://stackoverflow.com/questions/16538890/how-to-color-the-background-of-a-tcombobox-with-vcl-styles-enabled/16541786#16541786
June 23, 2016 at 11:43 am
Gracias de nuevo.
Cuando haga algo al respecto, y me salta bien, lo publico por aquí para todos.
Santiago.
January 12, 2017 at 6:28 am
Hello,
I noticed that there must be a problem adding a background image when ‘FormStyle = MDIForm’ ?
George Bakas