Delphi - Destroy component [duplicate] - delphi

This question already has answers here:
Why does my program crash when I destroy a button in its own OnClick handler?
(3 answers)
Closed 7 years ago.
I have a problem with the following example.I have a button that creates a runtime Panel with more components:
Panel := TPanel.Create(self);
Panel.Parent := FlowPanel;
Panel.Align := alTop;
Panel.Height := 24;
Panel.Width := FlowPanel.Width;
Text := TLabel.Create(self);
Text.Parent := Panel;
Text.Align := alLeft;
Text.Caption := Query.FieldByName('Nazev').AsString;
Text.AlignWithMargins := True;
Text.Tag := Data_Id;
Text.Width := 100;
Button := TButton.Create(self);
Button.Parent := Panel;
Button.Caption := 'Odstranit';
Button.Align := alRight;
Button.Margins.Top := 0;
Button.Margins.Bottom := 0;
Button.AlignWithMargins := True;
Button.OnClick := DeleteFlowPanelItem;
Button has OnClick event on DeleteFlowPanelItem;
procedure TAdminTypyPlochy.DeleteFlowPanelItem(Sender: TObject);
var
myPanel: TPanel;
begin
myPanel := TPanel(TButton(Sender).Parent);
myPanel.Free;
end;
And when you click on that, although I component is deleted but also when it pops up message Access violation at address ... Why ?
Thanks :)

The function that calls your button OnClick event handler is a method of that same button. Your OnClick event deleted the button and so when the event handler returns, you are now executing in an instance method of an object that has been destroyed.
You need to postpone the destruction of the button until the button click event handling is complete. Use PostMessage to post a custom message that identifies which button to destroy. Handle that message by destroying the specified button. For instance the button could be passed in lParam.
Personally I'd use AllocateHWnd to create a window that can be the target for these messages. That way you can be certain to avoid problems with window re-creation.

This happens because you are freeing the button from within the button's OnClick event handler. This is simply not allowed. The button is owned by this panel, therefore, when you free the panel, it also free's this button - before its event handler finishes executing.

Related

TEdit with clear button [duplicate]

When use TEdit control on the right side stay small icon 'x'. How after click on icon clear TEdit box.
Tnx all!
Delphi provide TClearEditButton to clear the TEdit content. It can be added by right clicking and selecting AddItem - TClearEditButton from the popup menu. It also has a Click procedure overriden in FMX.Edit unit like:
procedure TClearEditButton.Click;
var
EditTmp: TCustomEdit;
begin
inherited Click;
EditTmp := GetEdit;
if EditTmp <> nil then
begin
if EditTmp.Observers.IsObserving(TObserverMapping.EditLinkID) then
if not TLinkObservers.EditLinkEdit(EditTmp.Observers) then
Exit; // Can't change
EditTmp.Text := string.Empty;
if EditTmp.Observers.IsObserving(TObserverMapping.EditLinkID) then
TLinkObservers.EditLinkModified(EditTmp.Observers);
if EditTmp.Observers.IsObserving(TObserverMapping.ControlValueID) then
TLinkObservers.ControlValueModified(EditTmp.Observers);
end;
end;
Which make you don't need to write OnClick event handler for the TClearEditButton unless you want to do some other job along side with clearing the edit.
If you are using a TEditButton then you should write the OnClick event handler like:
procedure TForm1.EditButton1Click(Sender: TObject);
begin
Edit1.Text:= EmptyStr;
end;

Controls in a modal Form doesn't get focus when called from onActivate of another form. Why?

