Community
    • Login

    C# Windows Forms tab navigation broken when form registered with NPPM_MODELESSDIALOG

    Scheduled Pinned Locked Moved Notepad++ & Plugin Development
    pluginbugtab
    7 Posts 3 Posters 863 Views
    Loading More Posts
    • Oldest to Newest
    • Newest to Oldest
    • Most Votes
    Reply
    • Reply as topic
    Log in to reply
    This topic has been deleted. Only users with topic management privileges can see it.
    • Mark OlsonM
      Mark Olson
      last edited by Mark Olson

      Since the changes in Npp 8.6.1 that changed Ctrl+X and Ctrl+C into menu commands, I’ve been trying to figure out a decent solution that makes Ctrl+C and Ctrl+X work again in my C# plugins.

      The “correct” solution to this problem is to register and unregister each of my forms with NPPM_MODELESSDIALOG, and I understand how to do this. Doing so does indeed restore the functionality of Ctrl+C and Ctrl+X in Npp 8.6.1 and 8.6.2, but it also causes tab navigation to break, which is …unexpected.

      To be brief, in C# Windows Forms, controls have a TabIndex property that controls their position in the tab order. It is my understanding that things work differently in C++ (like I think you can choose either a top-to-bottom-then-left-to-right or left-to-right-then-top-to-bottom algorithm?), and I would be willing to use NPPM_MODELESSDIALOG if the new order of tab navigation made any sense at all, but it really doesn’t. I will show a diagram of the before and after for one of my forms (it’s a representative example; all my forms get messed up like this).

      This is the order of the controls by tab index (I chose a left-to-right-then-top-to-bottom order, more or less)

      3af4a543-0ee5-408e-ac87-1bb0c1af6b7c-image.png

      And this is the order that I get when I register my form with NPPM_MODELESSDIALOG:
      876893bf-bc3c-4226-ab14-e1dd776541d2-image.png

      Do you see a pattern in the second one? Because I sure don’t. And before you ask, the weird order you see there is not the order in which the controls are declared or initialized, as far as I can tell.

      My current thinking (very vague and speculative and most likely wrong somehow, because I’m not really experienced with C++) is that in winmain.cpp in the message loop there’s a call to TranslateMessage and DispatchMessageW after an earlier call (in Notepad_plus_Window::isDlgsMsg) to IsDialogMessageW, but the standard documentation for IsDialogMessageW specifically says that:

      Because the IsDialogMessage function performs all necessary translating
      and dispatching of messages, a message processed by IsDialogMessage
      must not be passed to the TranslateMessage or DispatchMessage function.
      
      CoisesC rdipardoR 3 Replies Last reply Reply Quote 1
      • CoisesC
        Coises @Mark Olson
        last edited by Coises

        @Mark-Olson said in C# Windows Forms tab navigation broken when form registered with NPPM_MODELESSDIALOG:

        To be brief, in C# Windows Forms, controls have a TabIndex property that controls their position in the tab order. It is my understanding that things work differently in C++

        Not really. There is a tab order; when building a dialog using the resource editor in Visual Studio, you can set the order to be any sequence you like. What that does is control the order in which the controls are defined in the resource file (which is compiled to produce, among other things, dialog templates), which determines the tab order.

        My current thinking (very vague and speculative and most likely wrong somehow, because I’m not really experienced with C++) is that in winmain.cpp in the message loop there’s a call to TranslateMessage and DispatchMessageW after an earlier call (in Notepad_plus_Window::isDlgsMsg) to IsDialogMessageW, but the standard documentation for IsDialogMessageW specifically says that:

        Because the IsDialogMessage function performs all necessary translating
        and dispatching of messages, a message processed by IsDialogMessage
        must not be passed to the TranslateMessage or DispatchMessage function.
        

        The further processing only happens when IsDlgsMsg returns false; when IsDialogMessageW returns true, so does IsDlgsMsg.

        It’s not clear to me what could cause this sort of behavior, but I don’t know C# and don’t how it integrates with “plain old Windows.” I noticed that your modal dialog “JSON to CSV” has some odd tabbing behavior — it appears to tab once on key down or repeat and again on key up — and as a modal dialog, it is unaffected by the Notepad++ message loop. Just a long shot: perhaps whatever is causing that odd behavior is somehow related to the unexpected behavior in non-modal dialogs.

        1 Reply Last reply Reply Quote 2
        • CoisesC
          Coises @Mark Olson
          last edited by

          @Mark-Olson said in C# Windows Forms tab navigation broken when form registered with NPPM_MODELESSDIALOG:

          Do you see a pattern in the second one?

          Looks like the order here:

          https://github.com/molsonkiko/JsonToolsNppPlugin/blob/929188b0b9cfd9231ea6a2323921ba026a371786/JsonToolsNppPlugin/Forms/FindReplaceForm.Designer.cs#L284
          and here:
          https://github.com/molsonkiko/JsonToolsNppPlugin/blob/929188b0b9cfd9231ea6a2323921ba026a371786/JsonToolsNppPlugin/Forms/FindReplaceForm.Designer.cs#L219

          considering that you start with focus on FindTextBox, loop, and “call into” the second set of adds when you hit AdvancedGroupBox.

          1 Reply Last reply Reply Quote 1
          • rdipardoR
            rdipardo @Mark Olson
            last edited by rdipardo

            Had a quick look and the issue seems to be the KeyUp handler, specifically the GenericTabNavigationHandler method which it calls in response to a TAB.

            When you register the form by sending NPPM_MODELESSDIALOG, the return value of form.GetNextControl() is a reference to the form itself, and the “next” control to get focus is the “Replace all” button.

            When it’s not registered, form.GetNextControl() returns a reference to the form’s next child component, currently the “Swap” button, and so on, as illustrated in the first screen capture above.

            You could maybe try iterating the form’s Controls collection instead of depending on tab order, as suggested by the docs.

            As for why NPPM_MODELESSDIALOG appears to change the component hierarchy, it could be another case of the WS_EX_CONTROLPARENT flag needing to be set on the parent form’s window attributes to make the OS aware that it is, in fact, a parent control.

            CoisesC 1 Reply Last reply Reply Quote 1
            • rdipardoR
              rdipardo
              last edited by

              After a closer look there seems to be more going on here.

              Without NPPM_MODELESSDIALOG, every control on the form receives its own key events. When a TextBox has focus, the sender object passed to GenericTabNavigationHandler is that TextBox, and the next tab stop is relative to that TextBox, and so on for every focusable control.

              The normal sequence of a key event should be WM_KEYDOWN (handled by .NET’s KeyDown) -> WM_CHAR (i.e. KeyPress) -> WM_KEYUP (i.e. KeyUP), but that assumes the receiver is some kind of edit control. An instance of Form doesn’t know about a key event until a child component broadcasts its own WM_KEYUP message to its parent; I don’t know how or if a parent can stop a child from processing it.

              After sending NPPM_MODELESSDIALOG, only the main form instance receives messages, and it’s only receptive to WM_KEYUP, so while the .NET KeyUp handler still works, all the others are dormant. Since the only events are coming from the main form, the next tab stop is always relative to it. There’s a loop in GenericTabNavigationHandler that will find a valid component in any case, but what it lands on will have nothing in common with the design-time tab order. You can even block the KeyUp handler’s code path, and focus will still jump around, suggesting some controls are silently processing tab keys.

              To see for yourself, first use the System.Diagnostics namespace and set up debug logging inside a static constructor, e.g.,

              static FindReplaceForm()
              {
                  Debug.Listeners.Add(new TextWriterTraceListener(System.Console.Out));
                  Debug.AutoFlush = true;
              }
              

              Then override ProcessKeyPreview, like this:

              protected override bool ProcessKeyPreview(ref Message msg)
              {
                var keyCode = (Keys)(byte)msg.WParam;
                switch (msg.Msg)
                {
                  case /*WM_KEYDOWN*/ 0x100:
                    Debug.WriteLine("WM_KEYDOWN");
                    break;
                  case /*WM_KEYUP*/ 0x101:
                    Debug.WriteLine("WM_KEYUP");
                    if (keyCode == Keys.Tab)
                    {
                      Debug.WriteLine("Handling TAB . . .");
                      // 1. send NPPM_MODELESSDIALOG, and:
                      //     a. WM_KEYUP is the only message ever received
                      //     b. TAB still moves focus around the form despite returning `true` here
                      // 2. don't send NPPM_MODELESSDIALOG, and:
                      //     a. we receive all of WM_KEYDOWN, WM_CHAR (handled by KeyPress), and WM_KEYUP, in that order
                      //     b. all key processing ends here if we return `true`
                      return true;
                    }
                    break;
                  case /*WM_CHAR*/ 0x102:
                    Debug.WriteLine("WM_CHAR");
                    break;
                  case /*WM_SYSKEYDOWN*/ 0x104:
                    Debug.WriteLine("WM_SYSKEYDOWN");
                    break;
                  case /*WM_SYSKEYUP*/ 0x105:
                    Debug.WriteLine("WM_SYSKEYUP");
                    break;
                  default:
                    break;
                }
                return base.ProcessKeyPreview(ref msg);
              }
              
              1 Reply Last reply Reply Quote 2
              • CoisesC
                Coises @rdipardo
                last edited by

                @rdipardo said in C# Windows Forms tab navigation broken when form registered with NPPM_MODELESSDIALOG:

                As for why NPPM_MODELESSDIALOG appears to change the component hierarchy, it could be another case of the WS_EX_CONTROLPARENT flag needing to be set on the parent form’s window attributes to make the OS aware that it is, in fact, a parent control.

                Based on @Mark-Olson’s second image, nothing is being changed; it’s exactly the order in which components are added here and here.

                I don’t even know how to set up a C# project, but by analogy to what I would say for a C++ project: has anyone tried making the order in which controls are added in FindReplaceForm.Designer.cs reflect the intended tab order, then letting the default dialog procedure (or whatever is the C# equivalent of that) handle navigation and not interfering by using keyboard event handlers? (Perhaps the keyboard event handlers were added in the first place because navigation in a non-modal dialog doesn’t work normally unless the message loop calls IsDialogMessage, which for a Notepad++ plugin requires using NPPM_MODELESSDIALOG.)

                1 Reply Last reply Reply Quote 0
                • Mark OlsonM
                  Mark Olson
                  last edited by

                  @Coises
                  @rdipardo

                  Thank you both for your suggestions! I meant to respond sooner, but there was a power outage.

                  In any case, Coises’ original suggestion worked! Thank you so much! I already knew that I had to disable GenericTabNavigationHandler, so all I did was go into the Designer.cs files for all of my forms and rearrange the order in which they were added to their parent and it just worked.

                  Hand-editing Designer.cs files is kind of a no-no, because they’re automatically generated by Visual Studio when you edit a form in the designer, but I’ll happily take this solution over whatever lousy alternative I was going to use.

                  1 Reply Last reply Reply Quote 1
                  • First post
                    Last post
                  The Community of users of the Notepad++ text editor.
                  Powered by NodeBB | Contributors