Delphi virtual keyboard: edit the object I'm currently in
Solved
duffduffduff
Posted messages
15
Status
Member
-
nicocorico Posted messages 846 Status Member -
nicocorico Posted messages 846 Status Member -
Hello everyone and thank you in advance for your help.
Please don't be too technical as I'm close to a level 0 in Delphi but I'm having fun ;-)
I'm creating a virtual keyboard in my goods order app via email.
My issue is that I would like Delphi to know in which edit field I am when I click on a key of my virtual keyboard without having to do it manually for each edit...
P.S.: I built my keyboard with a string grid
I hope I explained my problem well.
Thank you,
Céd
Please don't be too technical as I'm close to a level 0 in Delphi but I'm having fun ;-)
I'm creating a virtual keyboard in my goods order app via email.
My issue is that I would like Delphi to know in which edit field I am when I click on a key of my virtual keyboard without having to do it manually for each edit...
P.S.: I built my keyboard with a string grid
I hope I explained my problem well.
Thank you,
Céd
3 answers
Hello,
I don't exactly understand the question, but the answer is probably related to the "Col" and "Row" of the stringgrid, giving respectively the column and the row of the current item.
So you would just need to read these two values to deduce the key correspondence and respond accordingly...
--
The oak was also an acorn before it became an oak
I don't exactly understand the question, but the answer is probably related to the "Col" and "Row" of the stringgrid, giving respectively the column and the row of the current item.
So you would just need to read these two values to deduce the key correspondence and respond accordingly...
--
The oak was also an acorn before it became an oak
Sorry for the late reply, I took a good week off programming...
I misunderstood, my keyboard is indeed "full" and works perfectly as follows:
When I "enter" an edit, for example "edit1" (onenter)
I tell my string variable called "focus" focus:='edit1';
And so when I click on the 'A' key of my virtual keyboard, I say:
if focus='edit1' then edit1.text:=edit1.text+(the letter A from the virtual keyboard).
So far, so good... when I have 1, 2,... 5 edits, it's pretty simple, I just have to do:
if focus='edit1' then
if focus='edit2' then
if focus='edit3' then....edit3.text:=....
But imagine I have more than 50 edits in my program.... it becomes tedious and "repetitive" to code.
"This is where I get confused in my explanation"
What I'm looking for is for my app to remember automatically which is the last edit that was selected (that part is quite simple) but I would like my virtual keyboard to type in the right edit without having to write a line of code for each edit.
It's like I could program this:
on keyboard(keyA).click then last_focusededit.text:=.......
OR I could tell my string focus which edit I'm in and do
focus.text:=.... (which would mean: edit1.text:=.....)
Sorry again, even when I read it back, I don't understand anything ;-)
I misunderstood, my keyboard is indeed "full" and works perfectly as follows:
When I "enter" an edit, for example "edit1" (onenter)
I tell my string variable called "focus" focus:='edit1';
And so when I click on the 'A' key of my virtual keyboard, I say:
if focus='edit1' then edit1.text:=edit1.text+(the letter A from the virtual keyboard).
So far, so good... when I have 1, 2,... 5 edits, it's pretty simple, I just have to do:
if focus='edit1' then
if focus='edit2' then
if focus='edit3' then....edit3.text:=....
But imagine I have more than 50 edits in my program.... it becomes tedious and "repetitive" to code.
"This is where I get confused in my explanation"
What I'm looking for is for my app to remember automatically which is the last edit that was selected (that part is quite simple) but I would like my virtual keyboard to type in the right edit without having to write a line of code for each edit.
It's like I could program this:
on keyboard(keyA).click then last_focusededit.text:=.......
OR I could tell my string focus which edit I'm in and do
focus.text:=.... (which would mean: edit1.text:=.....)
Sorry again, even when I read it back, I don't understand anything ;-)
Hello,
Oh yes, I see! So, in programming, when there's repetition of code, it's generally because there's a way to make it much simpler and therefore much better!
Here, it's the variable that holds the Edit concerned by the key press that is wrong, meaning that what is needed is not a string, but a TEdit or a parent class.
In fact, TEdit is a class type, and it's possible to declare a variable of this type without actually creating the object, as this variable is in all cases just a pointer to an object, and not the object itself.
So we declare a variable "Focus: TEdit" or "Focus: TObject" and then we can assign it a pointer to the focused Edit, thus not with the name of the Edit, but with its pointer: "Focus:= Edit1".
Then, we can directly address the focused Edit using the variable: "Focus.text:= Focus.Text + (Letter from the virtual keyboard)", as if Focus were the Edit itself, which indeed gives us a formulation like this:
on keyboard(keyA).click then focus.text:=.......
Therefore, to define the content of "Focus," using the OnEnter event of TEdit is suitable, and it is possible to share it among all the Edits (by declaring it for one of them and copying the event name for the others), and then using "Sender": "If sender is TEdit then Focus:= Sender as TEdit".
Oh yes, I see! So, in programming, when there's repetition of code, it's generally because there's a way to make it much simpler and therefore much better!
Here, it's the variable that holds the Edit concerned by the key press that is wrong, meaning that what is needed is not a string, but a TEdit or a parent class.
In fact, TEdit is a class type, and it's possible to declare a variable of this type without actually creating the object, as this variable is in all cases just a pointer to an object, and not the object itself.
So we declare a variable "Focus: TEdit" or "Focus: TObject" and then we can assign it a pointer to the focused Edit, thus not with the name of the Edit, but with its pointer: "Focus:= Edit1".
Then, we can directly address the focused Edit using the variable: "Focus.text:= Focus.Text + (Letter from the virtual keyboard)", as if Focus were the Edit itself, which indeed gives us a formulation like this:
on keyboard(keyA).click then focus.text:=.......
Therefore, to define the content of "Focus," using the OnEnter event of TEdit is suitable, and it is possible to share it among all the Edits (by declaring it for one of them and copying the event name for the others), and then using "Sender": "If sender is TEdit then Focus:= Sender as TEdit".
No problem, it was a pleasure!
And the explanation about the "Sender" is that events are intended to be called by a third-party component, as in the case of TEdit: That is to say, the TEdit or another "TControl" receives the Windows event and triggers the event that has been declared in the Form via the object inspector; But to know who is the source of the event, there is always the "Sender" parameter that specifies the sender, thus the TEdit in this case.
In concrete terms, an event is a field containing a pointer to a specific object as well as one of the declared methods of that object; The event is therefore the call of a method of an instance of a third-party object.
Thus, when we declare an "OnEnter" event of a TEdit in the object inspector, Delphi adds a typed method in the Form, Edit1OnEnter for example, such that OnEnter is declared in the TEdit, and then connects the corresponding event of the TEdit to it.
The TEdit then calls the event contained in its "FOnEnter" field, passing itself as Sender, which triggers the call to the Edit1OnEnter method contained in the Form, and that’s it!
And the explanation about the "Sender" is that events are intended to be called by a third-party component, as in the case of TEdit: That is to say, the TEdit or another "TControl" receives the Windows event and triggers the event that has been declared in the Form via the object inspector; But to know who is the source of the event, there is always the "Sender" parameter that specifies the sender, thus the TEdit in this case.
In concrete terms, an event is a field containing a pointer to a specific object as well as one of the declared methods of that object; The event is therefore the call of a method of an instance of a third-party object.
Thus, when we declare an "OnEnter" event of a TEdit in the object inspector, Delphi adds a typed method in the Form, Edit1OnEnter for example, such that OnEnter is declared in the TEdit, and then connects the corresponding event of the TEdit to it.
The TEdit then calls the event contained in its "FOnEnter" field, passing itself as Sender, which triggers the call to the Edit1OnEnter method contained in the Form, and that’s it!
Well, this is where it’s important to grasp the principle of inheritance: all objects have at least one common ancestor (TObject), from which they inherit properties and code; Knowing this, to store a more flexible object pointer, it is sufficient to type it with their common ancestor, especially if it contains the elements we are interested in;
We see that TDbEdit and TEdit have TCustomControl as a common ancestor, so it is enough to declare:
Focus: TCustomControl
thus in this variable we can store DbEdit as well as TEdit, while having access to the Text property which belongs to their common ancestor TCustomControl.
That said, there is nothing mandatory about this, and on the other hand we quickly face the definition of pointers being much fuzzier, hence defined in TObject.
We can declare:
Focus : TObject;
As TObject, we can place an object of any class in this variable:
Focus:= Sender;
Focus:= Edit1;
Focus:= DbEdit1;
etc...
Then, to find out what type this variable without a type is, it is enough to test it:
If Focus is TCustomControl then (Focus as TCustomControl).Text:= '';
And therefore:
If Focus is TCustomControl then With (Focus as TCustomControl) do Text:= Text + keyboard letter;
The "is" operator allows to find out if the object is compatible with the specified class, knowing that it returns true if it's a descendant even if distant;
Thus TEdit is TCustomControl is true,
TDbEdit is TCustomControl is also true,
TDbEdit is TObject is also true.
The "as" operator is the counterpart of "is", it allows to state that we are using the object in the specified type.
So "is" allows to test the compatibility of the object with such class, and "as" allows to use it as such class, provided it descends from the given class.
Instead of "as", we can also do TCustomEdit(Focus).Text;
In fact, the Focus variable only contains a pointer to an object, and giving it a type only serves to specify to the compiler what we can expect from this variable, thus avoiding the need for testing later. Therefore, when the type is known, or when there is a common class beyond TObject responding to the different types we will place there, it is better in this case to declare the variable with the most evolved type possible, thus making the code clearer (besides the variable name, the type provides information about its content) and avoiding tests and other type casts.
Like many languages, Delphi is entirely structured around objects, and I suggest you expand your knowledge of it to better understand the mechanisms at play:
This tutorial seems well-constructed to me:
https://fbeaulieu.developpez.com/guide/?page=page_16
The oak was also an acorn before it became an oak
We see that TDbEdit and TEdit have TCustomControl as a common ancestor, so it is enough to declare:
Focus: TCustomControl
thus in this variable we can store DbEdit as well as TEdit, while having access to the Text property which belongs to their common ancestor TCustomControl.
That said, there is nothing mandatory about this, and on the other hand we quickly face the definition of pointers being much fuzzier, hence defined in TObject.
We can declare:
Focus : TObject;
As TObject, we can place an object of any class in this variable:
Focus:= Sender;
Focus:= Edit1;
Focus:= DbEdit1;
etc...
Then, to find out what type this variable without a type is, it is enough to test it:
If Focus is TCustomControl then (Focus as TCustomControl).Text:= '';
And therefore:
If Focus is TCustomControl then With (Focus as TCustomControl) do Text:= Text + keyboard letter;
The "is" operator allows to find out if the object is compatible with the specified class, knowing that it returns true if it's a descendant even if distant;
Thus TEdit is TCustomControl is true,
TDbEdit is TCustomControl is also true,
TDbEdit is TObject is also true.
The "as" operator is the counterpart of "is", it allows to state that we are using the object in the specified type.
So "is" allows to test the compatibility of the object with such class, and "as" allows to use it as such class, provided it descends from the given class.
Instead of "as", we can also do TCustomEdit(Focus).Text;
In fact, the Focus variable only contains a pointer to an object, and giving it a type only serves to specify to the compiler what we can expect from this variable, thus avoiding the need for testing later. Therefore, when the type is known, or when there is a common class beyond TObject responding to the different types we will place there, it is better in this case to declare the variable with the most evolved type possible, thus making the code clearer (besides the variable name, the type provides information about its content) and avoiding tests and other type casts.
Like many languages, Delphi is entirely structured around objects, and I suggest you expand your knowledge of it to better understand the mechanisms at play:
This tutorial seems well-constructed to me:
https://fbeaulieu.developpez.com/guide/?page=page_16
The oak was also an acorn before it became an oak
Great, nothing is impossible actually ;-) but I still have a but
var Focus : TObject; // this is okay
edit1onenter --> Focus:= Sender; // all is well
for the next part I'm stuck!
If Focus is TCustomControl then With (Focus as TCustomControl) do Text:= Text + keyboard letter;
the app compiles but nothing happens. Did I miss an episode?
var Focus : TObject; // this is okay
edit1onenter --> Focus:= Sender; // all is well
for the next part I'm stuck!
If Focus is TCustomControl then With (Focus as TCustomControl) do Text:= Text + keyboard letter;
the app compiles but nothing happens. Did I miss an episode?
As too much control kills control, I thought I’d be helpful by pointing out the well-structured version that I never use myself, but it works a bit differently than I thought until now, as I discover that the "is" and "as" operators only work if the type strictly matches, and that’s probably why I gave up using them a long time ago! So, for the operation as I described it, you need to use the InheritsFrom method instead of "is", and forced typecasting instead of "as":
If (Focus <> nil) and Focus.InheritsFrom(TCustomControl) then With TCustomControl(Focus) do Text:= Text + keyboard letter;
So, we start by testing the validity (non-null pointer) of Focus because we are calling a method from its pointer;
"InheritsFrom" returns True if the specified class matches that of the object or any of its ancestors;
TCustomControl(Focus) forces the compiler to treat the object as...
Moreover, to find out what's going wrong, nothing beats the integrated debugger! The F4 key allows you to run up to the cursor line and stop there, F7 advances step by step through function calls, F8 is a superficial step, and F9 runs to the end.
When the program is stopped on a line, you can inspect the value of certain variables and track them, which is extremely valuable for checking the conformity of the code execution...
The debugger works line by line only, so it’s sometimes necessary to break the lines into several parts:
If (Focus <> nil) and Focus.InheritsFrom(TCustomControl) then
With TCustomControl(Focus) do
Text:= Text + keyboard letter;
If (Focus <> nil) and Focus.InheritsFrom(TCustomControl) then With TCustomControl(Focus) do Text:= Text + keyboard letter;
So, we start by testing the validity (non-null pointer) of Focus because we are calling a method from its pointer;
"InheritsFrom" returns True if the specified class matches that of the object or any of its ancestors;
TCustomControl(Focus) forces the compiler to treat the object as...
Moreover, to find out what's going wrong, nothing beats the integrated debugger! The F4 key allows you to run up to the cursor line and stop there, F7 advances step by step through function calls, F8 is a superficial step, and F9 runs to the end.
When the program is stopped on a line, you can inspect the value of certain variables and track them, which is extremely valuable for checking the conformity of the code execution...
The debugger works line by line only, so it’s sometimes necessary to break the lines into several parts:
If (Focus <> nil) and Focus.InheritsFrom(TCustomControl) then
With TCustomControl(Focus) do
Text:= Text + keyboard letter;
Haa bah it's my fault, it's that I wrote TCustomControl instead of TCustomEdit, TCustomControl not being a parent of Edit-type controls! I've been working too much lately! And I've been working too much with TCustomControl!
TCustomEdit is the closest common ancestor between TEdit and TDbEdit, and contains the property "Text", whereas putting "With TCustomControl(Focus) do" doesn't generate an error because the compiler finds the property "Text" in "Form1"...
And "is" and "as" work well on the ancestors so, it's me who has been making the mistake from the beginning, sorry!
I promise, that line works, and this time I tested it beforehand!
If Focus is TCustomEdit then With (Focus as TCustomEdit) do Text:= Text + keyboard letter;
This shows the difficulty of pinpointing errors in programming, as this one may be obvious, but once you get it in your head that it's this name and not another, you don't see it anymore! Other errors are much sneakier, and the usefulness of the integrated debugger quickly becomes apparent for detecting them!
TCustomEdit is the closest common ancestor between TEdit and TDbEdit, and contains the property "Text", whereas putting "With TCustomControl(Focus) do" doesn't generate an error because the compiler finds the property "Text" in "Form1"...
And "is" and "as" work well on the ancestors so, it's me who has been making the mistake from the beginning, sorry!
I promise, that line works, and this time I tested it beforehand!
If Focus is TCustomEdit then With (Focus as TCustomEdit) do Text:= Text + keyboard letter;
This shows the difficulty of pinpointing errors in programming, as this one may be obvious, but once you get it in your head that it's this name and not another, you don't see it anymore! Other errors are much sneakier, and the usefulness of the integrated debugger quickly becomes apparent for detecting them!
THANK YOU THANK YOU THANK YOU THANK YOU THANK YOU THANK YOU THANK YOU THANK YOU THANK YOU THANK YOU THANK YOU THANK YOU THANK YOU THANK YOU THANK YOU THANK YOU THANK YOU THANK YOU THANK YOU THANK YOU THANK YOU THANK YOU THANK YOU THANK YOU THANK YOU THANK YOU THANK YOU THANK YOU THANK YOU THANK YOU THANK YOU THANK YOU THANK YOU THANK YOU THANK YOU THANK YOU THANK YOU THANK YOU THANK YOU THANK YOU THANK YOU THANK YOU THANK YOU THANK YOU THANK YOU THANK YOU THANK YOU THANK YOU THANK YOU THANK YOU THANK YOU THANK YOU THANK YOU THANK YOU THANK YOU THANK YOU THANK YOU THANK YOU THANK YOU THANK YOU THANK YOU THANK YOU THANK YOU THANK YOU THANK YOU THANK YOU THANK YOU THANK YOU THANK YOU THANK YOU THANK YOU THANK YOU THANK YOU THANK YOU THANK YOU THANK YOU THANK YOU THANK YOU THANK YOU THANK YOU THANK YOU THANK YOU THANK YOU THANK YOU THANK YOU THANK YOU THANK YOU THANK YOU THANK YOU THANK YOU THANK YOU THANK YOU THANK YOU THANK YOU THANK YOU THANK YOU THANK YOU THANK YOU THANK YOU THANK YOU THANK YOU THANK YOU THANK YOU THANK YOU THANK YOU THANK YOU THANK YOU THANK YOU THANK YOU THANK YOU THANK YOU THANK YOU THANK YOU THANK YOU THANK YOU THANK YOU THANK YOU THANK YOU THANK YOU THANK YOU THANK YOU THANK YOU THANK YOU
If you have the time... I have a question that I have already asked on a forum and no one has been able to answer. An acquaintance, "a little self-taught genius in computer science," created a component called "AUTOTABLE" for versions starting from D3 that is supposed to automatically refresh a table from a network database when a modification is made.
The component works as an ADO table, but the problem is that I cannot find the subtlety of why it was invented... in other words, to auto-refresh itself.
I have tried to contact this gentleman, but he has a lot of work and doesn't have the time to deal with a small component (probably no internet for him now) that he created more than 18 years ago...
Here is the link where you can find it: https://torry.net/authorsmore.php?id=1241
But as I said, there's no rush, just a lot of curiosity.
PS: I tested it under D6 with Access and Paradox
See you!
If you have the time... I have a question that I have already asked on a forum and no one has been able to answer. An acquaintance, "a little self-taught genius in computer science," created a component called "AUTOTABLE" for versions starting from D3 that is supposed to automatically refresh a table from a network database when a modification is made.
The component works as an ADO table, but the problem is that I cannot find the subtlety of why it was invented... in other words, to auto-refresh itself.
I have tried to contact this gentleman, but he has a lot of work and doesn't have the time to deal with a small component (probably no internet for him now) that he created more than 18 years ago...
Here is the link where you can find it: https://torry.net/authorsmore.php?id=1241
But as I said, there's no rush, just a lot of curiosity.
PS: I tested it under D6 with Access and Paradox
See you!
That said, the content of each string is accessible at runtime via "Items" +which is the container of the displayed strings+ using "Col" and "Row" to determine the relevant key.
But to do better, you should practice filling these strings programmatically rather than through the object inspector; it’s easy and very useful for going further.
For my part, I would even prefer to use a Drawgrid: from this component, it's easy to draw the keys yourself in the "OnDrawCell" method using colors and justifications with "DrawText" (which is a Windows function, not Delphi, thus in win32sdk).
Going beyond the use of simple strings to determine the keys allows for the implementation of all types of behaviors, such as the "enter", "esc", "back" keys, etc.
Ideally +while staying simple+ it would be to create a two-dimensional array representing the rows and columns of the keyboard, with a field for the virtual key of type Vk_ (virtual Key windows, coding all the keys of the keyboard), from which it is possible to subtract the ASCII code, thus the character to display or another symbol, with the bitmaps of an "ImageList" for example.
The content of this array can be defined simply as a constant.
Based on this, we know which character to draw during an "OnDrawCell" by reading from this array with the "Col" and "Row" present as parameters.
Similarly, it is easy to react during the hover of a key, or a click, by retrieving the concerned character from the array using the "Col" and "Row" properties of the component. However, these properties only reflect the last selected cell, so to know which cell is hovered over with "OnMouseMove", or more generally to know which cell is at a certain position, you have to use "MouseToCell".