Drawing on FMX canvas with WinApi functions - delphi

This question looks very simple, with VCL this is works fine (Image is TImage on VCL):
procedure TFormMain.btnDrawBackgroundClick(Sender: TObject);
var
theme: HTHEME;
begin
theme := OpenThemeData(0, 'TASKDIALOG');
if theme <> 0 then
try
DrawThemeBackground(theme,
Image.Canvas.Handle,
TDLG_SECONDARYPANEL,
0,
Image.ClientRect,
nil);
finally
CloseThemeData(theme);
end;
end;
Question: what I should change to get the same effect with FMX (on Windows)

Based on this answer you simply can't do that.
The problem is that with Firemonkey, you only have a single device
context for the form and not one for each component. When a component
needs to be redrawn, it gets passed the forms canvas but with clipping
and co-ordinates mapped to the components location.
But there is always some workaround and you can try something like this.
procedure TFormMain.btnDrawBackgroundClick(Sender: TObject);
var
lTheme : HTHEME;
lStream : TMemoryStream;
lBitmap : Vcl.Graphics.TBitmap;
begin
lTheme := OpenThemeData(0, 'TASKDIALOG');
if lTheme <> 0 then
try
lBitmap := Vcl.Graphics.TBitmap.Create;
try
lBitmap.Width := Round(Image.Width);
lBitmap.Height := Round(Image.Height);
DrawThemeBackground(lTheme, lBitmap.Canvas.Handle, TDLG_SECONDARYPANEL, 0,
Rect(0, 0, lBitmap.Width, lBitmap.Height), nil);
lStream := TMemoryStream.Create;
try
lBitmap.SaveToStream(lStream);
Image.Bitmap.LoadFromStream(lStream);
finally
lStream.Free;
end;
finally
lBitmap.Free;
end;
finally
CloseThemeData(lTheme);
end;
end;

Related

Delphi - change ribbon menu color when VCL theme is applied

