Recently I was asked to automate a little sub-routine that presented a series of data records and any two of four potential buttons for the user to select after seeing an analysis of the record. The boss said having the users see the analysis was wasting time since the users invariably selected the number one choice in the button list and he was prepared to live with my guesses for all but the best of his users. So, he wanted a NEW series of buttons added to offer Handle Automatically, Handle Manually and Handle Case by Case. The last button would just run the already existing code. The second button would essentially do nothing and just exit. The first button? Well, that was the rub.
What I decided to do was to use a couple of flags and then have the automatic path just simulate the click of whatever sub-button was best, based on the analysis. The issue was that calling Button1Click(Sender) wasn't possible because the procedure running the Analysis was called RunAnalysis and wasn't attached to a specific object to pass the TObject through. I eventually refactored the guts of the Button1Click method into Button1Pressed and then called THAT from Button1Click. Thus I was able to call Button1Pressed from within RunAnalysis.
The avoided path would have been to call Button1Click(Nil). I didn't try it since I had an easy solution (Thanks Modelmaker, by the way). But my question is, would the nil reference have worked or would it have caused a disaster. Could I have called a higher function (randomly?) that did have a sender, JUST to have a sender object in the procedure call? Just how important IS the Sender object, if I don't use anything that actually REFERENCES the Sender?
System details: Delphi 7 in Win 7 programming environment for use in Windows XP.
Thanks in advance for any wisdom, GM
I tend to put the event handler's code into another method when possible:
procedure TForm1.DoSomething(const Test: Boolean);
begin
// Do Something based on Test
end;
procedure TForm1.FormCreate(Sender: TObject);
begin
DoSomething(False); // init here
end;
procedure TForm1.CheckBox1Click(Sender: TObject);
begin
DoSomething(TCheckBox(Sender).Checked);
end;
So when I have a need to call CheckBox1Click(nil) it's a good sign for me to pull off the code from the event handler into a separate method.
A callback or "event" is no different than any other function. You can pass a NIL reference anywhere you want, as long as you either (a) wrote that code and know it's nil safe, or (b) you have read that code and everything it calls without checking for nil, and know it's nil safe.
Some programmers use NIL freely and some consider using NIL parameter values to be bad style. My style tends to be "don't assume Sender is assigned and don't assume that it is of a particular type", which leads to lots of check-code in my event handlers, but other code than mine, it varies widely and so the First Rule of coding comes in; "Don't make assumptions. Read the code.".
You can use nil as a parameter to a TNotify event (or any other expecting a Sender), as long as the code doesn't reference Sender:
procedure TForm1.Button1Click(Sender: TObject);
begin
DoSomeStuff(nil);
end;
procedure TForm1.DoSomeStuff(Sender: TObject);
begin
// Safe
DoSomeOtherStuff;
// Safe
// Do stuff with Sender
if Sender is TButton then
TButton(Sender).Caption := 'In DoSomeStuff'
// NOT safe!
with TButton(Sender) do
begin
Caption := 'In DoSomeStuff';
end;
end;
Short Answer - yes.
I use it to distinguish whether (say) a menu Event was clicked by a user or called directly by code.
Related
I need the user to be able to right click the button and it deletes itself but the following code isn't working
procedure TForm1.Button1Click(Sender: TObject); ////////Creates a new object
var
ExampleButton : TButton;
Begin
ExampleButton := TButton.Create(self); //Creates an object the same as its self
ExampleButton.Parent := self;
//Button properties go here
//Procedures called here
ExampleButton.OnMouseDown := DragOrDelete;
end;
Above creates the button, below I try to delete it
procedure TForm1.DragOrDelete(Sender: TObject; Button: TMouseButton;
Shift: TShiftState; X, Y: Integer);
begin
CursorPosition := Mouse.CursorPos; ////Location of mouse
ExampleButtonStartingLocation := TPoint.Create((Sender as Tbutton).Left, (Sender as Tbutton).Top);
if Button = mbRight then
FreeAndNil(TButton);
end;
The error I get is constant object cannot be passed as a var parameter.
Is it because I create numerous TButtons but the program doesn't know which one to refer one.
Well,
FreeAndNil(TButton);
should be
(Sender as TButton).Free; // thanks to DH
But this is not good. The RTL routines that call the event handler will still have a reference to the button, and need to continue accessing it after the event handler exits, so freeing it may cause further problems (also, Sender is not a var parameter, so setting it to nil will have no effect in the caller).
A better option might be to do something like creating a custom message with Sender as the wParam and posting it to the main form.
Edit
To do this you would create a user message, e.g.
const
WM_DELETE_CONTROL = WM_USER +1;
and replace the offending line with
PostMessage( FormMain.WindowHandle, WM_DELETE_CONTROL, WPARAM( Sender ), 0 );
Then create a procedure in your main form to handle the message, e.g.
procedure DestroyButton( var Msg : TMessage); message WM_DELETE_CONTROL;
with a definition like
procedure TForm1.DestroyButton( var Msg : TMessage);
begin
// RemoveControl( TButton( Msg.LParam ));
// correction - thanks to Remy Lebeau
TButton( Msg.WParam ).Free;
end;
You should make two changes there.
1) you should remember which object you created into a variable, living long enough that both procedures can access it.
2) you should destroy that object by the variable, mentioned above
Right now you are trying to destroy just some any random button. But that is hardly what you need! You probably want to destroy exactly the button you was creating, not some another one.
So:
1) FTableButton should be moved out of procedure TForm1.Button1Click and promoted into a variable of TForm1 class.
2) procedure TForm1.Button1Click should check if the button was already created and not create the second, third, forth... buttons.
procedure TForm1.Button1Click(Sender: TObject);
var
TableString : String;
Begin
if nil <> Self.FTableButton then
raise Exception.Create('Dynamic button already exists!!!');
TableString := IntToStr(TableNumber);
Self.FTableButton := TButton.Create(self);
....
Alternatively, you might choose to delete already existing button (if any) before creating a new one. Usually that is not a good idea, but in some specific scenarios it might make sense (when you need to "reset" the button object, to drop old customized object and create a new one with non-customized default properties).
procedure TForm1.Button1Click(Sender: TObject);
var
TableString : String;
Begin
Self.FTableButton.Free; // if there already was a dynamic button - destroy it
TableString := IntToStr(TableNumber);
Self.FTableButton := TButton.Create(self);
....
3) Now that you have that specific button remembered inside form's FTableButton variable you can use it to delete that very specific object.
procedure TForm1.ButtonMouseDown(Sender: TObject; Button: TMouseButton;
Shift: TShiftState; X, Y: Integer);
begin
...
if Button = mbRight then
FreeAndNil( Self.FTableButton );
end;
Now there is another potential issue here - the "cleanup after suicide" issue.
David Heffernan in comments states that you can not delete the TButton from its own event handler. David states that after you exit the TForm1.ButtonMouseDown procedure the just deleted button would (or at least potentially may) do some more after-event actions with itself without recognizing it was already deleted, leading to any potential error, most probably Access Violation of nil dereference kind.
I can agree with half of that claim.
1) using TForm and TButton from VCL library for Windows is safe, because VCL (at least in Delphi XE2 version) was carefully designed to avoid this trap. The button's internal event calling sequence is designed to never directly address itself after the TForm1.ButtonMouseDown exited. So, at least in that narrow case David is not correct and that is safe thing to do.
2) That is called "relying over implementation detail" however. It is bad style because it is fragile. It might work in some specific case but suddenly break from many other reasons.
As soon as you would switch from the standard VCL TButton to some other fancy buttons from any other library ( Dev Express, LMD, TMS, anything ) that code my suddenly broke. Or you might switch form VCL to FMX. Or switch from FMX/Win32 to FMX/Android.
Or maybe just upgrading Delphi to some newer version would have VCL broken in that regard (that is highly unlikely but possible nonetheless).
So, to play safe you have to decouple those two actions: the button's event handling and the process of its deletion. There are many possible ways to do it, but they all demand more or less extra work and some understanding more issues. Those ways I see split into two avenues.
1) Timing would not change, killing the button would be immediate. But it should not be button deleting itself, it should be some other component.
That is the approach I like the most. I think deleting button on right-click is not the good idea. User might click it randomly, by mistake, by sudden twitch of the hand. Doing something as extreme as suddenly deleting button would be way too harsh change for a random erratic action.
I think you should go traditional way here. Right click should open the context menu, and in the menu there should be the command to delete the button. That way, the menu would be deleting the button, not the button itself.
You would need to add the TPopupMenu onto the form having one single element - deleting the button.
object mnu1: TPopupMenu
object mniFreeBM: TMenuItem
Caption = 'Free the button'
OnClick = mniFreeBMClick
end
end
procedure TForm18.mniFreeBMClick(Sender: TObject);
begin
FreeAndNil( Self.btnMenu );
end;
Then you would have to connect that menu to the button.
.....
Self.FTableButton := TButton.Create(self);
// FTableButton.OnMouseUp := ButtonMouseUp; -- no more doing it! bad style!
Self.PopupMenu := mnu1;
mnu1.AutoPopup := True;
.....
Here we go. When user would R-click the button - there would come the menu, asking him if he wants to delete that button. If he does - then menu, not the button itself, would be freeing it. If users cancels the menu - then it was his random action and he want the button to be kept alive.
That is the better approach as I see.
2) other approaches revolve around idea of time delay, they assure that button only asks Delphi to be deleted someday later, but there would be no immediate kill.
Delphi when realizing it was asked to do it, would then delete the button some later time, after OnMouseUp procedure (and probably few other event-handling procedures too) are long executed and exited.
If the application is not heavy loaded with looong heaaavy computations, then that "later" is very very short actually. Most probably the user would never be able to see the difference. For playing safe, the button might also make itself invisible until someone would delete it.
There can be many approaches to do it, but I would list two.
2.1) DSM in his answer outlines Post_Message-centered approach. That is a good solid "old school" code. It is fast. It is well-understood. But it also has some limitations.
a) it only works on Windows. Forget Android, iOS and others. Well, if you only intend to work on Windows then you can just use standard VCL buttons which can suicide safely, at least in Delphi XE2.
b) it needs you to make a lot of boiler plate, like declaring extra procedures and constants.
c) it is unsafe - you have to make hard unchecked typecasts between integers and button pointers. Easy to make mistake typing/refactoring.
d) you need to understand Windows implementation: message loop, VCL place inside that message loop, difference between PostMessage, SendMessage and Perform, etc.
That is not a rocket science. For any "old school" desktop programmer it is easy and well known. Well, if you were one you would not ask such a question.
Another approach would use multi-threading. From performance perfectionism point of view that is abomination. Creating a new thread (quite the expensive operation!) just to call back and ask the button to be deleted - is very inefficient. But - that way you have much less code to write. You can use standard Delphi features that would most probably work with every operating system and every forms/buttons library, in current and future Delphi versions.
The code is like that.
procedure TForm18.btnThreadMouseUp(Sender: TObject; Button: TMouseButton;
Shift: TShiftState; X, Y: Integer);
begin
......
if mbRight = Button then
TThread.CreateAnonymousThread( procedure
begin
TThread.CurrentThread.Synchronize( nil, procedure
begin
FreeAndNil( btnThread );
end
);
end
).Start;
end;
This way when you exit the OnMouseButtonUp the button is not being deleted, there only is incoming request from the temporary thread to delete it. When the form would work that request out may differ, but anyway it would be another event that happens after you safely exited the button's event handler. Unless you used another abomination ProcessMessages but you did not and hopefully you never ever would.
I have procedure to show/hide one element on TForm like that:
procedure ShowHideControl(const ParentForm: TForm; const ControlName: String; ShowControl: Boolean);
var
i: Integer;
begin
for i := 0 to pred(ParentForm.ComponentCount) do
begin
if (ParentForm.Components[i].Name = ControlName) then
begin
if ShowControl then
TControl(ParentForm.Components[i]).Show
else
TControl(ParentForm.Components[i]).Hide;
Break;
end;
end;
end;
then I try to use it like:
procedure TForm1.Button6Click(Sender: TObject);
begin
ShowHideEveryControl(TForm(TForm1), 'Button4', True);
end;
Why do I get Access Violation on Button6 click?
For me everything is OK... Button4 exists as a child :)
This cast is wrong:
TForm(TForm1)
You are telling the compiler to ignore the fact that TForm1 is not a TForm instance, and asking it to pretend that it is. That is fine until you actually try to use it as an instance, and then the error occurs.
You need to pass a real instance to a TForm descendent. You can write it like this:
ShowHideEveryThing(Self, 'Button4', True);
Just in case you are not clear on this, the parameter of your procedure is of type TForm. That means you need to supply an instance of a class that either is, or derives from TForm. I repeat, you must supply an instance. But you supply TForm1 which is a class.
And then the next problem comes here:
if (ParentForm.Components[i].Name = FormName) then
begin
if ShowForm then
TForm(ParentForm.Components[i]).Show
else
TForm(ParentForm.Components[i]).Hide;
Break;
end;
Again you have used an erroneous cast. When the compiler tells you that it a particular object does not have a method, you must listen to it. It's no good telling the compiler to shut up and pretend that an object of one type is really an object of a different type. Your button is categorically not a form, so don't try to cast it to TForm.
It is very hard to know what you are actually trying to do here. When you write:
ShowHideEveryThing(Self, 'Button4', True);
It would seem to me to me more sensible to write:
Button4.Show;
It is not a good idea to refer to controls using their names represented as text. It is much safer and cleaner to refer to them using reference variables. That way you let the compiler do its job and check the type safety of your program.
The names used in your function are suspect:
procedure ShowHideEveryThing(const ParentForm: TForm; const FormName: String;
ShowForm: Boolean);
Let's look at them:
ShowHideEveryThing: but you claim that the function should show/hide one element. That does not tally with the use of everything.
FormName: you actually pass a component name, and then look for components owned by ParentForm that have that name. It seems that FormName is wrong.
ShowForm: again, do you want to control visibility of the form, or a single element?
Clearly you need to step back and be clear on the intent of this function.
As an aside, you should generally never need to write:
if b then
Control.Show
else
Control.Hide;
Instead you can write:
Control.Visible := b;
My number one piece of advice to you though is to stop casting until you understand it properly. Once you understand it properly, design your code if at all possible so that you don't need to cast. If you ever really do need to cast, make sure that your cast is valid, ideally by using a checked cast with the as operator. Or at least testing first with the is operator.
Your code shows all the hallmarks of a classic mistake. The compiler objects to the code that you write. You have learnt from somewhere that casting can be used to suppress these compiler errors and now you apply this technique widely as a means to make your program compile. The problem is that the compiler invariably knows what it is talking about. When it objects, listen to it. If ever you find yourself suppressing a compiler error with a cast, take a step back and think carefully about what you are doing. The compiler is your friend.
I have read this article how to add a button to another application. When the Button is added to the parent application, everything seems OK, but when this Button is added to another app called Labform (TLabForm), the code after click is not executed. I created also a descendant to implement simple behavior after click, but no success:
TButton2 = class (TButton)
public
procedure Click; override;
end;
procedure TButton2.Click;
begin
inherited;
MessageBox(ParentWindow, 'Hello', 'Window', MB_OK);
end;
procedure TForm1.btn1Click(Sender: TObject);
var
Button2 : TButton2 ;
Hand: THandle;
begin
// Hand:= FindWindow('TLabForm', 'Labform'); // button added, but SHOWS NO message after click
Hand:= FindWindow('TForm1', 'Form1'); // button added, and SHOWS message after click
if Hand <> 0 then
begin
Button2 := TButton2.Create(self);
Button2.ParentWindow := hand;
Button2.BringToFront;
end
else
ShowMessage('handle not found');
end;
How to solve it?
thanx
Whilst it is technically possible to do what you want, it is excruciatingly difficult. Raymond Chen wrote about this at some length. The executive summary:
Is it technically legal to have a parent/child or owner/owned relationship between windows from different processes? Yes, it is technically legal. It is also technically legal to juggle chainsaws.
So, you are attempting something with difficulty akin to juggling chainsaws. Unless you have a deep understanding of Win32 you've got no chance of succeeding.
So, if you want to modify the GUI of an existing process, and it's not tractable to do so with code in a different process, what can you do? Well, it follows that you need to execute code inside the target process.
That's easy enough to do with DLL injection. Inject a DLL into the process and modify it's UI from that DLL. Still not trivial. You'll have the best chance of success if you subclass a window by replacing the existing window procedure with one of your own. That will allow you to run your UI modification code in the UI thread.
This question is about being able to save routines, and being able to select them from a list…. When you select one it knows what to link where etc. Just for understanding. not the actual program
Say I want to create a routine in a Delphi form. And I want to create several different ones. They wont be exactly the same but some might be similar. I want to know how you can save things in Delphi and when you close or terminate the application they will remain remembered when you reopen it. I have no idea where to start and how to work this. Any help would be great. Just a hint or a direction, maybe a website with more info or even examples. I’m going to try to give a simpler description below about how it would look on the form…. Just for the idea and I think if I understand this then it would be enough, or a good start at least.
The form will contain a list box a save button and 4 different edit boxes. Lets say I type in edit1;1 and edit2;2 and edit3;3 and edit4;4. Then click the save button and it remembers these 4 value to each edit box and lets say saves in under the value in the list box of ≔edit1.text + ‘to’ + edit4.text. Hope it makes sense so far and then I type in the edit boxes everything the wrong way around. edit1;4 and edit2;3 and edit3;2 and edit4;1. And click save button and it does that again (≔edit1.text + ‘to’ + edit4.text) into the list box. Then I want to close the application. Open it again and still have this in there and still be able to add more of these odd samples….
Can anyone help me?
Edit question, might make it more clear....
I'm going to place the following elements on the form: 2 listboxes(with each 3 lines, in the first listbox: wood, plastic and glass. in the second listbox: tree,cup,window.)
Now I want to link the correct ones, they are in order here, but what is they were not. In a table or in a memory of the application which is not visible on the form I want to link them.
Then if i were to put two edit boxes on the form as wel and I type in the first one wood or tree, it places the other one in the other edit box. So in a way I suppose you are creating a table which knows which one correcsponds with which but also looksup up when you type in edit box. hope that makes sense
From what you wrote I assume that you already know how to add the values to remember to your list box, so I won't deal with this part here. Starting with this answer, you should already have a clue to what to look at in the delphi help files. Please be aware that I didn't test the example, so there may be typos in it.
To store data in the computers registry, delphi provides you with the unit Registry, which contains a class TRegistry.
TRegistry has all the methods needed to retrieve and store values. Following is a short example without any practical use, just to give you the picture. Please be aware that, like mentioned in the comments, there's plenty of room left for you to optimise it. It would, for example, be better to create the TRegistry object only once and then repeatedly call the methods. And - while this plain old delphi unit I wrote is syntactically valid - you might be better off using a more object-oriented approach, like deriving a new class from TRegistry. Please do also check the documentation for the return values of the methods, as some (like OpenKey) simply return false when they are unable to fulfill your request while others (like ReadString) can throw an execption.
unit Unit1;
interface
uses TRegistry, Windows;
procedure saveStringToRegistry(name : String; value : String);
function readIntegerFromRegistry(name : String) : Integer;
implementation
procedure saveStringToRegistry(name : String; value : String);
var r : TRegistry;
begin
r := TRegistry.Create;
try
r.RootKey := HKEY_CURRENT_USER; // Defined in unit Windows, as well as HKEY_LOCAL_MACHINE and others.
r.OpenKey('Software\Manufacturer Name\Produkt Name\Other\Folders',true); // TRUE allows to create the key if it doesn't already exist
r.WriteString(name,value); //Store the contents of variable 'value' in the entry specified by 'name'
r.CloseKey;
finally
r.Free;
end;
end;
function readIntegerFromRegistry(name : String) : Integer;
var r : TRegistry;
begin
r := TRegistry.Create;
try
r.RootKey := HKEY_LOCAL_MACHINE; // Defined in unit Windows
r.OpenKey('Software\Manufacturer Name\Produkt Name\Other\Folders',false); // FALSE: do not create the key, fail if it doesn't already exist
result := r.ReadInteger(name);
r.CloseKey;
finally
r.Free;
end;
end;
end.
OK, now you could use a for loop in your application to process all the items of your list box and save it to the registry using the procedure above, like this:
for i := 0 to ListBox1.Count -1 do
saveStringToRegistry('Entry' + IntToStr(i),ListBox1.Items[i];
Then, you would probably save the number of items you have (of course you would have to define the procedure saveIntegerToRegistry):
saveIntegerToRegistry('NumberOfEntries',ListBox1.Count);
When you need to reload the data, you could do:
ListBox1.Clear;
for i := 0 to readIntegerFromRegistry('NumberOfEntries') -1 do
ListBox1.Items.Add(readStringFromRegistry('Entry' + IntToStr(i));
OK, it's a very basic example, but should give you pointer in the right direction. It could of course need some exception handling (imagine the user accidently deleted Entry42 from the registry, but NumberOfEntries still says there are 55 entries).
Im not sure if i have explaned this the best i can but, here we go...
I have 2 Custom components on a form, Which are link together at design time through the IDE. Whenever i call a procedure from on of the Component i get the Access violation,
Access violation at address 0049A614
in module 'Project2.exe'. Read of
address 00000034.
This is a small section of my code
TMyClient = class(TClientSocket)
{...}
end;
and...
TPresence = class(TComponent)
private
ftheClient: TMyClient
public
procedure SetStatus(status: string);
published
property UserName : string read fUserName write fUserName;
property theClient: TMyClient read ftheClient write ftheClient;
end;
procedure TPresence.SetStatus(status: string);
begin
try
***** if theClient = nil then
Exception.Create('theClient is Nil');
except
on e:Exception do
MessageDlg(e.classname+', '+e.message, mtWarning, [mbOK], 0);
end;
{...}
end;
0049A614 is at the *****, and the IDE stops here.
I Have also tried to do the assign at run time with
Presence1.theClient := MyClient1;
with no luck
using procedures from Presence1 or MyClient1 that do not rely on each other work fine.
Delphi 7
Follow Up:
from mghie comments, i rethought about it.
I removed the TPresence Component from the form (which caused some strange IDE errors, that might have had something to do with it) and created it design time, assigning everything that was needed. Now it works, but putting the TPresence Component back on the from brings the error back.
Thankyou for your help guys, i should be able to work this one out now, if i can't ill reopen another question :)
You seem to be thinking that the exception is raised because the client field of Presence1 is not set - if you do however get the exception "Read of address 00000034" it means that the Self pointer in the SetStatus() call is nil. That would indicate that you call SetStatus() on an unassigned TPresence reference. It is not really possible to tell the reason for that from the snippet you posted, but it should get you started debugging.
I would still advise you to write a proper setter method for all component references in your own custom components - first because you have a better hook when debugging such problems (you can set a breakpoint there), and second because you should always call TComponent.FreeNotification() on such linked components to be able to track their destruction and set the internal reference to nil.
We probably need more of your code. It is possible you are not correctly creating an instance of TPresence which would give you the error you are experiencing. Try to give us a simple as possible code snippet that causes your error.