Forcing a Delphi form to draw when its not visible - delphi

I have a form that I scrape a bitmap to send to a small embedded TFT display. I then inject touch events from the display into the form to activate the controls. This all works very well unless the form is not visible. If its moved off the visible desktop, minimized or closed it will not get a paint event and never updates.
Is there a way to force the canvas to redraw itself visible or not?
All of the obvious things like called repaint does not work.

Yes you can use the PaintTo method on a form:
procedure TForm1.Button1Click(Sender: TObject);
var
Bitmap: TBitmap;
begin
Bitmap := TBitmap.Create;
Bitmap.Width := Form2.Width;
Bitmap.Height := Form2.Height;
Form2.PaintTo(Bitmap.Canvas, 0, 0);
Image1.Picture.Assign(Bitmap);
Bitmap.Free;
end;
Im my small example I made a project with two forms Form1 and Form2. On Form2 i placed a label and Timer.
Here's the code for Form2
procedure TForm2.Timer1Timer(Sender: TObject);
begin
Label1.Caption := FloatToStr(now);
end;
And i woks out well.

Related

How to stop Screen.Cursor affects all controls on the form?

I will try to simplify my problem. If for example you drop 2 TSpeedButton and do:
procedure TForm1.SpeedButton1Click(Sender: TObject);
begin
Screen.Cursor := crHourGlass;
SpeedButton2.Cursor := crHandPoint; // note I'm setting other cursor than crDefault
end;
The SpeedButton2.Cursor remains showing Screen.Cursor which was set to crHourGlass.
I have looked into the TScreen.SetCursor code, and realize it sets the cursor for the entire form.
My question: is it somehow possible to use the Screen.Cursor for the entire form, BUT without impacting some control(s) which I want to set other cursor.
The same happens with a TButton. I don't mind placing the SpeedButton on a windowed control if I can somehow control it's cursor while Screen.Cursor is set to crHourGlass.
Thanks.
This is intentional behavior as explained in the documentation for TScreen.Cursor:
... When Cursor is crDefault, the individual objects determine the
cursor image. Assigning any other value sets the mouse cursor image
for all windows belonging to the application. The global mouse cursor
image remains in effect until the screen's Cursor property is changed
back to crDefault. ..
Windowed controls handle their cursors in TWinControl.WMSetCursor procedure, handler of WM_SETCURSOR message, where they explicitly set the screen cursor if it is anything other than crDefault and disregard their own cursor.
So to change the behavior you can handle the mentioned message. For a TButton interposer, an example could be:
procedure TButton.WMSetCursor(var Message: TWMSetCursor);
begin
if (Cursor <> crDefault) and (Message.HitTest = HTCLIENT) then begin
Message.Result := 1;
Windows.SetCursor(Screen.Cursors[Cursor]);
end else
inherited;
end;
Graphic controls' cursors are handled by their parent TWinControl. So to change the behavior of a speed button, you would still need to handle the same message on its parent. This would likely be impractical since the parent class might not be known beforehand.
Still, a very non-generalized implementation, for example for a graphic control placed directly on the form, might look like the below:
procedure TForm1.WMSetCursor(var Message: TWMSetCursor);
var
SmPt: TSmallPoint;
Control: TControl;
begin
DWORD(SmPt) := GetMessagePos;
Control := ControlAtPos(ScreenToClient(SmallPointToPoint(SmPt)), True);
if Assigned(Control) and Boolean(Control.Tag) then begin
Message.Result := 1;
Windows.SetCursor(Screen.Cursors[Control.Cursor])
end else
inherited;
end;
Above example would require the graphic control to have a non zero tag value. E.g.:
procedure TForm1.Button1Click(Sender: TObject);
begin
Screen.Cursor := crHourGlass;
SpeedButton1.Cursor := crHandPoint;
SpeedButton1.Tag := 1;
end;

Issue with controls alignment in dynamically created Frame