In certain cases my application try to open a certain Form (Form2) automatically after another one (Form1) is shown. I'm using onFormActivate to call ShowModal, but after the second form is shown, it's controls are losing their focus.
To reproduce:
Create a new VCL Applicattion;
Create a second Form and drop a TEdit in it;
On Main Form add an onFormActivate listener;
'
procedure TForm1.FormActivate(Sender: TObject);
begin
Form2.ShowModal;
end;
After run you will see Form2 being shown. But the edit doesn't get the focus.
It works if I comment the MainFormOnTaskbar in the project file.
// Application.MainFormOnTaskbar := True;
But that is not what I'm supposed to change. I would like to understand: Why the TEdit is losing the focus?
OnActivate is triggered while focus is in progress of being shifting around. Interrupting that process is a really bad idea.
If you want the OnActivate event to trigger a ShowModal() call, you should delay it using PostMessage() (or a short TTimer) so the message loop can finish processing the focus shift that is already in progress, and then can perform the ShowModal() when it is safe to do so. For example:
const
WM_SHOWMODAL_FORM2 = WM_APP + 1;
procedure TForm1.FormActivate(Sender: TObject);
begin
PostMessage(Handle, WM_SHOWMODAL_FORM2, 0, 0);
end;
procedure TForm1.WndProc(var Message: TMessage);
begin
if Message.Msg = WM_SHOWMODAL_FORM2 then
Form2.ShowModal
else
inherited;
end;

Display TPanel as Modal

I have a main form, with multiple panels, some of which are hidden. As the user interacts with the main form, I need to make some of the hidden panels visible and display them in a modal fashion so the user can't interact with the other parts of the main form until they finish with the modal panel.
Is there a way to display an existing panel on a form in a modal fashion?
I would prefer to not cycle through the main forms controls and disable/hide everything except for the one panel, which is the common answer developers have given when others have asked this same question.
My goal is to simply display an existing panel on the main form in a modal fashion without having to manipulate the other controls on the main form.
Since a TForm has .ShowModal(), we can easily create a temporary form, move the TPanel to the form, display the form as modal, wait for the user to dismiss the form, then move the TPanel back to its original parent before destroying the TForm.
If you create a form with a hidden TPanel called pnl and a button on that panel called btnCloseModalPanel, then the following code displays pnl as modal until the user clicks the button.
begin
DisplayModalPanel(pnl);
// do something with 'pnl.data...'
end;
procedure TForm1.DisplayModalPanel(Panel: TPanel);
var
frm: TForm;
old_top, old_left: Integer;
old_parent: TWinControl;
old_visible: Boolean;
begin
frm := TForm.Create(Panel.Parent);
try
frm.BorderStyle := bsNone;
frm.Position := poOwnerFormCenter;
frm.Tag := 12921; // test in close button click, so we don't close the wrong form
// Rememer properties we can change and then restore them
old_top := Panel.Top;
old_left := Panel.Left;
old_parent := Panel.Parent;
old_visible := Panel.Visible;
// Move the panel to the modal form
Panel.Parent := frm;
Panel.Top := 0;
Panel.Left := 0;
Panel.Visible := True;
// Display the modal form
frm.AutoSize := True;
frm.ShowModal;
// Restore everything
Panel.Visible := old_visible;
Panel.Parent := old_Parent;
Panel.Left := old_left;
Panel.Top := old_top;
finally
FreeAndNil(frm);
end;
end;
procedure TForm1.btnCloseModalPanelClick(Sender: TObject);
var
frm: TForm;
begin
if pnl.Parent is TForm then
begin
frm := pnl.Parent as TForm;
if frm.Tag = 12921 then // don't close the wrong form
frm.Close;
end;
end;

Drop down menu for any TControl

