I have a button. Its OnClick event calls a procedure which destroys the button, but then the "thread" wants to return to the OnClick event and I get an access violation.
I'm completely stumped!
You need to destroy the button after all its code is finished executing. The standard way to do this is by posting a user-defined message to the form and giving the form a message method that will interpret it. For example:
unit Unit1;
interface
uses
Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
Dialogs, StdCtrls;
const
WM_KILLCONTROL = WM_USER + 1;
type
TForm1 = class(TForm)
Button1: TButton;
procedure Button1Click(Sender: TObject);
private
{ Private declarations }
procedure KillControl(var message: TMessage); message WM_KILLCONTROL;
public
{ Public declarations }
end;
var
Form1: TForm1;
implementation
{$R *.dfm}
{ TForm1 }
procedure TForm1.Button1Click(Sender: TObject);
begin
PostMessage(self.Handle, WM_KILLCONTROL, 0, integer(Button1))
end;
procedure TForm1.KillControl(var message: TMessage);
var
control: TControl;
begin
control := TObject(message.LParam) as TControl;
assert(control.Owner = self);
control.Free;
end;
end.
This works because the message gets put into the Windows Message Queue and doesn't come out until everything before it (including the Click message that the button is currently responding to) is finished processing.
You could instead just enable a timer in the OnClick event, then write the Timer event first to disable the timer and then call the procedure you are currently calling from the OnClick event. Set up the timer disabled and with a short interval time.
Related
I am trying to animate a celebratory trophy image that will 'bounce' up and down. I tried using a timer and then I used modulus to determine whether its odd or even, if its odd it goes up 10 if even it goes down 10 etc. I think the problem is looping, I need to use some form of loop right?
unit Unit11;
interface
uses
Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
Dialogs, pngimage, ExtCtrls,math, StdCtrls;
type
Tfrmwinner = class(TForm)
Panel1: TPanel;
Label1: TLabel;
Label2: TLabel;
Image1: TImage;
Image2: TImage;
Label3: TLabel;
Label4: TLabel;
Timer1: TTimer;
procedure Timer1Timer(Sender: TObject);
procedure Label4Click(Sender: TObject);
private
{ Private declarations }
public
{ Public declarations }
end;
var
frmwinner: Tfrmwinner;
implementation
uses Unit12,Unit4;
{$R *.dfm}
procedure Tfrmwinner.Label4Click(Sender: TObject);
var
position:integer;
begin
frmwinner.Hide;
frmboard.show;
unit12.frmboard.memlead.Lines.Add('Position'+#9+'Name'+#9+'ID Number');
unit12.frmboard.memlead.Lines.Add('___________________________________');
while not unit4.frmcontest.ADOLead.Eof do
begin
position:=position+1;
unit4.frmcontest.ADOLead.First;
unit12.frmboard.memlead.Lines.Add(inttostr(position)+#9+unit4.frmcontest.ADOLead['Name(s)']+#9+inttostr(unit4.frmcontest.ADOLead['ID Number']));
unit4.frmcontest.ADOLead.Next;
end;
end;
procedure Tfrmwinner.Timer1Timer(Sender: TObject);
var
icount,i:integer;
begin
icount:=0;
icount:=icount+1;
if (icount mod 2)=1 then
begin
image1.top:= image1.top+10;
image2.top:= image2.top+10;
end;
if (icount mod 2)=0 then
begin
image1.top:= image1.top-10;
image2.top:= image2.top-10;
end;
if icount=16 then
begin
timer1.Enabled:=false;
end;
end;
end.
This is what I've tried, with no luck
The problems related to how the image jumps (or doesn't jump) are in procedure TfrmWinner.Timer1Timer(Sender: TObject);
Note that event handlers like an OnTimer or OnKeyPress etc. are triggered by certain system events, and that the event handler you write should do its task as fast as possible and then exit. Also, anything you need to persist until the event handler is called the next time, must be saved in a "safe place" outside of the event handler.
First, the icount: integer variable cannot be declared in the OnTimer handler, because it would cease to exist every time the procedure exits.
Secondly, you can not initialize it (assign 0 to it) in the OnTimer handler, because then it would obviously never reach the final value of 16.
So, move the declaration of icount: integer to the private section of the form:
private
{ Private declarations }
icount: integer;
Then, initialize it to 0 and start the timer in the Label4Click() procedure if that is the purpose (it's unclear in your current code).
icount := 0;
Timer1.Enabled := True;
Here I have reproduced situation I have encounter couple of times. I have two forms. Form1 and Form2. Form1 has one edit field and OnKeyup event hooked up. Form2 has only one button and OnClick hooked up. When in Form1 user press VK_Retrun in field of type TEdit, Form2.Show is executed. Form2 shows up with focus on the button. The event OnClick is hooked up with the code "Close" inside. If user hit VK_RETURN key on the keyboard, Form2 closes as expected.. but here come the problem, Form2 got fired straight up again. It seems as when Form1 get focus the key is still in "a queue" and the edit field will proceed with VK_RETURN.
Here is a full listings of this situation:
unit UTestButton;
interface
uses
Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
Dialogs, StdCtrls;
type
TForm1 = class(TForm)
Edit1: TEdit;
procedure Edit1KeyUp(Sender: TObject; var Key: Word;
Shift: TShiftState);
private
{ Private declarations }
public
{ Public declarations }
end;
var
Form1: TForm1;
implementation
uses UTestButton2;
{$R *.dfm}
procedure TForm1.Edit1KeyUp(Sender: TObject; var Key: Word;
Shift: TShiftState);
begin
if (Key = VK_Return) THEN
Form2.Show;
end;
end.
This is the second unit.
unit UTestButton2;
interface
uses
Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
Dialogs, StdCtrls;
type
TForm2 = class(TForm)
Button1: TButton;
procedure Button1Click(Sender: TObject);
private
{ Private declarations }
public
{ Public declarations }
end;
var
Form2: TForm2;
implementation
{$R *.dfm}
procedure TForm2.Button1Click(Sender: TObject);
begin
Close;
end;
end.
In praxes I use sometimes simple form with labels or informations but one or two buttons and focus on button. User can hit key on keyboard as expected. If he use the vk_return and the underlying control use the key, I have to do some workaround to clear keys of buffer as with
PeekMessage(Mgs, 0, WM_CHAR, WM_CHAR, PM_REMOVE);
That´s not totally satisfying for me. Has anyone solution for this situation?
Simply handle the OnKeyDown event instead of OnKeyUp for your initial edit box:
procedure TForm1.Edit1KeyDown(Sender: TObject; var Key: Word;
Shift: TShiftState);
begin
if (Key = VK_RETURN) then
Form2.Show;
end;
How to create (when I want to show it) and destroy (when I want to hide it) frames on the main TForm? Frames' align = alClient.
I tried this:
The form:
unit main;
interface
uses
Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms, Dialogs, StdCtrls, uFrame1, uFrame2;
type
TFormMain = class(TForm)
procedure FormCreate(Sender: TObject);
private
{ Private declarations }
public
{ Public declarations }
f1: TFrame1;
f2: TFrame2;
end;
var
FormMain: TFormMain;
implementation
{$R *.dfm}
procedure TFormMain.FormCreate(Sender: TObject);
begin
f1 := TFrame1.Create(Self);
f1.Parent := Self;
end;
end.
First frame:
unit uFrame1;
interface
uses
Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms, Dialogs, StdCtrls;
type
TFrame1 = class(TFrame)
btn1: TButton;
procedure btn1Click(Sender: TObject);
private
{ Private declarations }
public
{ Public declarations }
end;
implementation
{$R *.dfm}
uses main, uFrame2;
procedure TFrame1.btn1Click(Sender: TObject);
begin
Self.Free;
FormMain.f2 := TFrame2.Create(FormMain);
FormMain.f2.Parent := FormMain;
end;
end.
Second frame:
unit uFrame2;
interface
uses
Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms, Dialogs, StdCtrls;
type
TFrame2 = class(TFrame)
lbl1: TLabel;
btn1: TButton;
procedure btn1Click(Sender: TObject);
private
{ Private declarations }
public
{ Public declarations }
end;
implementation
{$R *.dfm}
uses main, uFrame1;
procedure TFrame2.btn1Click(Sender: TObject);
begin
Self.Free;
FormMain.f1 := TFrame1.Create(FormMain);
FormMain.f1.Parent := FormMain;
end;
end.
but it crashes with access vialataions when I click button on FrameStart or Frame1 (TForm FormCreate works fine i.e. it creates and shows FrameStart).
Delphi 7.
You can't call Self.Free in those event handlers. When the event handler returns, the VCL code that executes next still uses a reference to an object that you just freed. And that's where the access violation comes from. If you had been running with FastMM in full debug mode then you would have been shown a helpful diagnostic message.
These frames will have to kill themselves in a more roundabout manner. Post a CM_RELEASE message to the frame asking it to call Free on the frame. You post the message, rather than sending it, so that all the in flight messages are processed first. You'll need to add a message handler to the frame to respond to the message.
You've got some of it.
The basic idea behind this sort of stuff.
add a private property to your mainform to hold the frame.
in the button click handler assuming you only want one at a time do
if assigned(fMyFrame) then
begin
fMyFrame.Free;
fMyFrame := nil;
end;
fMyFrame := TSomeFrame.Create(self);
fMyFrame.Parent := self;
fMyFrame.blah...
When you just want to get rid of it as opposed to replacing it
if assigned(fMyFrame) then
begin
fMyFrame.Free;
fMyFrame := nil;
end;
If you want your frame to raise another frame, repeat the above in there.
If you want the frame you raise in a frame to be a sibling, e.g. have the same Parent, then don't use Form1 var.
fMyNextFrame.Parent = self.Parent;
There's a huge number of ways you could improve on this once you get it working, it's a classic scenario for interfaces and or inheritance, but figure this bit out first.
mySomething := TMySomething.Create();
you can now do something with something.
After you called free, it's not can't, it's don't and don't let anything else either.
Don't do self.free, it's like playing with matches in barrel of petrol. It will hurt....
When a user selects a value in my TDateTimePicker I want to override the to-be-set value to the start of the week that goes with the selected value.
I tried setting it in the OnChange event, but then the originally selected value will be set right after I finished the event.
How would I go about this?
use the "ONCloseUp" event - this sample works for me (Delphi 7, WinXP)
unit Unit1;
interface
uses
Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
Dialogs, ComCtrls, DateUtils, StdCtrls;
type
TForm1 = class(TForm)
dtp1: TDateTimePicker;
btn1: TButton;
edt1: TEdit;
procedure btn1Click(Sender: TObject);
procedure dtp1CloseUp(Sender: TObject);
private
{ Private declarations }
procedure SetDayToMonday();
public
{ Public declarations }
end;
var
Form1: TForm1;
implementation
{$R *.dfm}
procedure TForm1.SetDayToMonday;
begin
dtp1.DateTime := dtp1.DateTime - DayOfTheWeek(dtp1.DateTime) + 1;
end;
procedure TForm1.dtp1CloseUp(Sender: TObject);
begin
SetDayToMonday;
end;
end.
--reinhard :-)
Use the onUserInput event!
I would post a message to the form, define a message (WM_USER+1000+X), post it, and handle it. If you don't "pend" it like this, you could also do a PendingDateTimeTimer:TTimer that does validation slightly later (say 10msec) after the OnChange event sets PendingDateTimeTimer.Enabled := true.
Is there a way to disable the parent windows buttons? I have a "working" form that is called by a lot of forms, that I would like to disable the parent form buttons until it's finished doing it's thing. Then turn them back on.
I'd like to do something that is attached to the OnShow event and onClose event.
Thanks
-Brad
Create the form you want to call, as in:
unit fMyModalForm;
interface
uses
Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
Dialogs;
type
TfrmMyModalForm = class(TForm)
procedure FormShow(Sender: TObject);
procedure FormClose(Sender: TObject; var Action: TCloseAction);
private
fCallingForm: TForm;
{ Private declarations }
public
{ Public declarations }
property CallingForm: TForm read fCallingForm write fCallingForm;
end;
(*
var
frmMyModalForm: TfrmMyModalForm;
*)
implementation
{$R *.dfm}
procedure TfrmMyModalForm.FormShow(Sender: TObject);
begin
fCallingForm.Enabled := False;
end;
procedure TfrmMyModalForm.FormClose(Sender: TObject;
var Action: TCloseAction);
begin
fCallingForm.Enabled := True;
end;
end.
Then after the button where you want to call this modal form:
unit fMain;
interface
uses
Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
Dialogs, StdCtrls,
fMyModalForm;
type
TfrmMain = class(TForm)
btnCall: TButton;
btn1: TButton;
btn2: TButton;
procedure btnCallClick(Sender: TObject);
private
{ Private declarations }
f : TfrmMyModalForm;
public
{ Public declarations }
end;
var
frmMain: TfrmMain;
implementation
{$R *.dfm}
procedure TfrmMain.btnCallClick(Sender: TObject);
begin
if not Assigned(f)
then begin
f := TfrmMyModalForm.Create(Self);
f.CallingForm := Self;
end;
f.Show();
end;
end.
If you just want to disable all buttons you can iterate through them and in stead of disabling the CallingForm only disable the buttons on the CallingForm. See the Stack Overflow topic (and my answer) at :Cast a form dynamically EDITED: or see answer of _J_ (which basically show the topic).
I would use actions in stead of buttons though.
If the secondary window opens, does something and closes, then it would make sense to open it with ShowModal instead of Show, that way the user can't use the main form until the second form has closed.
If you want to iterate though all the buttons and disable or enable them, the code would look something like this:
var
i: Integer;
begin
for i := 0 to MainForm.ComponentCount - 1 do
if (MainForm.Components[i] is TButton) then
TButton(MainForm.Components[i]).Enabled := False;
end;
For stuf like this you will need only 1 line of code and an TActionList component.
Create an actionlist with an action and link the action to the button. An action has an OnUpdate event which lets you determine if the action (and thus the linked button) should be enabled. The OnUpdate event is triggered everytime the action should know if it must be enabled or not.