Virtual keyboard - best way to send button clicks to memo? - delphi

I'm still on D2007 and have need to create a unicode enabled virtual keyboard. So I'm using TMS's unicode buttons to simulate the keys, but what I want to avoid is having to assign
mmo.Lines.Text := mmo.Lines.Text + Button1.Caption;
~50 times. There just has to be a better way to send one character at a time to the memo. Is there a way to use the Tag property in the Object Inspector and assign a value from there, or how?
How would you do this?

Assign the same OnClick event handler to all your buttons. Instead of referring to each button by name, as you did above with Button1, use the Sender parameter. That's what it's there for. It tells you which control's event was triggered to cause the handler to run.
Sender has the static type TObject. When one of your buttons is clicked, Sender will have the run-time type TButton, or whatever actual class you're using. To get your code to compile , you'll need to type-cast. For example:
procedure TKeyboardForm.ButtonClick(Sender: TObject);
begin
mmo.Lines.Text := mmo.Lines.Text + (Sender as TButton).Caption;
end;

A speedbutton of some kind may be a better choice for the buttons, since they don't grab focus. Then the blinking cursor would remain in the memo.
You may get the same result by tweeking the properties of the button you use. I don't know the tms-button, but you can try to set tabstop to false.

I'm getting maybe a little old and I live out in the country so I'm probably just behind the times. I don't know if "snarg" means "I'm still not getting it" or maybe "Sorry, I found my error." On the chance that it means the latter, it seems like this suggestion from Rob Kennedy was right on the money:
procedure TKeyboardForm.ButtonClick(Sender: TObject);
begin
mmo.Lines.Text := mmo.Lines.Text + (Sender as TButton).Caption;
end;
If you put a break point right on the line inside the event handler and evaluate TButton(Sender).Caption, doesn't it show you the caption of the button you pressed? I see that you are iterating through all the buttons now. If all you want is the caption, it seems like that code should solve it.
Jack

Related

How do I make a TVirtualStringTree process key presses with a higher precedence?

We've got a certain search form at work that was revamped recently. Its functionality is that of a standard search form: enter a few criteria in some boxes at the top, hit the Search button, and display results in a grid down below. But it was ugly and very limited in functionality, so one of my coworkers rebuilt it... right before leaving for a new job. Now I'm trying to complete the last few details.
One of the changes was replacing the old TListBox grid with a much more powerful TVirtualStringTree. But in the process, it appears to have broken something: before, if you clicked on a row in the grid (giving the grid in put focus) and hit Enter, the appropriate event handler would fire and deal with your input, opening the detail view for the selected item. In this grid, however, pressing Enter causes the TButton on the form with the Default = true property to fire its OnClick instead.
How can I make the TVirtualStringTree take precedence when it has input focus, so that it will respond to the user pressing Enter itself before (and preferably instead of) dispatching it to the form?
Handle the WM_GETDLGCODE message and include DLGC_WANTALLKEYS in the returned value. For example:
procedure WMGetDlgCode(var Message: TWMGetDlgCode); message WM_GETDLGCODE;
....
procedure TMyControl.WMGetDlgCode(var Message: TWMGetDlgCode);
begin
inherited;
Message.Result := DLGC_WANTALLKEYS;
end;
Depending on whether or not your control already handles this message and returns something other than 0 you might need to use:
Message.Result := Message.Result or DLGC_WANTALLKEYS;
If you don't want to modify the code for this class then use an interposer or set the WindowProc property of the control to intercept its window procedure.

Avoid window getting focus

I am working on a virtual keyboard the problem is when i press a key on the virtual keyboard the window witch the data needs to be sent loses focus. How can i avoid that ?
When your keyboard form receives focus, part of the message it receives is the handle of the window that lost focus (wParam). Do what you need to do and set the focus back to the window that lost focus.
EDIT: See the documentation on WM_SETFOCUS
EDIT 2:
Also, you could use the following when creating your custom form:
procedure TMainForm.CreateParams(var Params: TCreateParams) ;
//const WS_EX_NOACTIVATE = $8000000;
begin
inherited;
Params.ExStyle := Params.ExStyle + WS_EX_NOACTIVATE;
end;
To prevent your form from activating (taking focus from the other form). Like I alluded to in my comment, you should probably be using non-windowed controls for keys.
The only method I've seen to do what you want is to disable the window with the virtual keyboard EnableWindow(hWnd, FALSE).
Now, if the window is disabled you will not get mouse messages, right? You have to options:
The easy one: Use WM_SETCURSOR. It is sent even to disabled windows, and in the high-order word of lParam you have the identifier of the original message (WM_LBUTTONDOWN, etc.). The coordinates of the cursor can be read using GetMessagePos().
The cool one: Use a windows hook: SetWindowsHookEx(WH_MOUSE, ...). You'll have full control of your mouse messages.
Use a class that does not have the ability to gain keyboard focus, but only responds to mouse input.
Solution: Derive your virtual keyboard from TControl or TGraphicControl, and not from TWinControl or TCustomControl.
Does this help?
procedure WMMouseActivate(var Message: TWMMouseActivate); message WM_MOUSEACTIVATE;
procedure TMyForm.WMMouseActivate(var Message: TWMMouseActivate);
begin
Message.Result := MA_NOACTIVATE;
end;