Continue of this topic:
Drop down menu for TButton
I have wrote a generic code for DropDown memu with any TControl, but for some reason it dose not work as expected with TPanel:
var
TickCountMenuClosed: Cardinal = 0;
LastPopupControl: TControl;
type
TDropDownMenuHandler = class
public
class procedure MouseDown(Sender: TObject; Button: TMouseButton;
Shift: TShiftState; X, Y: Integer);
end;
TControlAccess = class(TControl);
class procedure TDropDownMenuHandler.MouseDown(Sender: TObject; Button: TMouseButton;
Shift: TShiftState; X, Y: Integer);
begin
if LastPopupControl <> Sender then Exit;
if (Button = mbLeft) and not ((TickCountMenuClosed + 100) < GetTickCount) then
begin
if GetCapture <> 0 then SendMessage(GetCapture, WM_CANCELMODE, 0, 0);
ReleaseCapture;
// SetCapture(0);
if Sender is TGraphicControl then Abort;
end;
end;
procedure RegisterControlDropMenu(Control: TControl; PopupMenu: TPopupMenu);
begin
TControlAccess(Control).OnMouseDown := TDropDownMenuHandler.MouseDown;
end;
procedure DropMenuDown(Control: TControl; PopupMenu: TPopupMenu);
var
APoint: TPoint;
begin
LastPopupControl := Control;
RegisterControlDropMenu(Control, PopupMenu);
APoint := Control.ClientToScreen(Point(0, Control.ClientHeight));
PopupMenu.PopupComponent := Control;
PopupMenu.Popup(APoint.X, APoint.Y);
TickCountMenuClosed := GetTickCount;
end;
This works well with TButton and with TSpeedButton and with any TGraphicControl (like TImage or TSpeedButton etc) as far as I can tell.
BUT does not work as expected with TPanel
procedure TForm1.Button1Click(Sender: TObject);
begin
DropMenuDown(Sender as TControl, PopupMenu1);
end;
procedure TForm1.Panel1Click(Sender: TObject);
begin
DropMenuDown(Sender as TControl, PopupMenu1); // Does not work!
end;
procedure TForm1.SpeedButton1Click(Sender: TObject);
begin
DropMenuDown(Sender as TControl, PopupMenu1);
end;
procedure TForm1.Image1Click(Sender: TObject);
begin
DropMenuDown(Sender as TControl, PopupMenu1);
end;
Seems like TPanel is not respecting ReleaseCapture; and not even Abort in the event TDropDownMenuHandler.MouseDown. What can I do to make this work with TPanel and other controls? What am I missing?
It's not that TPanel is not respecting ReleaseCapture, it is that the capture is not relevant at all. This is what happens after the popup menu is launched and active, and the control is clicked once again:
The click cancels the modal menu loop, the menu is closed and a mouse down message is posted.
VCL sets a flag within the mouse down message handling [csClicked].
Mouse down event handler is fired, you release the capture.
After the mouse down message returns, posted mouse up message is processed, VCL checks for the flag and clicks the control if it is set.
The click handler pops the menu.
Granted I didn't trace a working example so I can't tell when and how ReleaseCapture is helpful. In any case, it can't help here.
The solution I'd propose is a little different than the current design.
What we want is a second click to not to cause a click. See this part of the code:
procedure DropMenuDown(Control: TControl; PopupMenu: TPopupMenu);
var
APoint: TPoint;
begin
...
PopupMenu.PopupComponent := Control;
PopupMenu.Popup(APoint.X, APoint.Y);
TickCountMenuClosed := GetTickCount;
end;
The second click is in fact what closes the menu, before launching it again through the same handler. It is what causes the PopupMenu.Popup call to return. So what we can tell here is that the mouse button is clicked (either a left button or a double click), but not yet processed by the VCL. That means the message is yet in the queue.
Remove the registration mechanism (mouse down handler hacking) with this approach, it is not needed, and the class itself as a result, and the globals.
procedure DropMenuDown(Control: TControl; PopupMenu: TPopupMenu);
var
APoint: TPoint;
Msg: TMsg;
Wnd: HWND;
ARect: TRect;
begin
APoint := Control.ClientToScreen(Point(0, Control.ClientHeight));
PopupMenu.PopupComponent := Control;
PopupMenu.Popup(APoint.X, APoint.Y);
if (Control is TWinControl) then
Wnd := TWinControl(Control).Handle
else
Wnd := Control.Parent.Handle;
if PeekMessage(Msg, Wnd, WM_LBUTTONDOWN, WM_LBUTTONDBLCLK, PM_NOREMOVE) then begin
ARect.TopLeft := Control.ClientOrigin;
ARect.Right := ARect.Left + Control.Width;
ARect.Bottom := ARect.Top + Control.Height;
if PtInRect(ARect, Msg.pt) then
PeekMessage(Msg, Wnd, WM_LBUTTONDOWN, WM_LBUTTONDBLCLK, PM_REMOVE);
end;
end;
Additionally this doesn't depend on processing timing.
Requirements
If I understand you correctly, then the requirements are:
At the first left mouse button click on a Control, a PopupMenu should be shown beneath the Control.
At the second left mouse button click an that same Control, the shown PopupMenu should be closed.
Realize that, disregarding the implementation of requirement 1 for the moment, requirement 2 happens automatically: when you click outside a PopupMenu, the PopupMenu will close. This concludes to that the implementation of the first should not interfere with the second.
Possible solutions:
Count the clicks on the Control: at the first click, show the PopupMenu and at the second click, do nothing. But this will not work, because the PopupMenu may be closed already by clicks elsewhere and then a second click should actually be the first click.
At the first click, show the PopupMenu. At the second click, determine whether the PopupMenu is still shown. If so, then do nothing. Otherwise, assume a first click. This also will not work, because when a second click is processed, the PopupMenu will be already closed.
At the first click, show the PopupMenu. At the second click, determine whether the PopupMenu is closed sometime during the last couple of milliseconds. If so, then the disappearance is due to this very second click and do nothing. This is the solution you are currently using by utilizing the fact that TPopupMenu.Popup will not return until the PopupMenu is closed.
The current implementation
During the OnClick event of a Control:
The OnMouseDown event of the control is assigned to a custom handler,
The PopupMenu is Shown.
On the second click on the Control:
The time when then PopupMenu was closed is saved (this is still during execution of the previous OnClick event),
The custom OnMouseDown event handler is called,
If the saved time was within the last 100 milliseconds, the mouse capture is released and all execution is aborted.
Note: a possibly already OnMouseDown event setting is not saved and gone!
Why this works for a Button
A TCustomButton handles click events by responding to a by Windows send CN_COMMAND message. That is a specific Windows BUTTON sytem class control characteristic. By canceling the mouse capture mode, this message is not send. Thus the Control's OnClick event is not fired on the second click.
Why this doesn't work for a Panel
A TPanel handles click events by adding the csClickEvents style to its ControlStyle property. This is a specific VCL characteristic. By aborting execution, subsequent code due to the WM_LBUTTONDOWN message is stopped. However, the OnClick event of a TPanel is fired somewhere down its WM_LBUTTONUP message handler, thus the OnClick event is still fired.
Solution for both
Use davea's answer on your other question wherein he simply does nothing if the saved time of the PopupMenu's closing was within the last 100 milliseconds.

