-1

I saw a lot of questions (in so) and topics anywhere else where people try to debug a component using MesssageBox.Show (including myself at the beginning). That is definitely a bad approach. The reason is, that functions like Debug.Write or Console.Write do not work at design time. So you do the next best thing that works to output a string.

Another point is that I've never really looked at components before. When I wrote a program, I usually did it directly as code / class and tested it at runtime.

If you are interested (or confused or both) in what I write feel free to correct me if I'm wrong and / or add your suggestions.

My question should actually be: "What is the best way to debug a component during design time?". But stack overflow does't like questions like this.

Or just to the problem below: "Problem with debug / console output during design time".

What I tried first: MS docs guide to debug components at design time

I use the same practise as mentioned in this question to debug a component at design time.

Breakpoint works but I still don't get any output text using Console/Debug text: debug line


While in a normal runtime debug session the console and/or debug output is passed to the VS pane. Just to notice the debug pane outputs anything at all.
VS Pane

Of course, in such a scenario you should use Debug and not the Console. But since Debug.WriteLine didn't work, I still tried to output something directly in the console.

After some deeper tests I came to the conclusion that the particle from MS Docs is garbage and I gave up because:

  • Too much time and effort hooking into other session every time.
  • It's cluttered and confusing
  • It actually doesn't do the same debug routine as it would in runtime.
  • Unhandled errors causes the designer to deactivate the component which leads into restart (both) VS sessions.
  • And as said I didn't found a clue why I don't get debug / console output.

    Which further led me to the following conclusions:
  1. For me it is basically enough to output something in the console at critical points. What is extremely annoying is that I cannot delete the components and insert them again because Visual Studio gets stuck internally somewhere. That would be the next thing I want to tackle.

  2. It is definitely not necessary and logically wrong to attach the VS process to another instance of VS. The session of VS already does that with the component. And generates a temp exe inside appdata.

  3. A custom or extended error handler (logger) is a good idea. Like you do when you make a public exe where other people can send you the debug text. In this case you send it back to yourself. ;)

Next step: What options do stack overflow and other pages offer?

Related topics on stack overflow

Related topics on other sites

My current goals

  • Get away from MS docs "solution"
  • Set up a simple debug output system for the designer
