在设计期间在 VS(10) 中调试 C# .NET 自定义组件/控件

Posted

技术标签:

【中文标题】在设计期间在 VS(10) 中调试 C# .NET 自定义组件/控件【英文标题】:Debugging a C# .NET custom component / control in VS(10) during design time 【发布时间】:2021-02-26 15:13:42 【问题描述】:

我在其他任何地方都看到了很多问题和主题,人们尝试使用MesssageBox.Show 调试组件(包括开头的我自己)。这绝对是一个糟糕的做法。原因是,像 Debug.WriteConsole.Write 这样的函数在设计时不起作用。所以你做下一个最好的事情来输出一个字符串。

另外一点是,我以前从未真正看过组件。我写程序的时候,一般都是直接做代码/类,在运行时进行测试。

如果您对我写的内容感兴趣(或困惑或两者兼而有之),如果我错了,请随时纠正我和/或添加您的建议。

我的问题实际上应该是:“在设计时调试组件的最佳方法是什么?”。但是堆栈溢出不喜欢这样的问题。

或者只是针对以下问题:“设计时调试/控制台输出的问题”。

我首先尝试的是:MS docs guide to debug components at design time ????

我使用this question 中提到的相同做法在设计时调试组件。

断点有效,但我仍然没有使用控制台/调试文本得到任何输出文本: 在正常运行时调试会话中,控制台和/或调试输出被传递到 VS 窗格。只是要注意调试窗格会输出任何内容。 当然,在这种情况下,您应该使用Debug 而不是Console。但是由于Debug.WriteLine不起作用,我还是尝试直接在控制台输出一些东西。 经过一些更深入的测试,我得出结论,来自MS Docs 的粒子是垃圾,我放弃了,因为:

每次都需要花费大量时间和精力来连接其他会话。 杂乱无章 它实际上并不像在运行时那样执行相同的调试例程。 未处理的错误会导致设计人员停用导致重新启动(两个)VS 会话的组件。 如前所述,我没有找到为什么我没有得到调试/控制台输出的线索。 这进一步使我得出以下结论

    对我来说,在关键点在控制台中输出一些东西基本上就足够了。非常烦人的是,我无法删除组件并再次插入它们,因为 Visual Studio 在内部某处卡住了。这将是我接下来要解决的问题。

    绝对没有必要并且逻辑错误将VS进程附加到另一个VS实例。 VS 的会话已经对组件执行了此操作。并在 appdata 中生成一个临时 exe。

    自定义或扩展的错误处理程序(记录器)是个好主意。就像您制作公共 exe 时所做的那样,其他人可以向您发送调试文本。在这种情况下,您将其发回给自己。 ;)

下一步:堆栈溢出和其他页面提供哪些选项? ??????

堆栈溢出相关主题

How to debug winforms designer - 与 MS 文档相同,但接受的答案很有趣: How does the Winforms Designer instantiate my form? How can I debug at design time? - XDesProc.exe VS12++ 我们已经知道的相同程序,也许只是我,但我不喜欢它。 Visual Studio 2015 Debugging Custom Control - 这个只是让我感到困惑,似乎是一种不同于我喜欢的编程类型。 How to troubleshoot and debug Visual Studio design mode errors? - 方案 f 的另一个副本 How do you debug a WPF user control in design mode? - 方案 f 的另一个副本 DesignMode with nested Controls 这个很有趣。但是他们在某种程度上对 iDesignerHost 有问题,因此在组件类中设置了只读。在开始阅读之前,我已经解决了这两个问题。稍后看我的回答。 And this。 Good Way to Debug Visual Studio Designer Errors - 这个不错。

其他网站上的相关主题

Visual Studio 2013 and Blend design-time exception debugging How to debug C# Winforms User controls at Design Time Design-Time Support for Custom Controls - 尚未通读 Designer Debugging in WinRT

我目前的目标????

远离 MS 文档“解决方案” 为设计人员设置一个简单的调试输出系统

【问题讨论】:

