Get return value of an item clicked - delphi

I am trying to figure out how to get a TObject (TLabel or TCard) as a returned value after a player has clicked on the object.
Goes like this
Player plays a spell //
Spell says hey i need a target //
spell calls get a target //
player selects a target //
target is returned to spell//
spell does xx with target//
Here is an example of code.
procedure TSpell.Spell;
var
secondTarget : Tobject;
begin
if true then
begin
secondTarget := GetSecondTarget(); //how??
if SecondTarget.classname = TCard then
begin
showmessage('you selected a TCard');
end
if SecondTarget.classname = TLabel then
begin
showmessage('you selected a label');
end
end
GetSecondTarget() : TObject;
begin
showmessage('Select a Target');
//wait for user to click something
//return the TObject to TSpell.spell
end;
I have no idea on how to do the GetSecondTarget... how to wait for user to click on something then return it to TSpell.Spell Any help on how to do this?

Windows GUI programs are event driven. You should not contemplate calling a function on the main thread that will block until an event happens.
What's more, your call to ShowMessage results in a modal dialog. And modal dialogs disable their owning windows which means that the user is not able to click on the object that you are hoping they will click on. If you wish to show a form with an instruction, then at the very least it needs to be a modeless dialog.
The bottom line here is that you need to adapt your ideas to the concepts of event-driven programming. When the user clicks on an object, an OnClick event will be raised. Attach a handler to that event and respond to it when that handler executes. If you need to behave differently to the OnClick event at different times, you'll need to maintain some state in your program to determine that behaviour switching.

Related

Knowing Onclick Event fired

