How to force click in cxDBButtonEdit? - delphi

I, have many cxDBButtonEdit in my form, and I want, when the user press ENTER make a click in the first button.
I try to find any methods, procedure, function.. and nothing!
I try to make like this:
if (ActiveControl is TcxDBButtonEdit) then
begin
if ((ActiveControl as TcxDBButtonEdit).Properties.Buttons.Count > 0) and ((ActiveControl as TcxDBButtonEdit).Properties.Buttons.Items[0].Enabled) then
begin
(ActiveControl as TcxDBButtonEdit) <----- HERE
try
GetParentForm(Screen.ActiveForm).Perform(CM_DIALOGKEY, VK_TAB, 0);
Key := #0;
except
end;
end;
end;
Thanks,

You're working much too hard. :-)
Your question asks about clicking the button, but your code indicates sending a tab (VK_TAB). I'm not sure which you're actually wanting to do, so I'll try to address both.
I don't know anything about the TcxDBButtonEdit (or any of the other DevExpress controls), but something like this should work for you (for the tab key):
if (ActiveControl is TcxDBButtonEdit) then
begin
// We know it's a TcxDBButtonEdit, so we can directly cast it
if (TcxDBButtonEdit(ActiveControl).Properties.Buttons.Count > 0) and
(TcxDBButtonEdit(ActiveControl.Properties.Buttons.Items[0].Enabled) then
begin
Key := #0;
Self.SelectNext(ActiveControl, True, True); // See notes below
end;
end;
If you're wanting to actually click the button attached to the edit control, DevExpress support has an article that says that you can do this to invoke the button's click handler (replace the call to SelectNext above):
if (ActiveControl is TcxDBButtonEdit) then
begin
// We know it's a TcxDBButtonEdit, so we can directly cast it
if (TcxDBButtonEdit(ActiveControl).Properties.Buttons.Count > 0) and
(TcxDBButtonEdit(ActiveControl.Properties.Buttons.Items[0].Enabled) then
begin
Key := #0;
TcxCustomEditAccess(ActiveControl).DoButtonClick(0);
end;
end;
(I clearly can't compile the above code, because I don't have the DevEx controls; the line calling DoButtonClick is from their article. If the compiler complains about it, you probably need to add the following to your code (right above the method containing the code that uses it is probably the best place):
type
TcxCustomEditAccess = class(TCxCustomEdit);
From the 'Access' part of the name, it looks like the DoButtonClick might be a protected method of the TcxCustomEdit. Declaring the interposer class allows you access to the protected method.)
Notes about SelectNext:
From the TWinControl.SelectNext documentation
Moves the input focus from the current child control to the next one in the tab order.
Call SelectNext to move the child control focus. SelectNext selects the first child that follows or precedes CurControl in the tab order and that meets the criteria specified in the other parameters.
The GoForward parameter controls the direction of the search. If GoForward is true, FindNextControl searches forward through the child controls in tab order. If GoForward is false, SelectNext searches backward through the controls. The search wraps past the end of the collection back to CurControl.
The CheckTabStop parameter controls whether the control SelectNext finds must be a tab stop. If CheckTabStop is true, the returned control must have its TabStop property set to true, or the search for the next control continues.
If a child control matches the search criteria, that control obtains the focus. If no such child control is found, the focus remains unchanged.

Related

Expanding the functionality of existing example

This is just for learning sake : Using accepted answer in this example Delphi application with login / logout - how to implement?,how can you show a specific form (if you have many forms) ?
I have placed a query on that LoginForm (which displays various forms that can be selected and shown).
How should I change the existing code so I can accomplish this maintaining the same functionality meaning that whatever form I select and click 'open' it will become the mainform and be shown?
The main form is the first form created using Application.CreateForm. So use an if or case statement to conditionally switch which form is created that way.
In pseudo-code:
if Form1 is main form then
Application.CreateForm(TForm1, Form1)
else if Form2 is main form then
Application.CreateForm(TForm2, Form2)
....
There are quite likely slicker ways to do this, but this is the basic principle.
So, you might well make a function to return the class of the main form, using whatever mechanism you have to choose it:
function GetMainFormClass: TFormClass;
begin
Result :=...; // your logic here
end;
Then in the .dpr file you write
Application.CreateForm(GetMainFormClass, MainForm);
You seem, to be really struggling with this. I'll try to spell it out in a little more detail. The implementation of GetMainFormClass might look like this:
function GetMainFormClass: TFormClass;
var
FormClassName: string;
begin
FormClassName := ...; // somehow get the name of the form class, e.g. 'TForm1'
if FormClassName = 'TForm1' then
Result := TForm1
else if FormClassName = 'TForm2' then
Result := TForm2
else
raise EUnrecognisedFormClassName.Create(...);
end;
Personally, I don't understand why you are presenting a grid control to the user. To me that seems like a very weak user interface with which to make a selection. I would present a radio group. Then you could write code like this:
type
TMainFormType = (mftForm1, mftForm2, mftForm3);
const
MainFormClass = array [TMainFormType] of TFormClass = (
TForm1,
TForm2,
TForm3
);
Then you can have a function that returns TMainFormType, by way of showing the user a form and presenting a radio group with three buttons. You can convert the radio group's ItemIndex into the appropriate TMainFormType value.
function GetMainFormType: TMainFormType;
begin
// ... your UI shows now, presenting the radio group
Result := TMainFormType(RadioGroup.ItemIndex);
end;
And then in the .dpr file you write
Application.CreateForm(MainFormClass[GetMainFormType], MainForm);
There are lots of different ways to do this, but you do need to understand the basics of branching code. Either if statements, or lookup tables. If you don't understand how these work then, with all due respect, you need to brush up those skills before you proceed.

Switching modal forms in Delphi

I have a modal form (A) that shows another modal form (B). B displays a dataset and allows the user to interact with it. My problem is that one action requires that A becomes again the focused form so the user can input certain values without closing B. I have tried A.BringToFront and A.SetFocus and it indeed is shown at front, but the input focus remains in B and any click or the like in A results in the windows "ding" when you click where you should not. The code is some how like
A.ShowModal;
.
.
. inside an event of A:
B.ShowModal();
.
.
. inside an event of B:
someobject.someMethodThatRequiresAFocused;
My guess is that some obscure and strange API call could make A modal again ¿Any ideas?
Regards
When a modal form is shown, all currently visible forms including other modal forms are disabled. As such, it is not possible to switch between multiple modal forms. You need to re-think your UI design so that B does not go back to A for new input. At the very least, you could have B open a new modal form C that prompts the user for just the needed values and gives them to B, and then either B or C can update A with the new values afterwards.
There's no API that toggles modality between windows. In any case the API you're looking for your case is EnableWindow. That's how modality works, windows other than the one that the user should be working with are disabled so that he/she cannot interact with them. This is also the reason for the 'ding' sound, to provide feedback to the user.
So while letting the user work with a window that's been disabled in favor of another modal window is technically easy, handling states may not be straight forward. I present a bare minimum example below for what it would seem to take.
'FormB' first. Lets suppose you pass a reference of 'FormA' in the 'Owner' parameter while 'FormA' is constructing 'FormB'. The below is what the code that should make 'FormA' modal again could look like:
procedure TFormB.BtnMakeFormAModalAgainClick(Sender: TObject);
begin
Enabled := False; // so that 'A' will behave like it's modal
EnableWindow(TFormA(Owner).Handle, True); // so that 'A' could be interacted
TFormA(Owner).SetFocus;
end;
When this code runs, what happens is 'FormA' is enabled and brought to front, and 'FormB' is disabled - will produce a 'ding' when clicked on.
But we're not done yet. Because we have modified the meaning of modality - now we don't want 'FormA' to be closed when the user is done with it. Below is how could the code in 'FormA's unit could look like:
type
TFormA = class(TForm)
BtnShowModalB: TButton;
BtnOk: TButton;
procedure BtnShowModalBClick(Sender: TObject);
procedure BtnOkClick(Sender: TObject);
private
FModalB: TForm;
end;
implementation
uses
unitOfFormB;
{$R *.dfm}
procedure TFormA.BtnShowModalBClick(Sender: TObject);
begin
FModalB := TFormB.Create(Self); // so that FormB can find FormA from the Owner
FModalB.ShowModal;
FModalB.Free;
FModalB := nil; // Need this if we're going to decide if FormB is showing
// by testing against this reference
end;
procedure TFormA.BtnOkClick(Sender: TObject);
begin
if Assigned(FModalB) then begin // is FormB the actual modal form?
EnableWindow(Handle, False); // disable this form so it would 'ding'
FModalB.Enabled := True; // enable FormB, so user can interact with it
FModalB.SetFocus;
ModalResult := mrNone; // don't close, FormB is the first one to be closed
end else
ModalResult := mrOk;
end;
I'm nearly positive that this example is not complete, but here's the API that you're looking for.

Delphi: How to make ENTER key works as TAB key in TFrame

I have a Frame and some controls on them (edits, buttons, etc.). How to intercept pressing of ENTER key anywhere on a frame control and translate in to TAB key (taking into account SHIFT status)?
All you need is to modify the handling of CM_DIALOGKEY.
type
TMyForm = class(TForm)
protected
procedure CMDialogKey(var Message: TCMDialogKey); message CM_DIALOGKEY;
end;
procedure TMyForm.CMDialogKey(var Message: TCMDialogKey);
begin
if Message.CharCode=VK_RETURN then
Message.CharCode := VK_TAB;
inherited;
end;
Well, it's pretty obvious what this does and how it works.
You ask how to do this in a frame. It's not possible to handle dialog navigation in a frame. That's done by the form for fairly obvious reasons. So you'll need somehow to splice this code into the form that hosts your frame.
For a memo control this will have no effect. They will treat pressing ENTER as input of a line break. But I presume that's what you would wish to happen. Otherwise the memo control would be completely unusable.
Here's some example code that would handle a message on the frame to be able to navigate to the next control when Enter is pressed. Note that this sample does not modify the Enter key to become a Tab key. Instead it selects the next control and prevents further processing of the key down message.
Also note that the code may require further tweaking. One for, if any of the controls actually need to process the Enter key, for instance a TMemo, you need to add an exception. Second for, the navigation is wrapped in the frame, i.e. after the last frame control the first frame control is focused - not a control on the form and not on the frame. For these, you might want to add conditions for the message return, if you want default processing on some condition simply call inherited without doing any other thing.
type
TFrame2 = class(TFrame)
...
protected
procedure CMChildKey(var Message: TCMChildKey); message CM_CHILDKEY;
end;
..
procedure TFrame2.CMChildKey(var Message: TCMChildKey);
begin
if Message.CharCode = VK_RETURN then begin
SelectNext(Screen.ActiveControl, not Bool(GetKeyState(VK_SHIFT) and $80), True);
Message.Result := 1;
end else
inherited;
end;
I think there is a lot of "drop and forget" components to do it, for example on http://Torry.net
For instance, such a component was part of RxLib and later was inherted in JediVCL with TJvEnterAsTab name.

How to stop user highlighting and deleting text in an edit control?

I have an Edit Control on a DevExpress Ribbon of type TcxBarEditItem which I am recording the keypresses of to update a "floating" listbox of possible functionality to fire.
For some reason, the TcxBarEditItem and it's parent classes' event handler's do not work at all like Delphi's vanilla equivalents, meaning I have to record these keypresses.
My question however, is how to record/or prohibit, the user doing things like pasting in loads of text, or highlighting and deleting loads of text?
The way in which these controls seem to work means using String(TcxBarEditItem(control).EditValue) (which is how I would access the control as it is a member of a Command class - TS8RibbonCommand) isn't actually indicative of the text in the edit control until the user clicks out of it. I've tried doing loads of things like programmatically setting focus elsewhere and refocusing but nothing else seems to work bar recording the keypresses.
In the code snippet mirroredJumpStart is my copy of what the user is typing. The RefreshJumpStart function takes a string value and iterates over all the different string values in a list and populates a Listbox using AnsiContainsString.
procedure TS8RibbonJumpStartEdit.KeyPress(Sender: TObject; var Key: Char);
begin
if (Key in ['a'..'z']) or (Key in ['A'..'Z']) or (Key in ['0'..'9']) or (Key = ' ') then
manager.mirroredJumpStart := manager.mirroredJumpStart + Key
else if (Key = Chr(VK_BACK)) and (Length(manager.mirroredJumpStart) <> 0) then
Delete(manager.mirroredJumpStart, Length(manager.mirroredJumpStart), 1);
manager.RefreshJumpStart(manager.mirroredJumpStart);
end;
Any help would be great!
Assuming this is a standard TEdit control...
You can limit the amount of text by using MaxLength property
You can catch individual keystrokes by observing Key parameter in the event OnKeyDown
Just a couple little tips, not sure if it will help, because you don't say you're using the TEdit

How can I Prevent Shortcuts from Colliding/Interacting in Delphi?

I use the standard Cut, Copy, Paste actions on my Main Menu. They have the shortcuts Ctrl-X, Ctrl-C and Ctrl-V.
When I open a modal form, e.g. FindFilesForm.ShowModal, then all the shortcuts work from the form.
But when I open a non-modal form, e.g. FindFilesForm.Show, then the shortcuts do not work.
I would think that those actions should work if the FindFilesForm is the active form. It's modality should have nothing to do with it, or am I wrong in my thinking?
Never-the-less, how can I get the shortcuts to work on a non-modal form?
After Cary's response, I researched it further. It is not a problem with certain controls, e.g. TMemo or TEdit.
But it is for some others. Specifically, the ones where it happens include:
the text in a TComboBox
the text in a TFindDialog
a TElTreeInplaceEdit control, part of LMD's ElPack
I'll see if there are others and add them to the list.
These are all on important Non-Modal forms in my program.
So I still need a solution.
Okay. I really need help with this. So this becomes the first question I am putting a bounty on.
My discussion with Cary that takes place through his answer and the comments there describe my problem in more detail.
And as I mentioned in one of those comments, a related problem seems to be discussed here.
What I need is a solution or a workaround, that will allow the Ctrl-X, Ctrl-C and Ctrl-V to always work in a TComboBox and TFindDialog in a Non-Modal window. If those two get solved, I'm sure my TElTreeInplaceEdit will work as well.
It takes only a couple of minutes to set up an simple test program as Cary describes. Hopefully someone will be able to solve this.
Just be wary that there seems to be something that allows it to work sometimes but not work other times. If I can isolate that in more detail, I'll report it here.
Thanks for any help you can offer me.
Mghie worked very hard to find a solution, and his OnExecute handler combined with his ActionListUpdate handler do the trick. So for his effort, I'm giving him the accepted solution and the bounty points.
But his actionlist update handler is not simple and you need to specify in it all the cases you want to handle. Let's say there's also Ctrl+A for select all or Ctrl-Y for undo you might want. A general procedure would be better.
So if you do come across this question in your search for the answer, try first the answer I supplied that adds an IsShortcut handler. It worked for me and should handle every case and does not need the OnExecute handlers, so is much simpler. Peter Below wrote that code and Uwe Molzhan gets finders fee.
Thanks Cary, mghie, Uwe and Peter for helping me solve this. Couldn't have done it without you. (Maybe I could have, but it might have taken me 6 months.)
OK, first thing first: This has nothing to do with modal or non-modal forms, it is a limitation of the way the Delphi action components work (if you want to call it that).
Let me prove this by a simple example: Create a new application with a new form, drop a TMemo and a TComboBox onto it, and run the application. Both controls will have the system-provided context menu with the edit commands, and will correctly react on them. They will do the same for the menu shortcuts, with the exception of Ctrl + A which isn't supported for the combo box.
Now add a TActionList component with the three standard actions for Cut, Copy and Paste. Things will still work, no changes in behaviour.
Now add a main menu, and add the Edit Menu from the template. Delete all commands but those for Cut, Copy and Paste. Set the corresponding action components for the menu items, and run the application. Observe how the combo box still has the context menu and the commands there still work, but that the shortcuts do no longer work.
The problem is that the standard edit actions have been designed to work with TCustomEdit controls only. Have a look at the TEditAction.HandlesTarget() method in StdActns.pas. Since edit controls in combo boxes, inplace editors in tree controls or edit controls in native dialogs are not caught by this they will not be handled. The menu commands will always be disabled when one of those controls has the focus. As for the shortcuts working only some of the time - this depends on whether the VCL does at some point map the shortcuts to action commands or not. If it doesn't, then they will finally reach the native window procedure and initiate the edit command. In this case the shortcuts will still work. I assume that for modal dialogs the action handling is suspended, so the behaviour is different between modal and non-modal dialogs.
To work around this you can provide handlers for OnExecute of these standard actions. For example for the Paste command:
procedure TMainForm.EditPaste1Execute(Sender: TObject);
var
FocusWnd: HWND;
begin
FocusWnd := GetFocus;
if IsWindow(FocusWnd) then
SendMessage(FocusWnd, WM_PASTE, 0, 0);
end;
and similar handlers for the Cut command (WM_CUT) and the Copy command (WM_COPY). Doing this in the little demo app makes things work again for the combo box. You should try in your application, but I assume this will help. It's a harder task to correctly enable and disable the main menu commands for all native edit controls. Maybe you could send the EM_GETSEL message to check whether the focused edit control has a selection.
Edit:
More info why the behaviour is different between combo boxes on modal vs. non-modal dialogs (analysis done on Delphi 2009): The interesting code is in TWinControl.IsMenuKey() - it tries to find an action component in one of the action lists of the parent form of the focused control which handles the shortcut. If that fails it sends a CM_APPKEYDOWN message, which ultimately leads to the same check being performed with the action lists of the application's main form. But here's the thing: This will be done only if the window handle of the application's main form is enabled (see TApplication.IsShortCut() code). Now calling ShowModal() on a form will disable all other forms, so unless the modal dialog contains itself an action with the same shortcut the native shortcut handling will work.
Edit:
I could reproduce the problem - the key is to somehow get the edit actions become disabled. In retrospect this is obvious, the Enabled property of the actions needs of course to be updated too.
Please try with this additional event handler:
procedure TForm1.ActionList1Update(Action: TBasicAction; var Handled: Boolean);
var
IsEditCtrl, HasSelection, IsReadOnly: boolean;
FocusCtrl: TWinControl;
FocusWnd: HWND;
WndClassName: string;
SelStart, SelEnd: integer;
MsgRes: LRESULT;
begin
if (Action = EditCut1) or (Action = EditCopy1) or (Action = EditPaste1) then
begin
IsEditCtrl := False;
HasSelection := False;
IsReadOnly := False;
FocusCtrl := Screen.ActiveControl;
if (FocusCtrl <> nil) and (FocusCtrl is TCustomEdit) then begin
IsEditCtrl := True;
HasSelection := TCustomEdit(FocusCtrl).SelLength > 0;
IsReadOnly := TCustomEdit(FocusCtrl).ReadOnly;
end else begin
FocusWnd := GetFocus;
if IsWindow(FocusWnd) then begin
SetLength(WndClassName, 64);
GetClassName(FocusWnd, PChar(WndClassName), 64);
WndClassName := PChar(WndClassName);
if AnsiCompareText(WndClassName, 'EDIT') = 0 then begin
IsEditCtrl := True;
SelStart := 0;
SelEnd := 0;
MsgRes := SendMessage(FocusWnd, EM_GETSEL, WPARAM(#SelStart),
LPARAM(#SelEnd));
HasSelection := (MsgRes <> 0) and (SelEnd > SelStart);
end;
end;
end;
EditCut1.Enabled := IsEditCtrl and HasSelection and not IsReadOnly;
EditCopy1.Enabled := IsEditCtrl and HasSelection;
// don't hit the clipboard three times
if Action = EditPaste1 then begin
EditPaste1.Enabled := IsEditCtrl and not IsReadOnly
and Clipboard.HasFormat(CF_TEXT);
end;
Handled := TRUE;
end;
end;
I didn't check for the native edit control being read-only, this could probably be done by adding this:
IsReadOnly := GetWindowLong(FocusWnd, GWL_STYLE) and ES_READONLY <> 0;
Note: I've given mghie the answer as he did a lot of work and his answer is correct, but I have implemented a simpler solution that I added as an answer myself
I posted a link to this question on my blog, and got a suggestion from Uwe Molzhan who is not on StackOverflow. Uwe used to run DelphiPool. He pointed me to this thread at borland.public.delphi.objectpascal:
Action List (mis)behavior.
Tom Alexander who asked the original question in this thread even said:
This behavior occurs usually, but not
all the time. Sometimes after a series
of the above errors, the behavior
starts acting as I would expect.
which is exactly the strange behaviour I've been having that has made this problem near to impossible to track down.
Peter Below responded in that thread that if there are colliding shortcuts, you have to take steps to make sure the active control gets first crack at the shortcut.
Taking his code (which was written for a frames problem) and I just had to modify “ctrl is TCustomFrame” to “ctrl is TControl” and it works perfect. So here is what was needed:
public
Function IsShortcut( var Message: TWMKey): Boolean; override;
Function TMyform.IsShortcut( var Message: TWMKey): Boolean;
Var
ctrl: TWinControl;
comp: TComponent;
i: Integer;
Begin
ctrl := ActiveControl;
If ctrl <> Nil Then Begin
Repeat
ctrl := ctrl.Parent
Until (ctrl = nil) or (ctrl Is TControl);
If ctrl <> nil Then Begin
For i:= 0 To ctrl.componentcount-1 Do Begin
comp:= ctrl.Components[i];
If comp Is TCustomActionList Then Begin
result := TCustomActionList(comp).IsShortcut( message );
If result Then
Exit;
End;
End;
End;
End;
// inherited; { Originally I had this, but it caused multiple executions }
End;
So far this seems to work in all cases for me.
The ironic thing is that it didn't work for Tom Alexander, the original question asker. What he did instead was add a procedure to the FrameEnter event that set the focus to the appropriate grid for the frame. That might imply yet another alternative solution to my question, but I have no need to explore that since Peter's solution works for me.
Also note that Peter includes in his answer an excellent summary of the complex steps of key handling that is worth knowing.
But I do want to now check mghie's edit on his answer and see if that is also a solution.
I created a very simple example with two forms in Delphi 2009 (Update 3 and Update 4 installed) running on Vista 64-bit. The second form, Form2 is displayed non-modally (Form2.Show;). I have a TMemo on Form2. Ctrl-X, Ctrl-V, and Ctrl-C work just fine.
This was before I placed a TMainMenu on Form2.
So, I placed a TMainMenu on the form, and added a TActionList. I create an Edit menu items, and added Copy, Cut, Paste submenu items. I hooked these up to the standard actions EditCopy, EditCut, and EditPaste. Still, everything works fine as before. I can either use the menu items, or the Ctrl-C, Ctrl-X, and Ctrl-V key combinations.
There must be something else going on here.

Resources