My program uses dynamically created frames and sometimes I get an issue that their controls are improperly aligned.
I use my own container control inherited from TPanel, but the same problem can be found when using the GridPanel as well.
Here is the source of a test program that reproduces the problem (with compiled exe).
The key code snippets:
in the main form:
//creating Frame from the main form
procedure TForm1.FormCreate(Sender: TObject);
begin
f := TFrame2.Create(Self);
f.Parent := Self;
end;
in the frame:
//constructor of the Frame
constructor TFrame2.Create(AOwner: TComponent);
begin
inherited;
Edit1.Clear;// the problem appears
end;
The frame and all its controls are aligned and must have the width of the main form, but Edit1 and ComboBox1 are visually not aligned until you resize the form manually (Sending WM_SIZE has no effect).
But if you comment the Edit1.Clear line everything will work fine from the program start. This code is not specific for the error and you can enter here e.g. ComboBox1.Items.Add('') etc.
If the frame is created statically or the GridPanel is changed to Panel the problem disappears.
I have made a new test2 version thanks to #quasoft, it works better - now the controls are horizontally aligned proper but vertically combobox is not in the right place that can be seen by changing the form size.
Quick fix:
The quick solution to your problem is to use Text property of TEdit, instead of the Clear method - as already said, replacing Edit1.Clear with Edit1.Text := ''.
Understanding the problem
But you need to understand this problem better if you plan to use frames in Delphi in the long term, or they will haunt you while you sleep (joking).
The real problem is that you are modifying the state of the frame before a Parent has been assigned to it.
procedure TForm1.FormCreate(Sender: TObject);
begin
f := TFrame2.Create(Self); // <--- Your text edit is cleared inside
f.Parent := Self; // <--- Your frame is attached to the form here
end;
Doing so does not allow the TGridPanel component to take in account the width, height and position of parent when calculating the size of its columns and rows.
Using Text property works, because the property setter does not change the text of the control directly, but sends a message for the purpose to the message queue:
Except from Controls.pas:
...
procedure TControl.SetTextBuf(Buffer: PChar);
begin
Perform(WM_SETTEXT, 0, Longint(Buffer));
Perform(CM_TEXTCHANGED, 0, 0);
end;
procedure TControl.SetText(const Value: TCaption);
begin
if GetText <> Value then SetTextBuf(PChar(Value));
end;
...
Which in effect, causes the text to actually change after you have assigned the Parent of the frame - as the message queue will be processed a little bit after the form create method completes.
Clear method on the other hand directly changes the text:
Excerpt from StdCtrls.pas:
...
procedure TCustomEdit.Clear;
begin
SetWindowText(Handle, '');
end;
...
A better solution
As you already learned, the quick fix works only in the specific example you provided.
A better solution is to create an Init method in your frame and call this method from the main form after Parent has been assigned:
Your frame:
procedure TFrame2.Init;
begin
Edit1.Clear;
ComboBox1.Items.Add('Foo Bar');
end;
Your main form:
procedure TForm1.FormCreate(Sender: TObject);
begin
f := TFrame2.Create(Self);
f.Parent := Self; // <--- Your frame is attached to the form here
f.Init; // <--- Calls initialization code of frame
end;

Mouseover Image On Buttons In FMX XE2

How to make a mouseover image for button ?
I used to make in FMX 2 buttons, and fill it with bitmap. But its owful .
I found property IsMouseOver
procedure TForm1.Button1Paint(Sender: TObject; Canvas: TCanvas;
const ARect: TRectF);
begin
if Button1.IsMouseOver then
begin
Button1.Text:='yes';
end
else
begin
Button1.Text:='nono';
end;
end;
But , i realy dont understand how to use containers, i only want to change fill ( my bitmap) by the method written before. Can someone give a simple code?
Or maybe its easier to make in VCL ?
Put two separate TImage controls on the button (drag them onto the button in the Structure View):
Size them to fit the button, and give each a separate image using the MultiResBitmap property editor.
Create an event handler for one of the TImage components for the OnMouseEnter and OnMouseLeave events, and then assign those handlers to both of the TImage components:
procedure TForm1.Image1MouseEnter(Sender: TObject);
begin
Image1.Visible := False;
Image2.Visible := True;
end;
procedure TForm1.Image1MouseLeave(Sender: TObject);
begin
Image1.Visible := True;
Image2.Visible := False;
end;

how to change the color of 3D firemonkey objects from different thread

I have a cylinder
Cylinder1 : TCylinder;
and I want it to change colour when I click on it.
I've added this to the on click event
procedure TfrmFiremonkey.Cylinder1Click(Sender: TObject);
begin
Cylinder1.MaterialSource := GreenMaterial;
end;
The cylinder does not change colour. However it will change colour if I set Cylinder1.MaterialSource before I call the form.Show event
I suspect the reason its not showing is because Cylinder1Click is happening in a different thread.
I have discovered that
procedure TfrmFiremonkey.Cylinder1Click(Sender: TObject);
begin
TColorMaterialSource(Cylinder1.MaterialSource).Color := claBlue;
end;
Works fine

Delphi - Maximize a form to a particular screen

Quite a simple one i would think, but i need to be able to Maximize a form to a particular screen. Cant seem to find any Delphi specific info.
I can remember the forms position over subsequent application loads. However, when i restore the position, then call WindowState := wsMaximized, the form moves to the other screen! (i do have other forms also visible on that screen - it appears its maximizing to the 'active screen')
So i need a function like so:
procedure Maximize(const aScreenIndex : Integer);
begin
if aScreenIndex < Screen.MonitorCount then
//Maximize to that screen
end;
Intercept the WM_GETMINMAXINFO message and adjust the coordinates inside its MINMAXINFO structure as needed.
Set Form.Position to poDesigned at design time
In Form.FormShow or your Maximize procedure:
procedure Maximize(const aScreenIndex : Integer);
begin
if aScreenIndex < Screen.MonitorCount then
begin
//Maximize to that screen
Myform.Left := screen.Monitors[aScreenIndex ].Left;
Myform.WindowState := wsMaximized;
end;
end;

Resources