I use a server client component, and when a file is being received in the TransferFile event of this component, I use an alert message component. So I want, if the user clicks on the alert message, the program to continue executing code in the TransferFile event to accept the file transfer in case the button is clicked, or to exit the procedure when not.
pls see bellow code:
procedure TfrmReadFile.ServerReceiveEvent(Sender: TObject;
Client: TSimpleTCPClient; Event: TTOOCSEvent);
begin
if (Event is TTOOCSEventFileTransfert) then
begin
Alert.Show;
if Alert.OnAlertClick then
begin
with (Event as TTOOCSEventFileTransfert) do
if (dlgSaveFile.Execute) then
with TMemoryStream.Create do
try
Write(Content[1], Length(Content));
SaveToFile(dlgSaveFile.FileName);
finally
Free;
end;
end;
end;
end;
but "if Alert.OnAlertClick then" is wrong
procedure TfrmReadFile.AlertAlertClick(Sender: TObject);
begin
end;
Please help me for these codes.
the AlertMessage is one of the TMS component and it hasn't ShowModal but it has Alert.Show Procedure that I use. and I want to pause the executing code untill the alert show time is finish and if user not click on alert executing the code aborted and no file saved.
From your code it's obvious that by the time you get to show the Alert, the file transfer already occurred: it's only a matter of "Do I save to file" or "Do I discard the content I already received". I'm inferring this information from your use of the TMemoryStream.Write() - that function takes a buffer as a parameter, so I assume the Content[1] gives your the buffer. This also means the Content is allready populated with the data you need. Too late to NOT transfer it, it's already in memory, all you can do is save it to disk or discard it.
I also have no idea how TMS's Alert works, but I'm going to assume only one Alert can be shown at any given time, and I'm going to assume you dropped your Alert on a component (ie: There's only one Alert in the whole program).
You should first alter the code for your "received event" to immediately move the content to a TMemoryStream. Also make sure you don't get into trouble with recursive re-entrance. Add a private field to your form, call it FLastContentReceived: TMemoryStream; Now alter your code to look like this:
procedure TfrmReadFile.ServerReceiveEvent(Sender: TObject;
Client: TSimpleTCPClient; Event: TTOOCSEvent);
begin
if (Event is TTOOCSEventFileTransfert) then
begin
// Re-entry before we managed to handle the previous received file?
if Assigned(FLastContentReceived) then
raise Exception.Create('Recursive re-entry not supported.');
// No re-entry, let's save the content we received so we can save it to file
// if the user clicks the Alert button.
FLastContentReceived := TMemoryStream.Create;
// I don't know what Content is, but you've got that in your code so I
// assume this will work:
FLastContentReceived.Write(Content[1], Length(Content);
// Show the alert; If the OnAlertClick event fires we'll have the received file content
// in the FLastContentRecevied and we'll use that to save the file.
Alert.Show;
end;
end;
You're trying to do an if on Alert.OnAlertClick - so I assume there's an event in your Alert component that's called OnAlertClick. Write this in it's event handler:
procedure TfrmReadFile.AlertAlertClick(Sender: TObject);
begin
if not Assigned(FLastContentReceived) then raise Exception.Create('Bug');
try
if dlgSaveFile.Execute then
FLastContentReceived.SaveToFile(dlgSaveFile.FileName);
finally FreeAndNil(FLastContentReceived);
end;
end;
You'd also need a way to discard the FLastContentReceived if the form is closed before the Alert button gets clicked OR there's a time-out (the Alert goes away without the user clicking it). The first job (getting rid of FLastContentReceived) when the form is closed is simle: add this to your form's OnDestroy:
FLastContentRecevid;Free;
Handling the timeout might a bit more difficult. If the Alert has an event that's called when the alert times out and the Balloon goes away without being clicked then use that event handler to do this:
FreeAndNil(FLastContentRecevid);
If it offers no such thing you could set up a TTimer for an interval equal to the timeout of the alert (or slightly longer to be safe), enable it before showing the alert and do this from it's OnTimer:
procedure TFrmReadFile.Timer1Timer(Sender: TObject);
begin
Timer1.Enabled := False;
FreeAndNil(FLastContentRecevid);
end;

DELPHI Edit.OnExit by TAB, show window result on focus bug

I'm having trouble with the following scenario:
2 Edit's
Type something in Edit1 and press TAB, focus goes to Edit2
Edit1.OnExit -> show a Form with a message "Processing..." (makes a lengthy validation)
After the form closes, the focus on Edit2 seems to be "crashed"...
- the hole TEXT in Edit2 isn't selected
- the carret isn't flashing
Example:
Create a new form
Put 2 edits
Set this as OnExit event in Edit1:
procedure TForm1.Edit1Exit(Sender: TObject);
begin
with TForm.CreateNew(self) do
try
Width := 100;
Height := 50;
Position := poMainFormCenter;
show;
sleep(200);
finally
Free;
end;
end;
Run the application
Set focus in the Edit1 and press TAB
I'm using:
Delphi 7 Enterprise
Windows 7 x64
This is a known problem. Windows has problems when you change focus before it's completed the last focus change (eg., focus starts changing from Edit1 to Edit2, but Edit1.OnExit does something to change focus to another control or form.
This happens, for instance, when apps try to do validations in an OnExit event and then try to return focus to the original control when the validation fails.
The easiest solution is to post a message to your form handle in the OnExit instead, and handle the focus change need there. It will fire once the target control gets the input focus, and Windows doesn't get confused.
const
UM_EDIT1_EXITED = WM_USER + 1;
type
TForm1=class(TForm)
...
private
procedure UMEdit1Exited(var Msg: TMessage); message UM_EDIT1_EXITED;
end;
implementation
procedure TForm1.Edit1Exit(Sender: TObject);
begin
PostMessage(Handle, UM_EDIT1_EXITED, 0, 0);
end;
procedure TForm1.UMEdit1Exited(var Msg: TMessage);
begin
// Show your other form here
end;
From an old Borland NG post by Dr. Peter Below of TeamB:
here is my general sermon on the "show dialog from OnExit" problem:
If an OnExit handler is triggered (which happens in response to the
Windows
message WM_KILLFOCUS) Windows is in the midst of a focus change. If you do
something in the handler that causes another focus change (like popping up
a message box or doing a SetFocus call) Windows gets terribly confused.
The
missing cursor is a symptom of that.
If you have to display a message to your user from an OnExit handler, do
it
this way:
Define a constant for a user message somewhere in the INterface
section
of your unit, above the type declaration for your form
'Const
UM_VALIDATE = WM_USER + 200;'
Give your Form a handler for this message, best placed in the private
section of the class declaration:
Procedure UMValidate( Var Msg: TMessage ); message UM_VALIDATE;
Post a UM_VALIDATE message to the form from the OnExit handler if
the contents of the field are not ok. You can pass additional
information in the wparam and lparam parameters of the message, e.g.
an error number and the Sender object. In fact you could do the whole
validation in the UMValidate handler!
I'm not sure precisely what's going on here, but it looks like the order of processing of messages is a bit messed up. Instead of killing your other form with Free, use Release and the focus will behave as you desire.
Another option is to use ShowModal instead of Show. Normally you show a processing dialog modally because you don't want the user making modifications to the main form whilst you are processing. If you do that then you can carry on using Free.

How to identify the Tobject type for sender in Delphi?

i am creating code for a dialog with a radio group as part of a preferences form. Part of our code is that when the preferences form is opened, the radio group is clicked, which configures a bunch of stuff (ie if the radio button is 'off' then a bunch of config stuff is hidden).
What I want is to know when the user actually clicks the radio group as opposed to it being fired when the preferences dialog opens.
So the code looks like this:
(open preferences)...
rgMyGroupClick(nil)
procedure TdlgPreferences.rgMyGroupClick(Sender:TObject)
if sender <> nil then
begin
//do something useful
end;
But this code is also executed when the preferences dialog is opened. What should I put in there to only execute when the user actually clicks the mouse on the button?
Thanks
Testing your sender
You can test the sender in two ways:
procedure TdlgPreferences.rgMyGroupClick(Sender:TObject)
begin
if sender = RadioButton1 then //do action
else if sender = RadioButton2 then ....
or you can test the type of a sender.
procedure TdlgPreferences.rgMyGroupClick(Sender:TObject)
begin
if sender is TRadioButton then //do action
else if sender is TForm then ....
The is keyword tests to see if an object is of a certain type.
Note that the test if AObject is TObject is always true because every object is derived from TObject.
More fun with typecasting
The fact that is tests for the object type and all ancestors can be used for other purposes as well:
procedure TdlgPreferences.rgMyGroupClick(Sender:TObject)
begin
//TObject does not have a 'tag' property, but all TControls do...
if (sender is TControl) and (TControl(Sender).Tag = 10) then ....
Because of short-circuit boolean evaluation Delphi will first check (sender is TControl) and only continue if that is true. Making the subsequent test (TControl(Sender).Tag = 10) safe to use.
If you don't understand the construct TControl(Sender) you can read up on typecasting.
here: http://delphi.about.com/od/delphitips2007/qt/is_as_cast.htm
and here: http://delphi.about.com/od/oopindelphi/a/delphi_oop11.htm
If I'm understanding you correctly you are clicking your radiogroup programatically to set up the initial state of the form, but you have extra code in the click handler that you don't want to run?
If that's the case you might want to consider removing the code that sets the state of the form to its own method and then call it from both the radio click event and from the initialze/show/create of your form.
That way you can use the same code to set the form to your required state from a user click and programatically set it without doing any extra logic required when a user is interacting with the dialog. Or maybe I've completely misunderstood your problem...
something like this:
// (open preferences)...
SetStateOfFormForSelectedGroup();
procedure TdlgPreferences.SetStateOfFormForSelectedGroup()
begin
//do all setting of form for selected group here.
end;
procedure TdlgPreferences.rgMyGroupClick(Sender:TObject)
begin
SetStateOfFormForSelectedGroup();
//do something useful
end;
Try some functions or properties, as:
Sender.classtype
InheritedFrom()
Note: when using the obj SENDER of the ButtonClick( sender:TObject ) (for example)
Sender = class of button on the click => TButton
So, TButton(Sender).properties or methods etc...

Sending WM_COMMAND to a TMenuItem

In my Delphi form's OnShow method, I determine that a dialog must be opened automatically once the form is opened - and I should be able to do this by simulating a click on a menuitem.
However, calling menuitem.Click brings up the dialog before the main form has opened - which is not what I want.
I expect that should do what I want, but I cannot find what parameters to pass for "wparam" to send the click to my menuitem.
PostMessage(handle, WM_COMMAND, wparam, 0)
The MSDN WM_COMMAND docs talk about IDM_* identifiers, but how does that appear in Delphi?
(I know this is a very old question but despite being resolved in some way the real question has really gone unanswered.)
--
The command item identifier of a 'TMenuItem' is in the Command property. According to WM_COMMAND's documentation the high word of 'wParam' would be '0' and the low word would be the menu identifier;
PostMessage(Handle, WM_COMMAND, MakeWParam(MyMenuItem.Command, 0), 0);
or simply;
PostMessage(Handle, WM_COMMAND, MyMenuItem.Command, 0);
With a popup menu item there would be a slight difference: the VCL handles popup menus' messages with a different utility window. The global PopupList variable has the handle to it in its Window property;
PostMessage(PopupList.Window, WM_COMMAND, MyPopupMenuItem.Command, 0);
Perhaps you can try to open the dialog in the OnActivate event ?
I am not really sure if the OnActivate gets fired again other than when the form is shown but if it does you can use :
procedure TForm1.FormActivate(Sender: TObject);
begin
Form2.ShowModal;
Self.OnActivate := nil;
end;
Wouldn't you have to do this with a one-time timer, if you want the form to appear as per a normal Show/ShowModal, get drawn (etc) fully, and then immediately do something else?
tmrKickOff : a TTimer, 100 ms interval, disabled at design time,
fires off a 'tmrKickOffTimer' event.
in form create,
tmrKickOff.Enabled:=false; //just in case something happened in IDE
in form show, at end of all other stuff;
tmrKickOff.Enabled:=true;
in tmrKickOffTimer
begin
tmrKickOffTimer.Enabled:=false;
menuItemClick(nil);
end;
with apologies for style, form and any error-trapping :-)
Alternatively, handle the Application.OnIdle event with something along the lines of ...
if not DialogDone then
begin
MyDialogForm.ShowModal; // or menuItem.Click ....
DialogDone := true;
end;
OnIdle won't fire (for the first time) until the Form is shown and the message queue is empty.
I don't think you can send a message directly to your menu item, but you can just post it to the main window and show your dialog from there. I do this and it works great so that the dialog box (in my case, a login prompt) appears on top of the main window to avoid confusion.
-Mark
procedure WMPostStartup(var Message: TMessage); message WM_POSTSTARTUP;
procedure TMainForm.WMPostStartup(var Message: TMessage);
begin
Self.Refresh;
// Now show the dialog box.
end;
One method I have used, which is very simular to MarkF's solution, is to create a new user defined message type and send a message using that type to yourself when you determine that you need to perform this other process after your main form displays:
const
wm_SpecialProc = wm_User + 1;
procedure TForm1.WMSpecialProc(var Message:tMessage); message wm_SpecialProc;
begin
Form2.ShowModal;
end;
procedure TForm1.OnShow(Sender:tObject);
begin
if true then
PostMessage(Application.MainForm.Handle,wm_SpecialProc,0,0);
end;
The nice thing about this method is that you are in control of the message generation, so can populate ANY lparam or wparam you want to later use by your handler. I sent the message directly through the application.mainform but you could also just say handle for the current form.