7three
  • 163
  • 13
  • **TIP:** when debuggin designer components, controls, certain VSIX_, instead of **Attach to process...** (which is probably too late) consider changing the project's **Start Action**. See __ **> Properties > Debug > Start External Program** and specify the path to **devenv.exe** (Visual Studio). In **Command line arguments** enter the path to a _new minimal testbed solution project_ that contains your component. Ideally it should be a different solution. Now when you **Debug > Start Debugging** Visual Studio will launch and load your testbed –  Nov 15 '20 at 03:17
  • 1
    Hmmm....assuming yours is a **DEBUG** build, `Debug.Write`xxx should have worked. It's possible that the output is being written to the _2nd_ Visual Studio **Output** window. Failing that, use the Windows native function [`OutputDebugString`](https://www.pinvoke.net/default.aspx/kernel32/OutputDebugString.html) and use MS's [DebugView](https://learn.microsoft.com/en-us/sysinternals/downloads/debugview) –  Nov 15 '20 at 03:24
  • If by the "MS docs" you mean [Walkthrough: Debug Custom Windows Forms Controls at Design Time](https://learn.microsoft.com/en-us/dotnet/desktop/winforms/controls/walkthrough-debugging-custom-windows-forms-controls-at-design-time), this procedure is valid. The one issue I have with it is the inclusion of the "DebuggingExample" project in the same solution as the "DebugControlLibrary" project; I prefer to keep them separate. Writing to the Console will not write to the VS Output window. However, contrary to your assertion, the Debgug.WriteXXXX methods will generate output. – TnTinMn Nov 15 '20 at 14:14
  • If you are not seeing the results of these calls in the Output window, make sure that the "Redirect all Output Window text to the Immediate Window" option is not enabled. Please consider not using Console methods for debug output, the Debug/Trace methods are the way to generate this information. – TnTinMn Nov 15 '20 at 14:14
  • `Debug.XYZ` *should* work, yes. As already noted I have read through many links. And I noticed that many others also agree that MS Docs' debugging method is anything but good. Even if the procedure is basically valid. – 7three Nov 15 '20 at 20:42
  • There's nothing wrong with MesssageBox.Show. Just as there's nothing wrong with JS alert(), C printf(), Java System.out.println(), etc etc. The Microsoft article "Walkthrough: Debug Custom Windows Forms Controls at Design Time" is definitely *NOT* "garbage" - it's perfectly valid. It's just intended for a limited set of use cases. It may or may not be "optimal" for your particular use case. – paulsm4 Nov 15 '20 at 22:39

1 Answers1

0

What I've done so far is a simple message system that seems to work so far. I don't want to output the debug content inside a designer generated window. I could create a Form Window, I know. Or even better I could create an other application to communicate with the designer. For the moment I'm satisfied with a very simple solution. Just to get any output at all.

I made a few interesting experiences, e.g. to get the solution / project folder at design time.

It's a bit of a tinker, but I'm pretty happy with it. So I had the absurd idea to simply output the text internally into a notepad window. I have no "using" namespaces to make it more flexible and clearly expressed. There is also a dispose implementation. I'm not sure if that makes sense. But my idea was to avoid a persistent thread if the class is deconstructed during design time. Or just to be prepared for the case. Here's the code:

public class SendToNotepad : System.IDisposable
{
    private bool _disposed;

    public SendToNotepad() { }

    public static void Text(string text, bool d) { Send.SendText(text, d); }

    ~SendToNotepad() { Dispose(false); }

    public void Dispose()
    {
        Dispose(true);
        System.GC.SuppressFinalize(this);
    }

    protected virtual void Dispose(bool disposing)
    {
        if (_disposed) { return; }
        if (disposing)
        {
            // Dispose managed state
        }
        Send.Dispose();
        _disposed = true;
    }

    private static class Send
    {
        private const int STARTF_USESHOWWINDOW = 1;
        private const int SW_SHOWNOACTIVATE = 4;
        private const int SW_SHOWMINNOACTIVE = 7;

        private const int CREATE_NEW_CONSOLE = 0x000010;

        private const int EM_SETSEL = 0x00B1;
        private const int EM_REPLACESEL = 0x00C2;
        private const int WM_SETTEXT = 0x000C;
        private const int WM_GETTEXTLENGTH = 0x000E;

        private const int WM_COMMAND = 0x0111;
        private const int WM_APPCOMMAND = 0x0319;
        private const int WM_QUIT = 0x0012;

        private const int APPCOMMAND_SAVE = 0x201000;

        private const int SleepTime = 10;

        private static UnsafeNativeMethods.STARTUPINFO _si;
        private static UnsafeNativeMethods.PROCESS_INFORMATION _pi;

        private static System.Diagnostics.Process _p;
        private static System.Threading.Timer _timer;
        private static bool _isWaiting;
        private static string _waitingCatcher;
        private static string _debugFileName;
        private static readonly string Namespace = System.Reflection.MethodBase.GetCurrentMethod().DeclaringType.Namespace;
        private static readonly string[] TitleLoopAniChars = new[] { "|", "/", "––", "\\" };
        private static readonly int TitleLoopAniCharsLength = TitleLoopAniChars.Length - 1;

        public static void Dispose()
        {
            if (_p != null)
            {
                try
                {
                    UnsafeNativeMethods.CloseHandle(_pi.hProcess);
                    UnsafeNativeMethods.CloseHandle(_pi.hThread);
                }
                catch { }
                _p.Dispose();
                _p = null;
            }
            if (_timer != null)
            {
                _timer.Dispose();
                _timer = null;
            }
        }

        public static void SendText(string text, bool d)
        {
            if (!d) { return; }
            text = System.DateTime.Now.TimeOfDay + ": " + text + System.Environment.NewLine;
            if (_isWaiting)
            {
                _waitingCatcher += text;
                return;
            }
            _isWaiting = true;
            int maxWait = 200; // Max timeout over all (* SleepTime)
            if (_p == null)
            {
                _debugFileName = GetDebugFileName();
                if (System.String.IsNullOrEmpty(_debugFileName))
                {
                    _waitingCatcher += text;
                    _isWaiting = false;
                    return;
                }
                if (!System.IO.File.Exists(_debugFileName))
                {
                    try { System.IO.File.Create(_debugFileName).Dispose(); }
                    catch { }
                }
                if (!System.IO.File.Exists(_debugFileName))
                {
                    _waitingCatcher += text;
                    _isWaiting = false;
                    return;
                }
                _si = new UnsafeNativeMethods.STARTUPINFO
                {
                    dwFlags = STARTF_USESHOWWINDOW,
                    wShowWindow = SW_SHOWMINNOACTIVE,
                    cb = System.Runtime.InteropServices.Marshal.SizeOf(_si)
                };
                bool success = UnsafeNativeMethods.CreateProcess(null, "notepad /W \"" + _debugFileName + "\"", System.IntPtr.Zero, System.IntPtr.Zero, true, 0, System.IntPtr.Zero, null, ref _si, out _pi);
                while (maxWait-- > 0 && success)
                {
                    System.Threading.Thread.Sleep(SleepTime);
                    try { _p = System.Diagnostics.Process.GetProcessById(_pi.dwProcessId); } // grab Process to handle WaitForExit()
                    catch { }
                    if (_p != null) { break; }
                }
                if (_p == null)
                {
                    _waitingCatcher += text;
                    _isWaiting = false;
                    return;
                }
                while (maxWait-- > 0 && (!_p.Responding || !_p.WaitForInputIdle())) { System.Threading.Thread.Sleep(SleepTime); }
                _timer = new System.Threading.Timer(NotifyOnProcessExits);
                _timer.Change(0, 0);
            }
            else
            {
                while (maxWait-- > 0 && (!_p.Responding || !_p.WaitForInputIdle())) { System.Threading.Thread.Sleep(SleepTime); }
            }

            System.IntPtr fwx = UnsafeNativeMethods.FindWindowEx(_p.MainWindowHandle, System.IntPtr.Zero, "Edit", null);
            UnsafeNativeMethods.SendMessage(fwx, EM_SETSEL, 0, -1);
            UnsafeNativeMethods.SendMessage(fwx, EM_SETSEL, -1, -1);
            UnsafeNativeMethods.SendMessageW(fwx, EM_REPLACESEL, 1, text);
            if (!System.String.IsNullOrEmpty(_waitingCatcher))
            {
                UnsafeNativeMethods.SendMessage(fwx, EM_SETSEL, 0, -1);
                UnsafeNativeMethods.SendMessage(fwx, EM_SETSEL, -1, -1);
                UnsafeNativeMethods.SendMessageW(fwx, EM_REPLACESEL, 1, _waitingCatcher);
                _waitingCatcher = "";
            }
            UnsafeNativeMethods.SendMessage(_p.MainWindowHandle, WM_COMMAND, 0x0003, 0x0); // first menu, item 3 (save)
            _isWaiting = false;
        }

        private static void NotifyOnProcessExits(object timer)
        {
            if (_p == null) { return; }
            // _p.WaitForExit();
            // This is just for fun as response feedback
            int i = 0;
            while (!_p.HasExited)
            {
                System.Threading.Thread.Sleep(500);
                UnsafeNativeMethods.SetWindowText(_p.MainWindowHandle, Namespace + "  –>  " + _debugFileName + "       " + TitleLoopAniChars[i]);
                i = i == TitleLoopAniCharsLength ? 0 : i + 1;
            }
            Dispose();
        }

        private static string GetDebugFileName() // Hack to get solution path while design time
        {
            string s;
            try
            {
                var trace = new System.Diagnostics.StackTrace(true);
                var frame = trace.GetFrame(0);
                s = System.IO.Path.GetDirectoryName(frame.GetFileName());
            }
            catch { return null; }
            return s == null ? null : s + "Debug.txt";
        }

        [System.Security.SuppressUnmanagedCodeSecurity]
        private static class UnsafeNativeMethods
        {
            [System.Runtime.InteropServices.StructLayout(System.Runtime.InteropServices.LayoutKind.Sequential)]
            public struct STARTUPINFO
            {
                public System.Int32 cb;
                public string lpReserved;
                public string lpDesktop;
                public string lpTitle;
                public System.Int32 dwX;
                public System.Int32 dwY;
                public System.Int32 dwXSize;
                public System.Int32 dwYSize;
                public System.Int32 dwXCountChars;
                public System.Int32 dwYCountChars;
                public System.Int32 dwFillAttribute;
                public System.Int32 dwFlags;
                public System.Int16 wShowWindow;
                public System.Int16 cbReserved2;
                public System.IntPtr lpReserved2;
                public System.IntPtr hStdInput;
                public System.IntPtr hStdOutput;
                public System.IntPtr hStdError;
            }

            [System.Runtime.InteropServices.StructLayout(System.Runtime.InteropServices.LayoutKind.Sequential)]
            internal struct PROCESS_INFORMATION
            {
                public System.IntPtr hProcess;
                public System.IntPtr hThread;
                public int dwProcessId;
                public int dwThreadId;
            }

            [System.Runtime.InteropServices.DllImport("kernel32.dll")]
            public static extern bool CreateProcess(
                string lpApplicationName,
                string lpCommandLine,
                System.IntPtr lpProcessAttributes,
                System.IntPtr lpThreadAttributes,
                bool bInheritHandles,
                uint dwCreationFlags,
                System.IntPtr lpEnvironment,
                string lpCurrentDirectory,
                [System.Runtime.InteropServices.In] ref STARTUPINFO lpStartupInfo,
                out PROCESS_INFORMATION lpProcessInformation
            );

            [System.Runtime.InteropServices.DllImport("kernel32.dll", SetLastError = true)]
            [return: System.Runtime.InteropServices.MarshalAs(System.Runtime.InteropServices.UnmanagedType.Bool)]
            public static extern bool CloseHandle(System.IntPtr hObject);

            [System.Runtime.InteropServices.DllImport("user32.dll")]
            public static extern int SendMessage(System.IntPtr hWnd, int uMsg, int wParam, int lParam);
            [System.Runtime.InteropServices.DllImport("user32.dll")]
            public static extern System.IntPtr SendMessage(System.IntPtr hWnd, int uMsg, System.IntPtr wParam, System.IntPtr lParam);
            [System.Runtime.InteropServices.DllImport("user32.dll", CharSet = System.Runtime.InteropServices.CharSet.Unicode)]
            public static extern int SendMessageW(System.IntPtr hWnd, int uMsg, int wParam, string lParam);

            [System.Runtime.InteropServices.DllImport("user32.dll")]
            public static extern System.IntPtr DefWindowProc(System.IntPtr hWnd, int msg, System.IntPtr wParam, System.IntPtr lParam);

            [System.Runtime.InteropServices.DllImport("user32.dll")]
            public static extern System.IntPtr FindWindowEx(System.IntPtr hwndParent, System.IntPtr hwndChildAfter, string lpszClass, string lpszWindow);

            [System.Runtime.InteropServices.DllImport("user32.dll")]
            public static extern int SetWindowText(System.IntPtr hWnd, string text);
        }
    }
}

And inside a component class:

private static readonly bool DesignTime = LicenseManager.UsageMode == LicenseUsageMode.Designtime;
SendToNotepad.Text("Debug text", DesignTime);

That has the nice (unexcepted) side effect that if VS crashes the debug content is retained. Debug component

And now I have the still more absurd idea of hooking into the vs process and sending the text back. I think I'm digging myself in a hole deeper and deeper. ;)

7three
  • 163
  • 13
  • 1
    If `Debug.Write`xxx was not working why didn't you just _write to a file_ using your favourite logging library and use a tool like _[BareTail](https://www.baremetalsoft.com/baretail/)_. The latter will show the contents of a file and any changes in _real-time_, scrolling the contents as necessary. –  Nov 15 '20 at 03:45
  • 2
    You have essentially created the functionality of a _TraceListener_. You may consider writing this as a proper [TraceListener Class](https://learn.microsoft.com/en-us/dotnet/api/system.diagnostics.tracelistener?) so that it works with the normal Debug.Write methods. – TnTinMn Nov 15 '20 at 14:27
  • @MickyD Because I threw myself into something without thinking enough beforehand. And then I finished it. What you say is what I'm planning to do during my learning process. With taking into account your and other comments. – 7three Nov 15 '20 at 20:08
  • @TnTinMn I'll definitely do that. The TraceListener Class looks really promiseful. Maybe I'll even be able to sneak into the component with it. Let's see what comes out. – 7three Nov 15 '20 at 20:14
  • @MichkyD And my preference to use as few external tools as possible. – 7three Nov 15 '20 at 20:20
  • That's quite OK good sir. I am impressed with what you have done nethertheless –  Nov 16 '20 at 03:06
  • @MickyD I'm not sure you're just kidding me. Anyway, right now I'm in the process of figuring out what exactly VS does when I drag a component into the designer. As far as I see that's the crux of the matter. I imagine it in such a way that I can intercept and rewrite the corresponding process. I hope this way I can kill several birds with one stone. – 7three Nov 16 '20 at 03:58
  • 1
    Sincere. (I was one of the ones who gave you an up-vote). Regarding _"I'm in the process of figuring out what exactly VS does"_ you might want to check out the _Visual Studio Extensibility SDK_ –  Nov 16 '20 at 05:16
  • Thanks, You did a good investigation. +1 – niek tuytel Jul 01 '22 at 12:46