C# Dockable dialog undocked freezes Notepad++
-
I’m no C# coder, but have been adding to a fork of the MarkdownPanel and there is a nasty bug that I can’t solve.
Basically, undock the main MarkdownPanel viewer dockable dialog and Notepad++ freezes / crashes. There is some allusion to this in the issues though it mentions scrollbar interaction as well. I think it is really the undocking which causes the issue.
@rdipardo mentions this C# undocking / Notepad++ freezing is “a relatively trivial fix”, but alas my C# skills are not up to par to decipher the fix.
Any C# developer care to spoon-feed me?
Cheers.
-
@Michael-Vincent
I mean, I doubt I could figure out what is causing your issue, but what if you just look at the initialization code for these dockable dialogs, which I know can be undocked without causing the issues you’re describing, and compare their code to the code for your buggy form: -
@Mark-Olson said in C# Dockable dialog undocked freezes Notepad++:
can be undocked without causing the issues you’re describing
[…]The comment which @Michael-Vincent linked as giving the relatively trivial fix was specifically in response to my comment mentioning that CSVLint does have this problem.
The code given as a fix is simple and explicit, but not being a C# programmer myself, I don’t know how to tell him how to identify the grid(group) control for which the extended style attribute must be set.
-
@rdipardo mentions this C# undocking / Notepad++ freezing is “a relatively trivial fix” . . .
The comment which @Michael-Vincent linked as giving the relatively trivial fix was specifically in response to my comment mentioning that CSVLint does have this problem.
Don’t look at me. Damjan Cvetko correctly diagnosed this problem about 17 years ago — in the forgotten language of Delphi, the “.NET of the '90s”. The linked GitHub issue just gave me the idea that his hack would be translatable to C#, following the suggestion of @mahee96.
The gist is that you set the
WS_EX_CONTROLPARENT
bit on the docking form’sGWL_EXSTYLE
attribute whenever the OS emitsWM_NOTIFY
. Damjan used an intercepting message procedure (originally to clear theWS_EX_CONTROLPARENT
bit, but both @mahee96 and I found that setting it was more effective).The .NET way probably involves overriding
Windows.Forms.WndProc
, checking that theMsg
field of the passed-instruct
equalsWM_NOTIFY
, accessing and modifying the receiving form’sGWL_EXSTYLE
attribute if true . . . Never tried it, though 😉 -
This quick patch will at least keep the UI thread from hanging up.
Kludge of this sort is best hidden behind a base class, so the attribute-setting method takes a generic
Control
for easier extraction someday. An updated template would ideally define classes for basic docking and modeless forms, similar to the GUI toolkit that @ThosRTanner has been developing.Note that nothing is or should have to be added to the docking panel’s constructor, contrary to the pattern of Damjan’s template. Debugging showed that only the
WM_NOTIFY
handler actually works, and only if it loops recursively into every child control(!)diff --git a/NppMarkdownPanel/MarkdownPanelController.cs b/NppMarkdownPanel/MarkdownPanelController.cs index 6631772..3ed6e70 100644 --- a/NppMarkdownPanel/MarkdownPanelController.cs +++ b/NppMarkdownPanel/MarkdownPanelController.cs @@ -475,6 +475,24 @@ namespace NppMarkdownPanel { case (int)WindowsMessage.WM_NOTIFY: var notify = (NMHDR)Marshal.PtrToStructure(m.LParam, typeof(NMHDR)); + var panel = (MarkdownPreviewForm)viewerInterface; + + // do not intercept Npp notifications like DMN_CLOSE, etc. + if (notify.hwndFrom != PluginBase.nppData._nppHandle) + { + panel.Invalidate(true); + if (IntPtr.Size == 8) + { + SetControlParent(panel, Win32.GetWindowLongPtr, Win32.SetWindowLongPtr); + } + else + { + SetControlParent(panel, Win32.GetWindowLong, Win32.SetWindowLong); + } + + panel.Update(); + return; + } if (notify.code == (int)DockMgrMsg.DMN_CLOSE) { ToolWindowCloseAction(); @@ -482,5 +500,31 @@ namespace NppMarkdownPanel break; } } + + /// <summary> + /// Sets the <see cref="Win32.WS_EX_CONTROLPARENT"/> extended attribute on <paramref name="parent"/> and any child controls, + /// following @mahee96's advice on the archived Plugin.Net issue tracker. + /// <para> + /// <seealso href="https://github.com/kbilsted/NotepadPlusPlusPluginPack.Net/issues/17#issuecomment-683455467"/> + /// </para> + /// </summary> + /// <param name="parent"> + /// A WinForm that's been registered with Npp's Docking Manager by sending <see cref="NppMsg.NPPM_DMMREGASDCKDLG"/>. + /// </param> + private void SetControlParent(Control parent, Func<IntPtr, int, IntPtr> wndLongGetter, Func<IntPtr, int, IntPtr, IntPtr> wndLongSetter) + { + if (parent.HasChildren) + { + long extAttrs = (long)wndLongGetter(parent.Handle, Win32.GWL_EXSTYLE); + if (Win32.WS_EX_CONTROLPARENT != (extAttrs & Win32.WS_EX_CONTROLPARENT)) + { + wndLongSetter(parent.Handle, Win32.GWL_EXSTYLE, new IntPtr(extAttrs | Win32.WS_EX_CONTROLPARENT)); + } + foreach (Control c in parent.Controls) + { + SetControlParent(c, wndLongGetter, wndLongSetter); + } + } + } } } diff --git a/NppMarkdownPanel/PluginInfrastructure/Win32.cs b/NppMarkdownPanel/PluginInfrastructure/Win32.cs index db80520..471143d 100644 --- a/NppMarkdownPanel/PluginInfrastructure/Win32.cs +++ b/NppMarkdownPanel/PluginInfrastructure/Win32.cs @@ -303,6 +303,54 @@ namespace Kbg.NppPluginNET.PluginInfrastructure public const int WM_CREATE = 1; + public const int GWL_EXSTYLE = -20; + public const int GWLP_HINSTANCE = -6; + public const int GWLP_HWNDPARENT = -8; + public const int GWLP_ID = -12; + public const int GWL_STYLE = -16; + public const int GWLP_USERDATA = -21; + public const int GWLP_WNDPROC = -4; + + public const long WS_EX_ACCEPTFILES = 0x00000010L; + public const long WS_EX_APPWINDOW = 0x00040000L; + public const long WS_EX_CLIENTEDGE = 0x00000200L; + public const long WS_EX_COMPOSITED = 0x02000000L; + public const long WS_EX_CONTEXTHELP = 0x00000400L; + public const long WS_EX_CONTROLPARENT = 0x00010000L; + public const long WS_EX_DLGMODALFRAME = 0x00000001L; + public const long WS_EX_LAYERED = 0x00080000L; + public const long WS_EX_LAYOUTRTL = 0x00400000L; + public const long WS_EX_LEFT = 0x00000000L; + public const long WS_EX_LEFTSCROLLBAR = 0x00004000L; + public const long WS_EX_LTRREADING = 0x00000000L; + public const long WS_EX_MDICHILD = 0x00000040L; + public const long WS_EX_NOACTIVATE = 0x08000000L; + public const long WS_EX_NOINHERITLAYOUT = 0x00100000L; + public const long WS_EX_NOPARENTNOTIFY = 0x00000004L; + public const long WS_EX_NOREDIRECTIONBITMAP = 0x00200000L; + public const long WS_EX_OVERLAPPEDWINDOW = (WS_EX_WINDOWEDGE | WS_EX_CLIENTEDGE); + public const long WS_EX_PALETTEWINDOW = (WS_EX_WINDOWEDGE | WS_EX_TOOLWINDOW | WS_EX_TOPMOST); + public const long WS_EX_RIGHT = 0x00001000L; + public const long WS_EX_RIGHTSCROLLBAR = 0x00000000L; + public const long WS_EX_RTLREADING = 0x00002000L; + public const long WS_EX_STATICEDGE = 0x00020000L; + public const long WS_EX_TOOLWINDOW = 0x00000080L; + public const long WS_EX_TOPMOST = 0x00000008L; + public const long WS_EX_TRANSPARENT = 0x00000020L; + public const long WS_EX_WINDOWEDGE = 0x00000100L; + + [DllImport("user32")] + public static extern IntPtr GetWindowLongPtr(IntPtr hWnd, int nIndex); + + [DllImport("user32")] + public static extern IntPtr GetWindowLong(IntPtr hWnd, int nIndex); + + [DllImport("user32")] + public static extern IntPtr SetWindowLongPtr(IntPtr hWnd, int nIndex, IntPtr dwNewLong); + + [DllImport("user32")] + public static extern IntPtr SetWindowLong(IntPtr hWnd, int nIndex, IntPtr dwNewLong); + [DllImport("user32")] public static extern bool ClientToScreen(IntPtr hWnd, ref Point lpPoint);
-
Edit
If you tried the first iteration of the patch and found that 32-bit Notepad++ threw aSystem.EntryPointNotFoundException
, you can read the explanation on Stack Overflow. -
It looks like I have the same issue with my SQLinForm plugin, i.e. When the plugin is undocked, N++ freezes after a while.
Reading the discussion I do not understand what is the final suggestion to fix this issue. could you be so kind and point me to the solution?
Also, ist it possible to make a dialog not-undockable?Regards
Guido -
@Guido-Thelen said in C# Dockable dialog undocked freezes Notepad++:
It looks like I have the same issue with my SQLinForm plugin, i.e. When the plugin is undocked, N++ freezes after a while.
Reading the discussion I do not understand what is the final suggestion to fix this issue. could you be so kind and point me to the solution?Since SQLinForm appears to be a professional product, for which I presume stable and reliable solutions are in order, I’ll make this observation:
The best solution is not to use C# to write plugins for a C++ program.
If that is not practical, then the current best suggestion is to read the patch above from @rdipardo and figure out how to apply the same logic in your plugin. There is no simple, clean solution because the problem lies in some expectations that non-modal C# forms have regarding the environment in which they run, and Microsoft has not bothered to document anywhere what those expectations are, nor to indicate how a C++ program can provide an appropriate environment for a C# plugin. I believe C# does something in its message loop that is different from a “normal” Win32 API message loop. Without specific documentation, all fixes are going to be somewhat “hacky.”
-
@Coises many thanks for your answer. I am not a C++ programmer. Therefore I will try to solve it the “hacky” way .
I found another comment from you suggesting to send a MODELESS message.
https://community.notepad-plus-plus.org/post/91817
Would this also be a way to go?
Another option for me would be to not allow to unlock the dialog Panel. Is this possible?
Regards
Guido -
I fear I can’t be of much help here. I suggested NPPM_MODELESSDIALOG to solve a problem with common keyboard shortcuts not working in modeless dialogs (docking or not); Notepad++ eventually incorporated a different way of working around the problem for dialogs that don’t send that message, and C# programmers observed troublesome changes in behavior when they did send it.
I don’t know C#, so I don’t have a way of trying it. I don’t think it will help here, but I don’t know for sure.
I’m not familiar with docking dialogs, either; I don’t see a way to prevent undocking them, but someone who uses them more might know for sure.
-
This file in JsonTools contains a lot of the helper code you would need to make your forms compatible with NPP’s more recent releases.
As you may notice from reading the file, making your forms compliant with NPP’s expectations introduces a lot of frustrating issues, but IMO this is just the cost of maintaining a C# plugin.