Custom tSpeedButton painting - firemonkey

I have two TSpeedButton buttons on a form. When one button is enabled, the other is disabled and vice versa. I want to paint the buttons with custom colors to reflect whether they are enabled or disabled.
I coded a procedure named SetButtonsColors. This procedure sets (one button at a time), the variables ButtonFillColor and ButtonStrokeColor with the custom colors, accordingly with the button state, and immediately commands the repainting of the button.
On ButtonPaint handler I set the Canvas.Fill.Color and Canvas.Stroke.Color with ButtonFillColor and ButtonStrokeColor respectively and do the filling and drawing of the aRect.
For my disappointment, the buttons are always painted with the colors set for the last button. This happens, I presume, because Windows paints the buttons asynchronous, and hence, uses the last settings for Canvas.Fill.Color and Canvas.Stroke.Color.
If this is true, how can I force Windows to paint the button immediately after it's repainting is commanded?
Follows the code for SetButtonsColors and ButtonPaint:
procedure tForm1.SetButtonsColors;
begin
if Button1.Enabled then begin
ButtonFillColor := tAlphaColorRec.White;
ButtonStrokeColor := tAlphaColorRec.Black;
Button1.Repaint;
ButtonFillColor := tAlphaColorRec.Black;
ButtonStrokeColor := tAlphaColorRec.White;
Button1.Repaint;
end;
else begin
ButtonFillColor := tAlphaColorRec.Black;
ButtonStrokeColor := tAlphaColorRec.White;
Button1.Repaint;
ButtonFillColor := tAlphaColorRec.White;
ButtonStrokeColor := tAlphaColorRec.Black;
Button2.Repaint;
end;
end;
procedure tForm1.ButtonPaint (Sender: tObject; Canvas: tCanvas; const aRect: tRectF);
begin
Canvas.BeginScene;
Canvas.Fill .Color := lvButtonFillColor;
Canvas.Stroke.Color := lvButtonStrokeColor;
Canvas.FillRect (aRect, 0, 0, [] , 1, CornerTypeBevel);
Canvas.DrawRect (aRect, 0, 0, AllCorners, 1, CornerTypeBevel);
Canvas.EndScene;
end;
Thanks.

Related

Why doesn't the radio button on custom page checked in Inno Setup?

Why don't rbStandardInstallType and rbCustomInstallType radio buttons get checked even though I set the Checked property of one of those to True? On the other hand, rbDefaultMSSQLInstance and rbNamedMSSQLInstance radio buttons do get checked.
I create radio buttons like this:
function CreateRadioButton(
AParent: TNewNotebookPage; AChecked: Boolean; AWidth, ALeft, ATop, AFontSize: Integer;
AFontStyle: TFontStyles; const ACaption: String): TNewRadioButton;
begin
Result := TNewRadioButton.Create(WizardForm);
with Result do
begin
Parent := AParent;
Checked := AChecked;
Width := AWidth;
Left := ALeft;
Top := ATop;
Font.Size := AFontSize;
Font.Style := AFontStyle;
Caption := ACaption;
end;
end;
I have 2 custom pages where I must show my image on the left and some text and radio buttons on the right (2 radio buttons per page).
So, in my InitializeWizard procedure I've written this:
wpSelectInstallTypePage := CreateCustomPage(wpSelectDir, 'Caption', 'Description');
rbStandardInstallType := CreateRadioButton(WizardForm.InnerPage, True, WizardForm.InnerPage.Width, ScaleX(15), WizardForm.MainPanel.Top + ScaleY(30), 9, [fsBold], 'Standard');
rbCustomInstallType := CreateRadioButton(WizardForm.InnerPage, False, rbStandardInstallType.Width, rbStandardInstallType.Left, rbStandardInstallType.Top + rbStandardInstallType .Height + ScaleY(16), 9, [fsBold], 'Custom');
wpMSSQLInstallTypePage := CreateCustomPage(wpSelectInstallTypePage.ID, 'Caption2', 'Description2');
rbDefaultMSSQLInstance := CreateRadioButton(WizardForm.InnerPage, True, WizardForm.InnerPage.Width, ScaleX(15), WizardForm.MainPanel.Top + ScaleY(30), 9, [fsBold], 'DefaultInstance');
rbNamedMSSQLInstance := CreateRadioButton(WizardForm.InnerPage, False, rbDefaultMSSQLInstance.Width, rbDefaultMSSQLInstance.Left, rbDefaultMSSQLInstance.Top + rbDefaultMSSQLInstance.Height + ScaleY(10), 9, [fsBold], 'NamedInstance');
And finally, here's my CurPageChanged code in order to display all the controls properly:
procedure CurPageChanged(CurPageID: Integer);
begin
case CurPageID of
wpSelectInstallTypePage.ID, wpMSSQLInstallTypePage.ID:
WizardForm.InnerNotebook.Visible := False;
else
WizardForm.InnerNotebook.Visible := True;
end;
rbDefaultMSSQLInstance.Visible := CurPageID = wpMSSQLInstallTypePage.ID;
rbNamedMSSQLInstance.Visible := CurPageID = wpMSSQLInstallTypePage.ID;
rbStandardInstallType.Visible := CurPageID = wpSelectInstallTypePage.ID;
rbCustomInstallType.Visible := CurPageID = wpSelectInstallTypePage.ID;
end
You are adding the radio buttons to a wrong parent control (WizardForm.InnerPage). Not to the custom pages you are creating. And you then workaround that flaw by explicitly hiding/showing the radio buttons in CurPageChanged.
As all four radio buttons have the same parent (WizardForm.InnerPage), only one of them can be checked. So when you check the rbDefaultMSSQLInstance, the rbStandardInstallType is implicitly unchecked.
For the correct code, see:
Inno Setup Placing image/control on custom page
(make sure you remove your redundant CurPageChanged code)
You should also consider using CreateInputOptionPage instead of manually adding the radio buttons to a generic custom page.