I'm using TRibbon on an Delphi XE7 application with VCL theme applied and I'd like to change the menu color (because it's difficult to see the items in dark themes), as following:
I've tried the following code, but it only works when style is disabled:
Ribbon1.ApplicationMenu.Menu.ColorMap.MenuColor:= clYellow;
Also no effect with this line:
Ribbon1.ApplicationMenu.Menu.ColorMap.MenuColor:= StyleServices.GetStyleColor(scButtonHot);
Does anyone know if it is possible?
Thanks a lot!
Create your own style with the color you like.
After some try, I found a solution. I don't know if it's the best approach, but it worked for me and could be useful for someone else.
The problem is the method bellow (Vcl.ActnMenus.pas), when StyleServices is enabled:
procedure TCustomActionPopupMenu.DrawBackground;
begin
inherited;
if StyleServices.Enabled and not StyleServices.IsSystemStyle then
StyleServices.DrawElement(Canvas.Handle, StyleServices.GetElementDetails(tmPopupBackground),
Rect(0, 0, Width, Height))
else
begin
Canvas.Brush.Color := ColorMap.MenuColor;
Canvas.FillRect(ClientRect);
end;
end;
So, in order to bypass this method, I just hooked it (adapting from here):
unit MethodHooker;
interface
uses Windows, Vcl.ActnMenus;
type
PInstruction = ^TInstruction;
TInstruction = packed record
Opcode: Byte;
Offset: Integer;
end;
TCustomActionPopupMenu = class(Vcl.ActnMenus.TCustomActionPopupMenu)
procedure DrawBackgroundEx;
end;
implementation
procedure Patch(Address: Pointer; const NewCode; Size: NativeUInt);
var
NumberOfBytes: NativeUInt;
begin
WriteProcessMemory(GetCurrentProcess, Address, #NewCode, Size, NumberOfBytes);
end;
procedure Redirect(OldAddress, NewAddress: Pointer);
var
NewCode: TInstruction;
begin
NewCode.Opcode := $E9;//jump relative
NewCode.Offset := Integer(NewAddress)-Integer(OldAddress)-SizeOf(NewCode);
Patch(OldAddress, NewCode, SizeOf(NewCode));
end;
{ TCustomActionPopupMenu }
procedure TCustomActionPopupMenu.DrawBackgroundEx;
begin
Canvas.Brush.Color := $00EEEAE9;
Canvas.FillRect(ClientRect);
end;
initialization
Redirect(#TCustomActionPopupMenu.DrawBackground, #TCustomActionPopupMenu.DrawBackgroundEx);
end.
That's it. Just save this unit and add it to the project. No need to call this anywhere.

Colors of the TDialogService.MessageDialog

Can you explain how I can get used colors of the TDialogService.MessageDialog window?
Update: Which created using this command:
TDialogService.MessageDialog('Test3: Confirmation', MsgDlgType.mtConfirmation,
[TMsgDlgBtn.mbOK], TMsgDlgBtn.mbOK, 0,
procedure(const AResult: TModalResult)
begin
end);
I need color of the bottom panel (Button parent) and background color of the message. I need this color to make my own dialog looks like FMX default dialog.
Currently I have my own highly customizable dialog which looks like this:
And also where I can get icons which used in TDialogService.MessageDialog window?
Thanks to the answer of David Heffernan and Triber:
procedure GetThemeBackgroud(AImage: TImage; ATheme: HTHEME; APartID: Integer);
var
stream: TMemoryStream;
bitmap: Vcl.Graphics.TBitmap;
begin
bitmap := Vcl.Graphics.TBitmap.Create;
try
bitmap.Width := Round(AImage.Width);
bitmap.Height := Round(AImage.Height);
DrawThemeBackground(ATheme, bitmap.Canvas.Handle, APartID, 0,
Rect(0, 0, bitmap.Width, bitmap.Height), nil);
stream := TMemoryStream.Create;
try
bitmap.SaveToStream(stream);
AImage.Bitmap.LoadFromStream(stream);
finally
stream.Free;
end;
finally
bitmap.Free;
end;
end;
procedure GetThemeBackgroud;
var
theme: HTHEME;
begin
theme := OpenThemeData(0, 'TASKDIALOG');
if theme <> 0 then
try
// Client color
GetThemeBackgroud(imgClient, theme, TDLG_PRIMARYPANEL);
// Bottom color
GetThemeBackgroud(imgBottom, theme, TDLG_SECONDARYPANEL);
finally
CloseThemeData(theme);
end;
end;
Here we should to add 2 TImages: client and buttons parents:
Now I should investigate of the system icons loading

Using canvas field to paint on a custom control

This code generates an error:
Debugger Exception Notification
Project Project3.exe raised exception class $C0000005 with message 'access violation at 0x7757e12c: write of address 0x00000014'.
if Fowner_draw then
begin
canvas.CopyRect(ClientRect, FOD_canvas, ClientRect);
end
I found the solution by deleting pasteBmp.free; line from the code below. It seems like each time copyRect is called the value of FOD_canvas field is assigned again.
procedure Tncrtile.copy_rect(Cimage:timage; source:trect; dest:trect);
var
copyBmp,pasteBmp: TBitmap;
begin
if (Cimage.Picture.Graphic <> nil) and not Cimage.Picture.Graphic.Empty then
begin
copyBmp:=TBitmap.Create;
pasteBmp:=TBitmap.Create;
try
copyBmp.Height:=Cimage.Height;
copyBmp.Width:=Cimage.Width;
pasteBmp.Height:=source.Height;
pasteBmp.Width:=source.Width;
copyBmp.canvas.Draw(0, 0, Cimage.Picture.Graphic);
pasteBmp.Canvas.CopyRect(rect(0, 0, source.Width, source.Height), copyBmp.Canvas, source);
FOD_canvas:=pasteBmp.Canvas;
finally
copyBmp.free;
pasteBmp.free;
end;
Fdrawing_rect:=dest;
Fowner_draw:=true;
invalidate;
end;
end;
Why is this happening? I tried googling and the Delphi help.
As stated in comments, the error is because you are keeping a reference to a destroyed TCanvas and then trying to draw with it. You need to keep a copy of the actual TBitmap instead and then you can draw with it when needed:
constructor Tncrtile.Create(AOwner: TComponent);
begin
inherited;
FOD_Bmp := TBitmap.Create;
end;
destructor Tncrtile.Destroy;
begin
FOD_Bmp.Free;
inherited;
end;
procedure Tncrtile.copy_rect(Cimage: TImage; Source, Dest: TRect);
var
copyBmp, pasteBmp: TBitmap;
begin
if (Cimage.Picture.Graphic <> nil) and (not Cimage.Picture.Graphic.Empty) then
begin
copyBmp := TBitmap.Create;
pasteBmp := TBitmap.Create;
try
copyBmp.Height := Cimage.Height;
copyBmp.Width := Cimage.Width;
pasteBmp.Height := Source.Height;
pasteBmp.Width := Source.Width;
copyBmp.Canvas.Draw(0, 0, Cimage.Picture.Graphic);
pasteBmp.Canvas.CopyRect(Rect(0, 0, Source.Width, Source.Height), copyBmp.Canvas, Source);
FOD_Bmp.Assign(pasteBmp);
finally
copyBmp.Free;
pasteBmp.Free;
end;
Fdrawing_rect := Dest;
Fowner_draw := True;
Invalidate;
end;
end;
...
if Fowner_draw and (not FOD_BMP.Empty) then
begin
Canvas.CopyRect(ClientRect, FOD_Bmp.Canvas, ClientRect);
end

TBitMap to PBitMap KOL

I would like to convert a TBitMap to a PBitMap in KOL.
I tried this but I get a black picture as an output:
function TbitMapToPBitMap (bitmap : TBitMap) : PbitMap;
begin
result := NIL;
if Assigned(bitmap) then begin
result := NewBitmap(bitmap.Width, bitmap.Height);
result.Draw(bitmap.Canvas.Handle, bitmap.Width, bitmap.Height);
end;
end;
Any idea what's wrong with it? I am using Delphi7.
Thank you for your help.
EDIT: New CODE:
function TbitMapToPBitMap (const src : TBitMap; var dest : PBitMap) : Bool;
begin
result := false;
if (( Assigned(src) ) and ( Assigned (dest) )) then begin
dest.Draw(src.Canvas.Handle, src.Width, src.Height);
result := true;
end;
end;
procedure TForm1.Button1Click(Sender: TObject);
var
TBitMapTest : TBitMap;
PBitMapTest : PBitMap;
begin
TBitMapTest := TBitMap.Create;
TBitMapTest.LoadFromFile ('C:\test.bmp');
PBitMapTest := NewBitMap (TBitMapTest.Width, TBitMapTest.Height);
TbitMapToPBitMap (TBitMapTest, PBitMapTest);
PBitMapTest.SaveToFile ('C:\test2.bmp');
PBitMapTest.Free;
TBitMapTest.Free;
end;
To answer your question why are your target images black; it's because you were drawing those target images to source and black they were because the NewBitmap initializes images to black.
How to copy or convert if you want a TBitmap to KOL PBitmap I found only one way (maybe I missed such function in KOL, but even if so, the method used in the following code is very efficient). You can use the Windows GDI function for bit-block transfer, the BitBlt, which just copies the specified area from one canvas to another.
The following code, when you click on the button creates the VCL and KOL bitmap instances, loads the image to a VCL bitmap, call the VCL to KOL bitmap copy function and if this function succeed, draw the KOL bitmap to the form canvas and free both bitmap instances:
uses
Graphics, KOL;
function CopyBitmapToKOL(Source: Graphics.TBitmap; Target: PBitmap): Boolean;
begin
Result := False;
if Assigned(Source) and Assigned(Target) then
begin
Result := BitBlt(Target.Canvas.Handle, 0, 0, Source.Width, Source.Height,
Source.Canvas.Handle, 0, 0, SRCCOPY);
end;
end;
procedure TForm1.Button1Click(Sender: TObject);
var
KOLBitmap: PBitmap;
VCLBitmap: Graphics.TBitmap;
begin
VCLBitmap := Graphics.TBitmap.Create;
try
VCLBitmap.LoadFromFile('d:\CGLIn.bmp');
KOLBitmap := NewBitmap(VCLBitmap.Width, VCLBitmap.Height);
try
if CopyBitmapToKOL(VCLBitmap, KOLBitmap) then
KOLBitmap.Draw(Canvas.Handle, 0, 0);
finally
KOLBitmap.Free;
end;
finally
VCLBitmap.Free;
end;
end;

How to get a image of a panel with a combobox

I need to capture an image of panel.
The problem I am running into is that if the Panel contains a TCombobox the Text does not appear.
procedure AssignPanelImageToPicture(Panel : TPanel;Image : TImage);
var
B : TBitmap;
begin
B := TBitmap.Create;
try
B.Width := Panel.Width;
B.Height := Panel.Height;
B.Canvas.Lock;
Panel.PaintTo(B.Canvas.Handle,0,0);
B.Canvas.Unlock;
Image1.Picture.Assign(B);
finally
B.Free;
end;
end;
Using this code, I drop a panel with a TCombobox on it. Then Enter a value into the Text Property. I also drop a TImage Next two it. Then I add a button to call the above
code.
Here is the result:
Is there a better way to capture a true image of the panel.
What about using the GetDC and BitBlt functions?
procedure AssignPanelImageToPicture(Panel : TPanel;Image : TImage);
var
B : TBitmap;
SrcDC: HDC;
begin
B := TBitmap.Create;
try
B.Width := Panel.Width;
B.Height := Panel.Height;
SrcDC := GetDC(Panel.Handle);
try
BitBlt(B.Canvas.Handle, 0, 0, Panel.ClientWidth, Panel.ClientHeight, SrcDC, 0, 0, SRCCOPY);
finally
ReleaseDC(Panel.Handle, SrcDC);
end;
Image.Picture.Assign(B);
finally
B.Free;
end;
end;

Resources