提示: 当调试设计器组件、控件、某些 VSIX_ 时,而不是 附加到进程...(这可能为时已晚)考虑更改项目的开始操作。请参阅 > Properties > Debug > Start External Program 并指定 devenv.exe (Visual Studio) 的路径。在命令行参数中输入包含您的组件的新的最小测试平台解决方案项目的路径。理想情况下,它应该是一个不同的解决方案。现在,当您 Debug > Start Debugging 时,Visual Studio 将启动并加载您的测试平台 嗯....假设您的版本是 DEBUGDebug.Writexxx 应该可以工作。输出可能正在写入 2nd Visual Studio Output 窗口。如果做不到这一点,请使用 Windows 原生函数 OutputDebugString 并使用 MS 的 DebugView 如果“MS docs”是指Walkthrough: Debug Custom Windows Forms Controls at Design Time,则此过程有效。我遇到的一个问题是将“DebuggingExample”项目包含在与“DebugControlLibrary”项目相同的解决方案中;我更喜欢将它们分开。写入控制台不会写入 VS 输出窗口。但是,与您的断言相反,Debgug.WriteXXXX 方法将生成输出。 如果您没有在输出窗口中看到这些调用的结果,请确保未启用“将所有输出窗口文本重定向到立即窗口”选项。请考虑不要使用 Console 方法进行调试输出,Debug/Trace 方法是生成此信息的方式。 Debug.XYZ 应该工作,是的。如前所述,我已经阅读了许多链接。我注意到许多其他人也同意 MS Docs 的调试方法一点也不好。即使程序基本有效。 【参考方案1】:

到目前为止,我所做的是一个简单的消息系统,到目前为止似乎可以正常工作。我不想在设计器生成的窗口中输出调试内容。我知道,我可以创建一个表单窗口。或者更好的是,我可以创建一个其他应用程序来与设计师交流。目前我对一个非常简单的解决方案感到满意。只是为了得到任何输出。

我做了一些有趣的经历,例如在设计时获取解决方案/项目文件夹。

这有点小题大做,但我很满意。所以我有一个荒谬的想法,将文本简单地在内部输出到记事本窗口中。我没有“使用”名称空间来使其更加灵活和清晰地表达。还有一个 dispose 实现。我不确定这是否有意义。但是我的想法是如果在设计时解构类,则避免使用持久线程。或者只是为案件做好准备。代码如下:

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);
        
    

在组件类中:

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

如果 VS 崩溃,调试内容会被保留。

现在我有了一个更荒谬的想法,即连接到 vs 进程并将文本发回。我想我正在把自己挖得越来越深。 ;)

【讨论】:

如果Debug.Writexxx 无法正常工作,您为什么不使用您最喜欢的日志库写入文件 并使用BareTail。后者将实时显示文件的内容和任何更改,并根据需要滚动内容。 您基本上已经创建了 TraceListener 的功能。你可以考虑把它写成一个正确的TraceListener Class,这样它就可以和普通的 Debug.Write 方法一起工作。 @MickyD 因为我事先没有充分考虑就投入了一些事情。然后我完成了。你说的就是我在学习过程中打算做的。考虑到您和其他 cmets。 @TnTinMn 我一定会这样做的。 TraceListener 类看起来很有前途。也许我什至可以用它潜入组件。让我们看看结果如何。 真诚的。 (我是给你投票的人之一)。关于“我正在弄清楚 VS 究竟做了什么”,您可能需要查看 Visual Studio 可扩展性 SDK

以上是关于在设计期间在 VS(10) 中调试 C# .NET 自定义组件/控件的主要内容,如果未能解决你的问题,请参考以下文章

c# HttpWebRequest 使用代理访问网页,在vs中release调试的时候可以,直接运行出错

在VS2015中用C++创建DLL并用C#调用且同时实现对DLL的调试

c# vs2005在win7下如何用管理员身份调试程序

在调试时编辑 C#

我在VS2017里面用C#在.net framework上编写了一个dll,我想编译,怎么解决?

VS2012中WPF设计界面卡(C#)