Firemonkey tEdit OnPaint

I have a tEdit and I want to paint it's rect with a different set of Fill and Stroke colors. I coded as follows:
procedure TForm1.FormCreate (Sender: TObject);
begin
Edit1.OnPaint := HandleEditPaint;
end;
procedure TForm1.HandleEditPaint (Sender: tObject; Canvas: tCanvas; const aRect: tRectF);
begin
Canvas.BeginScene;
Canvas.Fill .Color := TAlphaColorRec.Aqua;
Canvas.Fill .Kind := TBrushKind.bkSolid;
Canvas.Stroke.Color := TAlphaColorRec.Red;
Canvas.Stroke.Kind := TBrushKind.bkSolid;
Canvas.FillRect (aRect, 0, 0, AllCorners, 1, TCornerType.Bevel);
Canvas.DrawRect (aRect, 0, 0, AllCorners, 1, TCornerType.Bevel);
Canvas.EndScene;
end;
It paints the control's rect properly, but it cannot be focused because when clicking on the Control, it enters in a loop that trigger's HandleEditPaint non stop. How can I avoid this to happen? I know that in painting the control's rect, I must take care of the control's text too, but I should concern about the mouse cursor either? Is there a way to achieve this behavior using styles?

How to avoid flickering when animating GUI components in Lazarus

I'm moving a TMemo object left and right in my GUI application. The problem is, is that the letters in my TMemo are flickering as soon as the movement starts.
I've looked this up, and, apparently, setting the DoubleBuffering property of my main form should've helped me, but it didn't. So I tried setting that property to true on all objects that were moving, but flickering was still present.
Are there any ways to achieve flicker-free animations of GUI components in Lazarus? I'm a novice in Lazarus, so I'm kind of blindly googling for solutions right now. I would really appreciate some help.
To provide further context, here's how I animate my TMemo: I've got a TTimer with an interval value of 10, and its OnTimer event moves my TMemo left and right contiguously. To make the movement slightly smoother, I added a simple cosine interpolation function.
In the end here's the code:
procedure TServerSideForm.ControlPanelHideTimerTimer(Sender: TObject);
begin
if (hideAnimVal < 1) then
begin
hideAnimVal := hideAnimVal + 0.025;
end
else
begin
MemoHideTimer.Enabled:=false;
end;
// hideStart - starting position of my TMemo, hideEnd - end position of my TMemo
hideCurr := Round(CosineInterpolation(hideStart, hideEnd, hideAnimVal));
Memo.Left:=hideCurr;
end;
Cosine interpolation:
function CosineInterpolation(Val1, Val2, Angle: Double): Double;
var
Percent: Double;
begin
Percent := (1-Cos(Angle*PI))/2;
Result := (Val1 * (1 - Percent) + Val2 * Percent);
end;
I would try to move an image instead:
var
Memo1dc: hdc;
Cnv: TCanvas;
Rct: TRect;
implementation
procedure TForm1.MemoHideTimerTimer(Sender: TObject);
begin
if Memo1.Visible then
begin
Memo1dc := GetDC(Memo1.Handle);
Cnv.Handle := Memo1dc;
Rct.Height := Memo1.Height;
Rct.Width := Memo1.Width;
Image1.Left := Memo1.Left;
Image1.Top := Memo1.Top;
Image1.Width := Memo1.Width;
Image1.Height := Memo1.Height;
Image1.Canvas.CopyRect(Rct, Cnv, Rct);
Memo1.Visible := False;
Image1.Visible := True;
end;
if (hideAnimVal < 1) then
begin
hideAnimVal := hideAnimVal + 0.025;
end
else
begin
MemoHideTimer.Enabled := False;
end;
// hideStart - starting position of my TMemo, hideEnd - end position of my TMemo
hideCurr := Round(CosineInterpolation(hideStart, hideEnd, hideAnimVal));
Image1.Left := hideCurr;
if MemoHideTimer.Enabled = False then
begin
Memo1.Left := Image1.Left;
Memo1.Visible := True;
Image1.Visible := False;
end;
end;
procedure TForm1.FormCreate(Sender: TObject);
begin
Cnv := TCanvas.Create;
end;

