views:

622

answers:

3

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:

  1. the text in a TComboBox
  2. the text in a TFindDialog
  3. 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 at: https://forums.codegear.com/thread.jspa?threadID=19946

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.)

+1  A: 

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.

Cary Jensen
You are correct that this simple example worked. I'll have to either build the simple example up, or break my program down to see what is blocking me here. In between my question and your answer, I did find others with the same problem at https://forums.codegear.com/thread.jspa?threadID=19946 and I tried Peter Below's solution, but it didn't work for me. So that adds to your conclusion that something else is going on here.I shall research further.Thanks for answering. You've given me something to go on.
lkessler
Cary, see the update to my question. TMemo's and TEdits don't have the problem. Please try your test program with a TComboBox or a TFindDialog.
lkessler
Curious. I placed a TFindDialog and a TComboBox component on my main form as well as the non-modal form. The cut and paste worked with the TFindDialog on both forms. The cut and paste worked with the TComboBox on the main form, but not on the non-modal form.
Cary Jensen
What you've seen on the TComboBox is the problem I'm trying to solve. For me and my version of your simple test, the TFindDialog also does not properly do Ctrl-X, Ctrl-C, Ctrl-V when it is called from either the Main Form or the Non-Modal form, but does work from a Modal Form. So I don't know why that is not happening for you.
lkessler
After I started playing with it for the above comment, all of a sudden, it worked on the non-modal form. After some more playing around, it continued to work. I shut the program and rebuilt it. It still continued to work. This is now extremely strange to me. I have no idea what it could be? But Cary, you are at least a witness that something strange is happening.
lkessler
... but I go back to my program and the problem is definitely there.
lkessler
Strange! Could it be the order in which components are created that changed in your small example? Do you have problems with any other HotKey? I've seen a case where adding a shortcut in a menu somewheredid break a Ctrl-A in an a priori unrelated form. Also check the KeyPreview...
François
François: That's an interesting thought. I'll take a look at some other possible interactions of the cut, copy and paste shortcuts. But the simple example, the way Cary suggested to set up, doesn't leave much room for external influences. Primarily, it does require the ActionList and a non-modal form for it to happen.
lkessler
+3  A: 

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

-- Louis

mghie
mghie: Thank you for the detailed analysis. I've been working for an hour trying to verify what you say. I do what you say in the first 4 paragraphs up to your horizontal line. You are correct that for the ComboBox on the main form, the shortcuts do not work. However, if you place two buttons on the main form that open up a modal and non-modal form. And on each form place a ComboBox. Then when opening the Combo Box on the modal form, the shortcuts DO work, but on the non-modal they don't. So it still does seem to have something to do with modal/non-modal forms.
lkessler
Yes, I assume that due to modal dialogs disabling all other forms in the application their action components are not used for determining what key events will be mapped to commands. If the key events go to the native windows unmodified, then shortcuts still work. However, if you place the action components and the menu on the modal dialog then it doesn't work either. That's why I was saying that modality doesn't really matter, it's just that it affects which action lists are used to search for potential shortcuts. A side effect, if you will.
mghie
So now I put your EditPaste1Execute routine (as well as Copy1 and Cut1) handlers in. I'm impressed by your knowledge of this. Thanks. It would have taken me a long time to figure that out. But even with this, I still have a problem. Do this: From the main form, press a button to open a find dialog. Put in a find string, select part of it and do Ctrl-X. It now works! But continue on back to the main form. Press a button to open a non-modal form with a combo box on it. Do Ctrl-X from the combo box. It DOESN'T work. Reopen the program. Do the Combo box first (works) and then find (doesn't work).
lkessler
There must be something else still. I did all of these tests, and everything worked for me. This is with Delphi 2009, fully patched.
mghie
mghie: Tonight I'll put my simple test program up on my website (can't do it right now - I'm not at home), and maybe then this last problem will show up for you. Once it's up, I'll post another comment with the link to the test program.
lkessler
Okay, I've put up a zip file with my test Delphi 2009 project at: http://www.beholdgenealogy.com/cutcopypastetest.zip ... but as I test it now with the works/doesn't work example I gave above, the "doesn't work" is not happening. Everything works. I am either going completely nuts, or something is really screwy here. This is not very reassuring for me. We all want programs we can verify work all the time, and not some times. But please try it. Maybe it will happen for you. If not, I am at a loss for any explanation.
lkessler
mghie: Your fix did not completely work the first time I tried it (see: 4 comments ago), but now seems to work (see: 1 comment ago) and that is very strange. But unfortunately, when I put it into my program, it does not work at all.
lkessler
Well mghie, your code does work in my program and does seem to fix the problem. See my final edits of the question, above.
lkessler
+2  A: 

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; 
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.

lkessler
This is an interesting solution, which has the small problem that it corrects the shortcut problem *only*. If for example there's a toolbar with buttons for Cut, Copy and Paste they will be disabled if the focused edit window isn't a `TCustomEdit`, while the shortcuts for those edit actions will work, making the UI appear inconsistent. But it solves the problem in your question, so +1.
mghie