Disabled TEdit Font Colour

I have an application having one TEdit which is disabled when the application runs. After some calculations it will be enabled. My requirement is to set the Font.Color of this disabled TEdit as Blue instead of Grey (Disabled Font Color).
This is not supported by the standard TEdit. You could set the edit to ReadOnly instead of Disabled - this way the font color is preserved but user can't change the value of the edit. Ie to "disable" the edit
Edit1.ReadOnly := True;
Edit1.Font.Color := clBlue;
and to enable it again
Edit1.ReadOnly := False;
Edit1.Font.Color := clWindowText;
See Peter Below's two suggestions for accomplishing your objective on Torry's Delphi Pages at this link. Judging from your comment about what you Googled, his first suggestion will be simpler for you to implement. Drop a TPanel on a form and drag a TEdit onto the TPanel (i.e., TPanel is TEdit's parent. Then drop a Button on the form to simulate when your calculations are done.
procedure TForm1.btnToggleEnabledClick(Sender: TObject);
begin
if Panel1.Enabled then
begin
{Calcs are not done, so disable the TEdit}
Panel1.Enabled := false;
Edit1.Font.Color := clBlue;
Edit1.Text := 'Calcs not done';
end
else
begin
{Calcs are done, so enable the TEdit}
Panel1.Enabled := true;
Edit1.Font.Color := clWindowText;
Edit1.Text := 'Calcs all done';
end;
end;

Resources