TShape - drawing issue on Paint event

I'm stuck in a class derived from TShape.
In the Paint method, I use the Canvas to draw a rectangle. In the Form, I have a TTrackBar that allows to change Left and Top coordinates of the TShape.
It does not matter what values I set to the Left and Top using the TTrackBar, the rectangle does not move accordingly. Instead, when I set those values via code, the rectangle appears in the correct position.
I'm coding a FireMonkey app with Delphi 10.1 Berlin on Windows 10.
unit frmShapeStudy;
interface
type
tMy_Shape = class (tShape)
protected
procedure Paint; override;
public
constructor Create (aOwner: tComponent); override;
procedure Draw;
end;
tformShapeStudy = class (tForm)
trkBarLeft: TTrackBar;
trkBarTop: TTrackBar;
procedure FormCreate (Sender: tObject);
procedure TrackBarChange (Sender: tObject);
end;
var
formShapeStudy: tformShapeStudy;
implementation
{$R *.fmx}
var
My_Shape : tMy_Shape;
lvShapeRect : tRectF ;
procedure tformShapeStudy.FormCreate (Sender: tObject);
begin
My_Shape := tMy_Shape.Create (Self);
with My_Shape do begin
Parent := Self;
TrackBarChange (Self);
end;
end;
procedure tformShapeStudy.TrackBarChange (Sender: TObject);
begin
My_Shape.Draw;
end;
constructor tMy_Shape.Create (aOwner: tComponent);
begin
inherited;
with lvShapeRect do begin
Left := Self.Left;
Top := Self.Top ;
Height := 20;
Width := 20;
end;
end;
procedure tBS_Shape.Draw;
begin
l := formShapeStudy.trkBarLeft.Value;
t := formShapeStudy.trkBarTop .Value;
{`Left & Top` are set with `l & t` or with `120 & 150`
and tested separately, by commenting the propper code lines}
lvShapeRect.Left := l; // this does no work
lvShapeRect.Top := t; // this does no work
lvShapeRect.Left := 120; // this works
lvShapeRect.Top := 150; // this works
Repaint;
end;
procedure tMy_Shape.Paint;
begin
inherited;
with Canvas do begin
Fill .Color := tAlphaColorRec.Aqua;
Stroke.Color := tAlphaColorRec.Blue;
BeginScene;
FillRect (lvShapeRect, 0, 0, Allcorners, 1, tCornerType.Bevel);
DrawRect (lvShapeRect, 0, 0, Allcorners, 1, tCornerType.Bevel);
EndScene;
end;
end;
end.
Sorry Folks! Is the old habit of using Left & Top instead of Position.X & Position.Y. I agree with the new Position way to set Left & Top, but it doesn't make sense that Embarcadero still makes available this properties, but they do nothing in terms of setting Left & Top of the control. Saying it in other words, since those old properties are still available, they should set the Left & Top properties same as Position.X & Position.Y, otherwise it leads to this kind of mistake, where you sware you are setting Left & Top, but the control does not move.
The correct way to set Left & Top is:
Position.X := aLeft;
Position.Y := aTop;
unless Embarcadero changes the behavior of Left & Top properties (wich is very unlikely).

Lazarus find control under cursor

I am using the following code from this posting.
Code from Checked Answer
I need to get the Control (Label.Caption) under the mouse cursor from one of several TLabel and it worked fine when the Label was on the Main From. I put the Labels on a Panel on the Main form and now this only finds the Panel. I only want this to work on a select few of the Labels of the many that are on the Panel.
I tried changing the Z-Order for the Labels as "Bring To Front" but it made no difference, still got the Panel. How can I again find a Label under the cursor now that they are on the Panel?
Lazarus does not appear to have FindVCLWindow or ObjectAtPoint.
procedure TForm1.Button1Click(Sender: TObject);
var
ctrl : TControl;
point : TPoint;
begin
point := Mouse.CursorPos; // Mouse pos at screen
Dec(point.X, Left); // Adjust for window.
Dec(point.Y, Top);
Dec(point.Y, GetSystemMetrics(SM_CYCAPTION)); // Adjust to client area.
ctrl := ControlAtPos(point, True, True, True);
// I added the following
tStr:=ctrl.Name; // DEBUG: This now shows "Panel2"
aStr:=(ctrl as TLabel).Caption; // This used to work
end;
Try:
procedure TForm1.Button1Click(Sender: TObject);
var
ctrl: TControl;
pt: TPoint;
begin
pt := ScreenToClient(Mouse.CursorPos);
ctrl := ControlAtPos(pt, [capfRecursive, capfAllowWinControls]);
if Assigned(ctrl) then
Caption := ctrl.Name
else
Caption := Format('%d, %d', [pt.x, pt.y]);
end;

Resources