Generalized fix for Ctrl+C/X woes in C# plugins (beginning in Notepad++ 8.6.1)
-
TLDR: If you have a Notepad++ plugin based on kbilsted’s NotepadPlusPlusPluginPack.Net C# plugin template or my NppCSharpPluginPack template, you should look at my explanation of how to register your forms with
NPPM_MODELESSDIALOG
and deal with related issues.Shoutout to @Coises for being the first person to correctly identify the source of this trouble, and also the first person to recognize how to fix the problems with tab ordering.
Regulars here may remember the drama and confusion surrounding
Ctrl+C
andCtrl+X
in plugin textboxes that started in Notepad++ 8.6.1.If you’re not familiar, the issue is basically as follows:
- Notepad++ has always pre-processed keyboard shortcuts executed by all forms not registered with
NPPM_MODELESSDIALOG
. - Prior to Notepad++ 8.6.1, Notepad++ just executed the standard Windows cut/copy operations when Ctrl+X/C were pressed.
- This mean that cut/paste previously worked normally even in forms that weren’t registered with
NPPM_MODELESSDIALOG
, because Notepad++ didn’t do any preprocessing of Ctrl+X/C that would cause problems. - In Notepad++ 8.6.1, Ctrl+X and Ctrl+C were bound to menu commands, and no longer did the normal Windows cut/copy commands.
- Because of this change in Npp 8.6.1, Ctrl+C/X were broken in all forms that were neither (a) pop-up dialogs NOR (b) registered with
NPPM_MODELESSDIALOG
.
After many hours of thrashing about in confusion, complaining bitterly to anyone who would listen, and trying one half-baked solution after another, I have finally implemented a general-purpose solution to this problem, which should properly handle all the weird corner cases your plugin might have.
Rather than describe the solution in full here, I encourage you to read my explanation over at NppCSharpPluginPack.
- Notepad++ has always pre-processed keyboard shortcuts executed by all forms not registered with
-
-
I’m going to list all the issues that I’ve observed so far, along with the ones mentioned above:
KeyUp
andKeyPress
event handlers no longer work consistently (I assume they only work for keys that are not considered “special” (i.e., keys other thanEnter
,Tab
,Ctrl
, etc.))- Hitting the
Enter
key no longer creates a newline in multiline textboxes (presumably because theKeyUp
message has been consumed by the preprocessing here?)- I tried fixing this issue by setting
AcceptsReturn
toTrue
for the multiline textboxes, but that did nothing. - the fix that actually works is shown here.
- I tried fixing this issue by setting
- Hitting the
Tab
key does not traverse the form’s controls inTabIndex
order, but instead follows the order in which the controls became visible/enabled (I think).- Note that this requires changes to
Designer.cs
(shown in NppCSharpPluginPack, but it also requires care when changing the visibility of previously invisible controls (see this code in JsonTools for an example)
- Note that this requires changes to
To be clear, I am not mentioning these issues to shame Don Ho, or to suggest that
NPPM_MODELESSDIALOG
is net negative for Notepad++. Don Ho obviously has a hard enough job as it is without having to worry about weird interactions between legacy C++ APIs and Windows Forms. -
@Mark-Olson said in Generalized fix for Ctrl+C/X woes in C# plugins (beginning in Notepad++ 8.6.1):
all the issues
I find this very troubling. I’m not familiar with C#, but just on general programming principles, when fixing one problem creates half a dozen new problems — problems that require arcane and seemingly arbitrary fixes — the first problem wasn’t fixed properly.
As far as I can tell, in a full C#/WinForms program, the message loop isn’t exposed as such — it’s part of Application.Run. I note that Application has an OpenForms property, which implies that it “knows about” all open forms. I’m guessing it implements its own equivalent of IsDialogMessage for modeless forms, but from what we’re seeing, it must differ in some ways from IsDialogMessage.
What I have not been able to find anywhere is a description of what C#/WinForms expects from the message loop when that loop is implemented using Win32 API.
If someone can find an authoritative description of what should happen in the message loop to support C#/WinForms properly, we could make a strong case for extending NPPM_MODELESSDIALOG with an additional option (e.g., MODELESSDIALOGADDFORM) that would instruct Notepad++ to use message processing appropriate for a C#/WinForms modeless form instead of that for a Win32 API modeless dialog. But first we need to know (not guess) what that processing is, and be able to point to appropriate documentation.
-
@Mark-Olson said in Generalized fix for Ctrl+C/X woes in C# plugins (beginning in Notepad++ 8.6.1):
all the issues
This is a long shot, but does setting Form.KeyPreview true on the form make any difference? I’m thinking that IsDialogMessage sends “special” keys to the dialog procedure first, and maybe if that property has the default value (false), the processing code for the form isn’t “expecting” them to come to it first.
-
@Coises said in Generalized fix for Ctrl+C/X woes in C# plugins (beginning in Notepad++ 8.6.1):
This is a long shot, but does setting Form.KeyPreview true on the form make any difference?
Nope, doesn’t help at all. Actually actively makes matters worse, by making my
KeyUp
handlers stop working.My current hack to solve the Enter-in-multiline-textbox problem isn’t perfect, but it’s good enough to not cause me serious annoyance, and it only took me about 20 minutes to figure out. Please don’t worry too much about it.
If someone can find an authoritative description of what should happen in the message loop to support C#/WinForms properly, we could make a strong case for extending NPPM_MODELESSDIALOG with an additional option
Speaking as one of the people who is suffering the most from the current state of affairs, I think making such a change would not be worth the effort, and in any case unlikely to be approved by Don Ho. As I see it, the issue seems to be that such a change would essentially set the precedent that Don Ho and the other core NPP devs are held accountable for any weird interactions between C++ APIs (which he is at least capable of understanding) and whatever random GUI framework (Tkinter? Windows Forms? WPF? whatever HTML stuff people make with Jn?) plugin makers want to use.
While I started off really pissed off at Don Ho for causing these issues for me, I think the fact that Notepad++ can be pretty easily extended in just about any language is awesome, and I don’t want to penalize Don Ho for making Notepad++ so extensible.
-
@Mark-Olson said in Generalized fix for Ctrl+C/X woes in C# plugins (beginning in Notepad++ 8.6.1):
I think the fact that Notepad++ can be pretty easily extended in just about any language is awesome, and I don’t want to penalize Don Ho for making Notepad++ so extensible.
You are thanking the wrong person, I think. Don Ho obviously took for granted that every plugin would be developed on the same toolchain as the host application. And C++ developers have mostly vindicated that assumption by using Microsoft’s Visual C++ compiler almost exclusively. Plugins built with GCC are even harder to find than non-C++ ones.
The developers of the first .NET plugin template are the ones who bridged the gap between .NET Framework and the Win32 interface, just as Damjan Cvetko wrote the bindings that virtually every Object Pascal plugin still uses today. All of it has been a third-party effort — ad majorem gloriam de Notepad++ — no thanks to Don, even though his user base has benefited immensely from it.
In the long run, keeping pace with upstream developments is not sustainable for any third party, unless you’re developing plugins professionally or with plenty of sponsorship. It’s even less sustainable when you have to contend with bizarre and subtle incompatibilities that probably can’t be fixed without rewriting the component library, or at least sub-classing every off-the-shelf component [^1]. And even then you still have all the technical debt of an originally 32-bit template that can’t marshal a notification structure without writing out of bounds.
My advice to anybody considering a new .NET plugin would be to learn C++. I would say the same if they were thinking about an Object Pascal plugin, but of course that will never happen. Pascal is the most clumsily explicit language you will ever see. Coding in it is like building with LEGO: the structure may be coherent, but the pieces never blend together; it’s all rectilinear edges and rough little knobs. To Pascal’s credit, it’s only a short remove from C, so the compiler, debugger and component library can directly interface with system APIs. But C++ already does that, with fewer abstractions, and without having to statically link an entire runtime.
[^1] A typical stack trace, captured by Visual Studio, revealing that every class derived from
Control
implements its ownWndProc
method(!):> JsonTools.dll!JSON_Tools.Forms.FindReplaceForm.FindReplaceForm_KeyUp(object sender, System.Windows.Forms.KeyEventArgs e) Line 74 C# System.Windows.Forms.dll!System.Windows.Forms.Control.OnKeyUp(System.Windows.Forms.KeyEventArgs e) Unknown System.Windows.Forms.dll!System.Windows.Forms.TextBox.OnKeyUp(System.Windows.Forms.KeyEventArgs e) Unknown System.Windows.Forms.dll!System.Windows.Forms.Control.ProcessKeyEventArgs(ref System.Windows.Forms.Message m) Unknown System.Windows.Forms.dll!System.Windows.Forms.Control.WmKeyChar(ref System.Windows.Forms.Message m) Unknown System.Windows.Forms.dll!System.Windows.Forms.Control.WndProc(ref System.Windows.Forms.Message m) Unknown System.Windows.Forms.dll!System.Windows.Forms.TextBoxBase.WndProc(ref System.Windows.Forms.Message m) Unknown System.Windows.Forms.dll!System.Windows.Forms.TextBox.WndProc(ref System.Windows.Forms.Message m) Unknown System.Windows.Forms.dll!System.Windows.Forms.NativeWindow.DebuggableCallback(System.IntPtr hWnd, int msg, System.IntPtr wparam, System.IntPtr lparam) Unknown
-
@rdipardo said in Generalized fix for Ctrl+C/X woes in C# plugins (beginning in Notepad++ 8.6.1):
Don Ho obviously took for granted that every plugin would be developed on the same toolchain as the host application. And C++ developers have mostly vindicated that assumption by using Microsoft’s Visual C++ compiler almost exclusively. Plugins built with GCC are even harder to find than non-C++ ones.
I don’t draw the same conclusion. It’s possible that I’ve missed something, but as far as I can tell, the plugin interface is strictly C (not C++) and Win32 API. That is as close to generic as you can get under Windows.
Were the interface C++, a GCC plugin probably wouldn’t work. The C ABI is compatible across compilers (and languages); C++ ABIs are not. (They’re not even guaranteed to be compatible between different versions of the same compiler, though generally compiler vendors try really hard not to break things in that way.)
When writing in C++ for Windows only and using the Win32 API, Visual Studio with MSVC is simply the path of least resistance. Unless you have another tool chain already set up, or additional requirements, there is little reason to use GCC, so I’m not surprised it isn’t frequently done. There’s no reason it can’t be done, it’s just extra hassle for no benefit.
I suspect the problem for C# plugins is, strictly speaking, WinForms and not C# itself. WinForms was probably not meant to interoperate with unmanaged Win32 API windows in the same application (especially with Win32 handling the top level windows and the message loop).
If that’s the problem, and if the creators and maintainers of C#/WinForms have not specified any mechanism by which this can be reliably accomplished, then there’s little anyone can do about it except hack around and hope, or refrain from trying to use C#/WinForms for something it is not meant to do.