Delphi CMExit message not sent when modal dialog is closed?

In one part of the application I'm working on, there is a form control that does validation on reception of the CMExit message, which is exactly how the Delphi documentation says to do it (this code sample is from the Delphi Help files):
procedure TDBCalendar.CMExit(var Message: TWMNoParams);
begin
try
FDataLink.UpdateRecord; { tell data link to update database }
except
on Exception do SetFocus; { if it failed, don't let focus leave }
end;
inherited;
end;
The purpose of this is to perform the validation as soon as the control loses focus. So, for example, if I were to click on the OK button, the form control would lose focus, this method would run and on an exception would set the focus back to that form control. (Thus the "click" event on the OK button would never go through and the dialog would never close).
The problem I'm having is that this form control is inside a modal dialog window. Clicking OK does indeed send the CMExit message and causes the record to update (and validation to occur). However, pressing Enter while in the form control causes the modal dialog to close without sending the CMExit message. It's as if the form control never "loses focus". This means that not only does the dialog close without the form actually validating the data, but the data set isn't updated, either.
Given this problem, where is the best place for me to place my dataset updating/validation code? I could move it up to the dialog form itself and implement it in the OnCloseQuery handler, but this would mean that the logic is duplicated in both the form control and on the form itself. (The form control is used in other places, and I want to avoid changing its behaviour).
(I speculate that CMExit isn't triggered because the control never actually does lose focus. The form is closed, but the form control still "has focus" on the closed form.)
Closing a form does not necessarily fire the on-exit event of a TControl. The user could hit Alt-F4, for example.
I'd suggest moving the validation to a separate proc, and call that separate proc from both the on-exit and on-close events.
The below code should work without too much modification:
function TDBCalendar.UpdateSuccessful: boolean;
begin
{ tell data link to update database }
{ if successful, return True, else return False }
{ this function must be Public if a form is gonna check this value }
Result := True;
try
FDataLink.UpdateRecord;
except on Exception do
Result := False;
end;
inherited;
end;
procedure TDBCalendar.CMExit(var Message: TWMNoParams);
begin
//if not valid, then don't let them leave
if not(UpdateSuccessful) then begin
SetFocus;
end;
end;
///////////////////////////////////////////
//on the form that contains your control...
///////////////////////////////////////////
procedure TMyForm.FormClose(Sender: TObject; var Action: TCloseAction);
begin
//if not valid, then don't let them close the form
if not(dbcal.ControlIsValid) then begin
Action := caNone;
end
else begin
inherited;
end;
end;

Resources