Delphi: TabStop problems of TRadioButton

When TRadioButton has TabStop=True, it's acting very strange.
If you will try to switch focus between many radio buttons on a form using Tab key, you would do it only 1 time per app session. The tabulation is one-way, never returning back to the first radio button. Also when the focus is moving across radio buttons, they becoming "checked" automatically.
Can this behavior be fixed without creating my own component?
I want standard radio buttons to
switch focus cyclically
prevent radio button from checking when the focus comes into it (I want my users to check them using Space key)
I understand that you're working with existing code, which is a real world constraint that's too often dismissed in these forums.
Sounds to me like checkboxes would suit you better. You can enforce the exclusivity normally expected of RadioButtons in the OnChecked event. That should solve your tabbing/focus and selection/deselection issues.
Checkboxes won't be checked automatically upon receiving focus, and your users can check/uncheck them with the space key.
You can put code in the OnEnter event to prevent the checkbox from selecting.
You'll need to store the previously selected RadioButton somehow though.
var
SelectedRadioButton: TRadioButton;
//event shared by all radiobuttons
procedure TForm1.RadioButton1Enter(Sender: TObject);
begin
if Sender <> SelectedRadioButton then begin
SelectedRadioButton.Checked:= true;
end;
end;
procedure TFrameOrder.RadioButton1Click(Sender: TObject);
begin
SelectedRadioButton:= (Sender as TRadioButton);
end;
procedure TFrameOrder.RadioButton1KeyPress(Sender: TObject; var Key: Char);
var
MyRadioButton: TRadioButton;
begin
MyRadioButton:= (Sender as TRadioButton);
if Key in [#32,#13] then begin
MyRadioButton.Checked:= true;
RadioButton1Click(MyRadioButton);
end; {if}
end;
It probably clearer to create a new TMyRadioButton component though because this will clutter up your regular code.
I have found an interesting article of Craig Stuntz about this problem. As I can see, I'll need to create my own control to solve it.
By default only one RadioButon has property TabStop = True;
All Radiobuttons are treated as one controll.
When radiobutton has focus you can switch beetween radiobutons using arrow up and down.
Now when user choose one option they can press tab to switch to another controll (without changing radio options).

When I add a TPanel to a TToolBar, do I get a TPanel or a TToolButton?

When Delphi (2006) goes quantum: I've got "something" that appears to be both a TToolBar and a TPanel, depending on how you observe it. I'd like to understand what's going on.
Here is how to create it and what happens:
in the DFM
add a TToolBar named bar;
in that TToolBar, put a TPanel.
in the code and at runtime:
the panel appears in the list of buttons bar.Buttons[], let's say at index i
bar.Buttons[i], from the compiler point of view, is a TToolButton
bar.Buttons[i].ClassName = 'TPanel'
(bar.Buttons[i] is TToolButton) = true, but that's the compiler optimising the call to 'is' out;
indeed IsBarButton(bar.Buttons[i]) is false for function IsBarButton (defined below);
bar.Buttons[i].Name is the name I gave the TPanel in the DFM
inspecting the value bar.Buttons[i] in the debugging:
it has a property 'Caption' the real TToolButton's don't have
strangely, it has all properties TToolButton's have, like TToolButton.Indeterminate (=true).
IsToolButton:
function IsToolButton(X : TObject) : boolean;
begin
Result := X is TToolButton;
end;
So bar.Buttons[i] both is and is not a TToolButton... what's up ?
(Bottom story is I'd like to distinguish my TPanel from the genuine TToolButton's. This I can do in more or less hackish ways. My goal by asking this question here, is to get a fuller understanding of what's really happening here.)
Question: what is happening ?
Sub-question: is it legitimate to add a TPanel to a TToolBar ?
The only thing the OS allows to be added to a tool bar is a tool button. To add anything else, you technically need to create a button and then put your other things on top of it. The button that gets added is literally a placeholder. It's there to take up space so the next thing you add gets positioned properly.
You can see this sometimes if the non-tool-button control you add is transparent. Then you can see the tool bar's separator underneath, so it looks like there's a vertical line running through the middle of your control.
When you add a non-tool-button control to the tool bar, the Buttons property indeed lies about the type of the control. You'll notice throughout ComCtrls.pas that TToolBar itself always casts the buttons to TControl and then checks whether they really descend from TToolButton. It's completely legitimate to add non-buttons to a tool bar; that's why the Form Designer allows it in the first place.
I suggest you use the Form Designer to create your tool bar. That way, the IDE will maintain an identifier for you in your form, so you'll always have a direct reference to your panel. You won't have to go hunting for it in the tool bar. Even if you're creating the tool bar manually, it's a good idea to make an extra field to refer to the panel. Even if you move the panel around within the tool bar, it will still be the same object the whole time, so you needn't worry about dangling references.
When you put a couple of buttons and a panel on a toolbar, and a Memo somewhere, then run this code in the form's onCreate:
procedure TForm1.FormCreate(Sender: TObject);
function _IsToolButton(const aObject: TObject): Boolean;
begin
Result := aObject is TToolButton;
end;
function _IsPanel(const aObject: TObject): Boolean;
begin
Result := aObject is TPanel;
end;
var
i: Integer;
begin
for i := 0 to bar.ButtonCount - 1 do begin
Memo.Lines.Add(Format('bar.Buttons[%d].Name: %s', [i, bar.Buttons[i].Name]));
Memo.Lines.Add(Format('bar.Buttons[%d].ClassName: %s', [i, bar.Buttons[i].ClassName]));
Memo.Lines.Add(Format('bar.Buttons[%d] is TToolButton: %s', [i, BoolToStr(_IsToolButton(bar.Buttons[i]), True)]));
Memo.Lines.Add(Format('bar.Buttons[%d] is TPanel: %s', [i, BoolToStr(_IsPanel(bar.Buttons[i]), True)]));
// Memo.Lines.Add(Format('bar.Buttons[%d] has Caption property: %s', [i, 'dunno yet']));
Memo.Lines.Add('');
end;
end;
you'll see that the panel is not a TooButton and most definitely a TPanel.
The debugger showing properties of a ToolButton for the panel, is simply the debugger casting each and every bar.Buttons[i] to a TToolButton. When you right-click on the "Data" tab of the Debug inspector, you can Type Cast it to a TPanel and you will get the correct information.
'is it legitimate?' - well, you are definitely using the toolbar in a way that the creator of the toolbar did not ment it to be used. Will it blow up in your face? Who knows. I guess you could walk through the sourcecode for the toolbar and check if it is safe or not, but what about possible third party tools or components, expecting to find buttons in a toolbar?
I would see if I could find another way of solving my problem. Clever hacks have a tendency to turn out not so clever after all, and it will surely higten the wtf-rate of your code.
Do you have to use a toolbar? What about a flowpanel with buttons and panels instead? Or a panel with a toolbar and a panel?

How can a shared event handler know which control's event it's handling?

I want to write some code that assigns the same event handler to several different buttons. Is there a way to implement it without referring to each button by name but by using something generic like self or sender to refer to the button?
Yes. Every normal method call includes a hidden "Self" that refers to the object. But in an event handler, "Self" is the form, not the button. The button is Sender, and you'll have to typecast it using something like Sender as TButton.
You'll need to use sender.
(Sender as TButton).Enabled := False;
Would disable any button that has this event handler assigned to its onclick event. The cast can also be done
TButton(Sender).Enabled := False;
but in this case you need to be 100% that sender is a button. Using as introduces a check before the cast, so is slightly slower, but in this type of example is not really a problem I think.
You can do something like this:
procedure OnClickButton(Sender: TObject);
var btn: TButton;
begin
if Sender is TButton then btn := TButton(mycontrol)
else
exit;
//and then use btn as just another button control
end;
and to assign the same event to different controls you could do:
if mycontrol is TButton then
TButton(mycontrol).OnClick := OnClickButton;
Consider 'disconnecting' yourself from buttons and use actions. Plonk an action list on your form, right-click it and 'add' and action. Name it, caption it (as if it were a button, say) and then wire up its OnExecute event to your code. Finally, go to your button and click on the 'Action' property and nominate your new action. When you click on the button, your code is executed.
Why is this useful? Well:
1. You will not lose access to your code, which commonly happens when you delete the button and replace it with, say, a TPopupMenu.
2. One action can be launched with way from several places, multiple buttons or menus.
3. Better still, fill in the action's 'OnUpdate' event with something like:
procedure TForm1.MyActionOnUpdate( ASender : TObject );
begin
With Sender as TAction do
Enabled := ItsPossibleToRunMyCode;
end;
This bit of code will enable and disable any control that uses this action without you needing to do anything.

Resources