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
Related
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.
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;
why when we do ttexture.assign(aTBitmap) then the bitmap data is not copied in the texture if TCanvasStyle.NeedGPUSurface is set. If we do the same with a TBitmapSurface then the data is copied... why such strange behavior ?
procedure TTexture.Assign(Source: TPersistent);
var
M: TBitmapData;
begin
if Source is TBitmap then
begin
if FHandle <> 0 then
TContextManager.DefaultContextClass.FinalizeTexture(Self);
FPixelFormat := TBitmap(Source).PixelFormat;
FStyle := [TTextureStyle.Dynamic];
FTextureScale := TBitmap(Source).BitmapScale;
SetSize(TBitmap(Source).Width, TBitmap(Source).Height);
if not (TCanvasStyle.NeedGPUSurface in TBitmap(Source).CanvasClass.GetCanvasStyle) then
begin
if TBitmap(Source).Map(TMapAccess.Read, M) then
try
UpdateTexture(M.Data, M.Pitch);
finally
TBitmap(Source).Unmap(M);
end;
end;
end else if Source is TBitmapSurface then
begin
if FHandle <> 0 then
TContextManager.DefaultContextClass.FinalizeTexture(Self);
FStyle := [TTextureStyle.Dynamic];
SetSize(TBitmapSurface(Source).Width, TBitmapSurface(Source).Height);
UpdateTexture(TBitmapSurface(Source).Bits, TBitmapSurface(Source).Pitch);
end else
inherited ;
end;
Application works as I'd like but there are quite big memory leakage.
Every event that throttles one thread gives me 4 TBitmaps and 2 TStrokeBrush that are lost.
The procedure DrawSine(); is triggered in Execute in Synchronize statement:
procedure SineThread.DrawSine();
var
sin_T : Extended;
Point2 : TPoint;
I : Integer;
begin
TempBitmap.SetSize(Twidth, Theight);
TempBitmap.Canvas.BeginScene();
TempBitmap.Canvas.Stroke.Kind := TBrushKind.bkSolid;
TempBitmap.Canvas.Stroke.Color := claLime;
TempBitmap.Canvas.Clear(TAlphaColorRec.Black);
for I := 0 to Twidth do
begin
sin_T := Sin(((I - Tphas)/100.0) * Tfreq);
Point2.X := Round(I);
Point2.Y := Round(sin_T * Tampl) + Round(Theight/2.0);
if I = 0 then
begin
Point1.X := Round(I);
Point1.Y := Round(sin_T * Tampl) + Round(Theight/2.0);
TempBitmap.Canvas.DrawLine(Point1, Point2, 1.0, TempBrush);
end
else
begin
if I = Twidth then
begin
TempBitmap.Canvas.DrawLine(Point1, Point2, 1.0, TempBrush);
Point1.X := Round(I);
Point1.Y := Round(Theight/2.0);
end
else
begin
TempBitmap.Canvas.DrawLine(Point1, Point2, 1.0, TempBrush);
Point1.X := Point2.X;
Point1.Y := Point2.Y;
end;
end;
end;
TempBitmap.Canvas.EndScene();
end;
SineThread Constructor and Destructor:
constructor SineThread.Create(CreateSuspended: Boolean);
begin
inherited Create(CreateSuspended);
try
TempBitmap := TBitmap.Create();
TempBrush := TStrokeBrush.Create(TBrushKind.bkSolid, TAlphaColorRec.White);
finally
Twidth := 0;
Theight := 0;
Tampl := 0;
Tphas := 0;
Tfreq := 0;
Point1 := Point(0,0);
end;
end;
destructor SineThread.Destroy();
begin
inherited Destroy();
TempBitmap.Free();
TempBrush.Free();
end;
OnTerminate when finishing thread looks like:
procedure TForm1.OnTerminateProc1(Sender: TObject);
var
TempStream : TMemoryStream;
begin
try
TempStream := TMemoryStream.Create();
finally
(Sender as SineThread).GetBitmap.SaveToStream(TempStream);
Image1.Bitmap.LoadFromStream(TempStream);
TempStream.Free();
end;
end;
The Trigger() procedure is started every the the value on TrackBars change:
procedure TForm1.Trigger(Sender: TObject);
var
sine1_thread : SineThread;
sine2_thread : SineThread;
sineSum_thread : SineSumThread;
begin
try
begin
sine1_thread := SineThread.Create(True);
sine2_thread := SineThread.Create(True);
sineSum_thread := SineSumThread.Create(True);
end;
finally
begin
sine1_thread.SetSineParams(TrackBar1.Value, TrackBar2.Value, TrackBar3.Value);
sine1_thread.SetImageParams(Trunc(Image1.Width), Trunc(Image1.Height));
sine1_thread.FreeOnTerminate := True;
sine1_thread.OnTerminate := OnTerminateProc1;
sine1_thread.Start();
sine2_thread.SetSineParams(TrackBar4.Value, TrackBar5.Value, TrackBar6.Value);
sine2_thread.SetImageParams(Trunc(Image2.Width), Trunc(Image2.Height));
sine2_thread.FreeOnTerminate := True;
sine2_thread.OnTerminate := OnTerminateProc2;
sine2_thread.Start();
sineSum_thread.SetSineParams(TrackBar1.Value, TrackBar2.Value, TrackBar3.Value, TrackBar4.Value, TrackBar5.Value, TrackBar6.Value);
sineSum_thread.SetImageParams(Trunc(Image3.Width), Trunc(Image3.Height));
sineSum_thread.FreeOnTerminate := True;
sineSum_thread.OnTerminate := OnTerminateProc3;
sineSum_thread.Start();
end;
end;
end;
It seems that the threads are not being destroyed. Since they are freed on terminate that seems odd. You set FreeOnTerminate, so if the threads terminate they will be destroyed.
Let us assume that the threads to terminate. In which case the explanation is that your destructor is missing override directive. It should be declared like this:
destructor Destroy; override;
My psychic debugging skills (not infallible) tell me that you missed the override. So when Destroy is called, the base class method runs and not yours.
The most effective way to track down leaks is to use the full version of FastMM. When configured correctly that will give stack traces for the allocation associated with a leak. And lots of other useful stuff to help find defects earlier.
Don't use finally in a constructor's implementation. If an exception is raised then, the instance will be destroyed and so your finally block is pointless.
Use the correct resource acquisition pattern:
obj := TMyClass.Create;
try
obj.Foo; // do stuff with obj
finally
obj.Free;
end;
As you write it, an exception raise in the constructor will lead to you calling Free on an uninitialized instance variable.
Deallocate resource in reverse order to their acquisition. That means that your destructor should be written:
destructor SineThread.Destroy;
begin
TempBrush.Free;
TempBitmap.Free;
inherited;
end;
The finally in TForm1.Trigger is also wrong. A finally block runs no matter what. If for some reason you fail to create an object, you must not carry on as if that failure did not happen. You use finally to protect a resource. You acquire a resource, and use the finally block to make sure that you release it no matter what.
There's absolutely no need for threads in your program. As you explained in your previous question, and mentioned again here, you use Synchronize to put all the work onto the main threads. This renders the threads nugatory. I don't know why you chose to use threads. Perhaps you thought that by doing so, your program would perform better. That is not always the case, and certainly not when you implement threading the way you have done.
Programming is hard enough at the best of times, without needless complexity. Especially when you have not yet mastered the language. My advice is to do that first before moving on to advanced topics like threading.
Finally, you must learn to present complete, but cut-down examples for questions like this. You omitted quite a bit of code, and if I am right, the most important bit of code, that which causes the leak, was omitted.
One general rule to remember is this one:
When an object's constructor raises an exception, it's destructor is
called automatically.
So the try..finally sequence in SineThread.Create is not needed.
In an object's destructor, call inherited as last item.
constructor SineThread.Create(CreateSuspended: Boolean);
begin
inherited Create(CreateSuspended);
TempBitmap := TBitmap.Create();
TempBrush := TStrokeBrush.Create(TBrushKind.bkSolid, TAlphaColorRec.White);
Twidth := 0;
Theight := 0;
Tampl := 0;
Tphas := 0;
Tfreq := 0;
Point1 := Point(0,0);
end;
destructor SineThread.Destroy();
begin
TempBitmap.Free();
TempBrush.Free();
inherited;
end;
same goes for OnTerminateProc1 :
procedure TForm1.OnTerminateProc1(Sender: TObject);
var
TempStream : TMemoryStream;
begin
TempStream := TMemoryStream.Create();
try
(Sender as SineThread).GetBitmap.SaveToStream(TempStream);
Image1.Bitmap.LoadFromStream(TempStream);
finally
TempStream.Free();
end;
end;
no need for try..finally inTrigger() :
procedure TForm1.Trigger(Sender: TObject);
var
sine1_thread : SineThread;
sine2_thread : SineThread;
sineSum_thread : SineSumThread;
begin
sine1_thread := SineThread.Create(True);
sine2_thread := SineThread.Create(True);
sineSum_thread := SineSumThread.Create(True);
sine1_thread.SetSineParams(TrackBar1.Value, TrackBar2.Value, TrackBar3.Value);
sine1_thread.SetImageParams(Trunc(Image1.Width), Trunc(Image1.Height));
sine1_thread.FreeOnTerminate := True;
sine1_thread.OnTerminate := OnTerminateProc1;
sine1_thread.Start();
sine2_thread.SetSineParams(TrackBar4.Value, TrackBar5.Value, TrackBar6.Value);
sine2_thread.SetImageParams(Trunc(Image2.Width), Trunc(Image2.Height));
sine2_thread.FreeOnTerminate := True;
sine2_thread.OnTerminate := OnTerminateProc2;
sine2_thread.Start();
sineSum_thread.SetSineParams(TrackBar1.Value, TrackBar2.Value, TrackBar3.Value, TrackBar4.Value, TrackBar5.Value, TrackBar6.Value);
sineSum_thread.SetImageParams(Trunc(Image3.Width), Trunc(Image3.Height));
sineSum_thread.FreeOnTerminate := True;
sineSum_thread.OnTerminate := OnTerminateProc3;
sineSum_thread.Start();
end;
Hi there I have a problem
I need to auto fill in information from the database, but if i do it like this:
leemail.text := dm.atInlog['email'];
lenaam.text := dm.atInlog['naam'];
leAdres.text := dm.atInlog['adres'];
lePostcode.text := dm.atInlog['postcode'];
leTelefoonnummer.text := dm.atInlog['telefoon'];
leWoonplaats.Text := dm.atInlog['Woonplaats']
It just takes the first row. I want a specific row.
I can make it work with a button like this:
procedure TfmKlant.BTGegevensClick(Sender: TObject);
begin
//vraag gegevens van gebruiker op
dm.atInlog.Open;
while (not gevonden) and (not dm.atInlog.eof) do
begin
if dm.atInlog['email'] = fminloggen.inlognaam
then
begin
// plaats gegevens in de textboxen
gevonden := true;
leemail.text := dm.atInlog['email'];
lenaam.text := dm.atInlog['naam'];
leAdres.text := dm.atInlog['adres'];
lePostcode.text := dm.atInlog['postcode'];
leTelefoonnummer.text := dm.atInlog['telefoon'];
leWoonplaats.Text := dm.atInlog['Woonplaats']
end
else dm.atInlog.Next;
end;
But It does not do this in create form. How can I auto fill in the labeledit with the requested data?
thanks in advance
You could use TDataSet.Locate or Lookup:
type
TfmKlant = class(TForm)
// ... other declarations
private
procedure ShowData(p_Email: string);
end;
...
procedure TfmKlant.FormCreate(Sender: TObject);
begin
// assuming the data set is already open, and fminloggen.inlognaaem is already set
if dm.atInLog.Locate('email', fminloggen.inlognaam, []) then
begin
ShowData(fminloggen.inloognam);
end;
end;
procedure TfmKlant.ShowData(p_Email: string);
begin
gevonden := true;
leemail.text := dm.atInlog['email'];
lenaam.text := dm.atInlog['naam'];
leAdres.text := dm.atInlog['adres'];
lePostcode.text := dm.atInlog['postcode'];
leTelefoonnummer.text := dm.atInlog['telefoon'];
leWoonplaats.Text := dm.atInlog['Woonplaats']
end;