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.