• .NET Windows Form 改变窗体类名(Class Name)有多难?


      研究WinForm的东西,是我的一个个人兴趣和爱好,以前做的项目,多与WinForm相关,然而这几年,项目都与WinForm没什么关系了,都转为ASP.NET MVC与WPF了。关于今天讨论的这个问题,以前也曾深入研究过,只是最近有朋友问到这个问题,就再挖挖这个坟(坑)。

    一、类名是啥?

       打开神器SPY++,VS2013 在【工具】菜单里:  

      VS2013之前的VS版本,在【开始菜单】里:

      打开SPY++,点击标注的按钮,

      在打开的窗口上,把雷达按钮拖到你想查看的窗口,就可以看到它的类名了,下面就是QQ的类名:

      再看看.NET WinForm的窗体类名:

      一大串啊,有没有,我不想这样,我想要一个有个性的、简单的类名,咋办?

    二、 不是有个CreateParams属性吗?

      作为一个有多年WinForm开发经验的程序猿,这有啥难的,WinForm的控件不是都有个CreateParams属性吗?里面可以不是就可以设置窗口类名吗?看看:

      真的有,这不就简单了嘛,动手,于是有下面代码:  

        public partial class FormMain : Form
        {
            public FormMain()
            {
                InitializeComponent();
            }
    
            protected override CreateParams CreateParams
            {
                get
                {
                    CreateParams createParams = base.CreateParams;
                    createParams.ClassName = "Starts2000.Window"; //这就是我想要的窗体类名。
                    return createParams;
                }
            }
        }
    

      编译,运行,结果却是这样的:  

     

      泥煤啊,这是什么啊,翻~墙,一通谷歌,原来类名使用前都需要注册啊,难道微软只注册了自己的类名,我个性化的他就不帮我注册,那我就自己注册吧,坑爹的微软啊。

    三、注册一个窗口类名吧

      注册窗口类名需要用到Windows API函数了,用C#进行P/Invoke?太麻烦了,做了这么多年的WinForm开发,我可是练了《葵花宝典(C++/CLI)》的,只是因为没自宫,所以没大成,不过,简单用用还是可以的。

      创建一个C++空项目,设置项目属性-配置属性-常规,如下图:  

      于是有了下面的代码:

      1. FormEx.h

    #pragma once
    #include <Windows.h>
    #include <vcclr.h>
    
    #define CUSTOM_CLASS_NAME  L"Starts2000.Window"
    
    namespace Starts2000
    {
    	namespace WindowsClassName
    	{
    		namespace Core
    		{
    			using namespace System;
    			using namespace System::Windows::Forms;
    			using namespace System::Runtime::InteropServices;
    
    			private delegate LRESULT WndProc(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam);
    
    			public ref class FormEx :
    				public Form
    			{
    			public:
    				static FormEx();
    				FormEx();
    			private:
    				static LRESULT WndProc(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam);
    				static void ProcessExit(Object^ sender, EventArgs^ e);
    			};
    		}
    	}
    }
    

      2. FormEx.cpp  

    #include "FormEx.h"
    
    namespace Starts2000
    {
    	namespace WindowsClassName
    	{
    		namespace Core
    		{
    			static FormEx::FormEx()
    			{
    				WNDCLASSEX wc;
    				Starts2000::WindowsClassName::Core::WndProc ^windowProc =
    					gcnew Starts2000::WindowsClassName::Core::WndProc(FormEx::WndProc);
    				pin_ptr<Starts2000::WindowsClassName::Core::WndProc^> pWindowProc = &windowProc;
    
    				ZeroMemory(&wc, sizeof(WNDCLASSEX));
    				wc.cbSize = sizeof(WNDCLASSEX);
    				wc.style = CS_DBLCLKS;
    				wc.lpfnWndProc = reinterpret_cast<WNDPROC>(Marshal::GetFunctionPointerForDelegate(windowProc).ToPointer());
    				wc.hInstance = GetModuleHandle(NULL);
    				wc.hCursor = LoadCursor(NULL, IDC_ARROW);
    				wc.hbrBackground = (HBRUSH)COLOR_WINDOW; //(HBRUSH)GetStockObject(HOLLOW_BRUSH);
    				wc.lpszClassName = CUSTOM_CLASS_NAME;
    
    				ATOM classAtom = RegisterClassEx(&wc);
    				DWORD lastError = GetLastError();
    				if (classAtom == 0 && lastError != ERROR_CLASS_ALREADY_EXISTS)
    				{
    					throw gcnew ApplicationException("Register window class failed!");
    				}
    
    				System::AppDomain::CurrentDomain->ProcessExit += gcnew System::EventHandler(FormEx::ProcessExit);
    			}
    
    			FormEx::FormEx() : Form()
    			{
    			}
    
    			LRESULT FormEx::WndProc(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam)
    			{
    				System::Windows::Forms::Message message = System::Windows::Forms::Message::Create((IntPtr)hWnd,
    					(int)msg, (IntPtr)((void*)wParam), (IntPtr)((void*)lParam));
    				System::Diagnostics::Debug::WriteLine(message.ToString());
    				return DefWindowProc(hWnd, msg, wParam, lParam);
    			}
    
    			void FormEx::ProcessExit(Object^ sender, EventArgs^ e)
    			{
    				UnregisterClass(CUSTOM_CLASS_NAME, GetModuleHandle(NULL));
    			}
    		}
    	}
    }
    

      3. 创建一个C# WinForm项目,引用上面创建的C++/CLI项目生成的DLL,代码跟最开始的区别不大。  

        public partial class FormMain : /*Form*/ FormEx
        {
            public FormMain()
            {
                InitializeComponent();
            }
    
            protected override CreateParams CreateParams
            {
                get
                {
                    CreateParams createParams = base.CreateParams;
                    createParams.ClassName = "Starts2000.Window"; //这就是我想要的窗体类名。
                    return createParams;
                }
            }
        }
    

      编译,运行,结果却仍然是这样的:  

      泥煤啊,微软到底干了什么,我只是想搞点小玩意,满足下我的虚荣心,你竟然……,心中千万头“羊驼”奔腾而过。

      没办法了,微软不都开源了吗,也不需要反编译了,直接下源代码看吧。

    四、也不反编译了,直接找源代码看吧

      在微软的网站(http://referencesource.microsoft.com/)Down下代码,从Form→ContainerControl→ScrollableControl→Control,在Control里找到NativeWindow,再在NativeWindow里面找到了WindowClass,在WindowClass里找到了坑爹的RegisterClass方法,恍然大悟了,有没有,具体看代码,我加了注释。  

    private void RegisterClass() {
        NativeMethods.WNDCLASS_D wndclass = new NativeMethods.WNDCLASS_D();
    
        if (userDefWindowProc == IntPtr.Zero) {
            string defproc = (Marshal.SystemDefaultCharSize == 1 ? "DefWindowProcA" : "DefWindowProcW");
    
            userDefWindowProc = UnsafeNativeMethods.GetProcAddress(new HandleRef(null, UnsafeNativeMethods.GetModuleHandle("user32.dll")), defproc);
            if (userDefWindowProc == IntPtr.Zero) {
                throw new Win32Exception();
            }
        }
    
        string localClassName = className;
    
        if (localClassName == null) {  //看看是否自定义了classnName,就是我们在 CreateParams ClassName设置的值。
    
            // If we don't use a hollow brush here, Windows will "pre paint" us with COLOR_WINDOW which
            // creates a little bit if flicker.  This happens even though we are overriding wm_erasebackgnd.
            // Make this hollow to avoid all flicker.
            //
            wndclass.hbrBackground = UnsafeNativeMethods.GetStockObject(NativeMethods.HOLLOW_BRUSH); //(IntPtr)(NativeMethods.COLOR_WINDOW + 1);
            wndclass.style = classStyle;
    
            defWindowProc = userDefWindowProc;
            localClassName = "Window." + Convert.ToString(classStyle, 16);
            hashCode = 0;
        }
        else {   //坑爹的就在这里了
            NativeMethods.WNDCLASS_I wcls = new NativeMethods.WNDCLASS_I();
            /*注意下面这句代码,特别注意 NativeMethods.NullHandleRef,MSDN说明:
                * BOOL WINAPI GetClassInfo(
                *    _In_opt_ HINSTANCE  hInstance,
                *    _In_     LPCTSTR    lpClassName,
                *    _Out_    LPWNDCLASS lpWndClass
                *  );
                * hInstance [in, optional]
                *  Type: HINSTANCE
                *  A handle to the instance of the application that created the class. 
                * To retrieve information about classes defined by the system (such as buttons or list boxes),
                * set this parameter to NULL.
                * 就是说,GetClassInfo 的第一个参数为 NULL(NativeMethods.NullHandleRef)的时候,只有系统注册的 ClassName
                * 才会返回 True,所以当我们设置了CreateParams ClassName的值后,只要设置的不是系统注册的 ClassName,都会
                * 抛出后面的 Win32Exception 异常,泥煤啊。
            */
            bool ok = UnsafeNativeMethods.GetClassInfo(NativeMethods.NullHandleRef, className, wcls);
            int error = Marshal.GetLastWin32Error();
            if (!ok) {
                throw new Win32Exception(error, SR.GetString(SR.InvalidWndClsName));
            }
            wndclass.style = wcls.style;
            wndclass.cbClsExtra = wcls.cbClsExtra;
            wndclass.cbWndExtra = wcls.cbWndExtra;
            wndclass.hIcon = wcls.hIcon;
            wndclass.hCursor = wcls.hCursor;
            wndclass.hbrBackground = wcls.hbrBackground;
            wndclass.lpszMenuName = Marshal.PtrToStringAuto(wcls.lpszMenuName);
            localClassName = className;
            defWindowProc = wcls.lpfnWndProc;
            hashCode = className.GetHashCode();
        }
    
        // Our static data is different for different app domains, so we include the app domain in with
        // our window class name.  This way our static table always matches what Win32 thinks.
        // 
        windowClassName = GetFullClassName(localClassName);
        windowProc = new NativeMethods.WndProc(this.Callback);
        wndclass.lpfnWndProc = windowProc;
        wndclass.hInstance = UnsafeNativeMethods.GetModuleHandle(null);
        wndclass.lpszClassName = windowClassName;
    
        short atom = UnsafeNativeMethods.RegisterClass(wndclass);
        if (atom == 0) {
    
            int err = Marshal.GetLastWin32Error();
            if (err == NativeMethods.ERROR_CLASS_ALREADY_EXISTS) {
                // Check to see if the window class window
                // proc points to DefWndProc.  If it does, then
                // this is a class from a rudely-terminated app domain
                // and we can safely reuse it.  If not, we've got
                // to throw.
                NativeMethods.WNDCLASS_I wcls = new NativeMethods.WNDCLASS_I();
                bool ok = UnsafeNativeMethods.GetClassInfo(new HandleRef(null, UnsafeNativeMethods.GetModuleHandle(null)), windowClassName, wcls);
                if (ok && wcls.lpfnWndProc == NativeWindow.UserDefindowProc) {
    
                    // We can just reuse this class because we have marked it
                    // as being a nop in another domain.  All we need to do is call SetClassLong.
                    // Only one problem:  SetClassLong takes an HWND, which we don't have.  That leaves
                    // us with some tricky business. First, try this the easy way and see
                    // if we can simply unregister and re-register the class.  This might
                    // work because the other domain shutdown would have posted WM_CLOSE to all
                    // the windows of the class.
                    if (UnsafeNativeMethods.UnregisterClass(windowClassName, new HandleRef(null, UnsafeNativeMethods.GetModuleHandle(null)))) {
                        atom = UnsafeNativeMethods.RegisterClass(wndclass);
                        // If this fails, we will always raise the first err above.  No sense exposing our twiddling.
                    }
                    else {
                        // This is a little harder.  We cannot reuse the class because it is
                        // already in use.  We must create a new class.  We bump our domain qualifier
                        // here to account for this, so we only do this expensive search once for the
                        // domain.  
                        do {
                            domainQualifier++;
                            windowClassName = GetFullClassName(localClassName);
                            wndclass.lpszClassName = windowClassName;
                            atom = UnsafeNativeMethods.RegisterClass(wndclass);
                        } while (atom == 0 && Marshal.GetLastWin32Error() == NativeMethods.ERROR_CLASS_ALREADY_EXISTS);
                    }
                }
            }
    
            if (atom == 0) {
                windowProc = null;
                throw new Win32Exception(err);
            }
        }
        registered = true;
    }
    

    五、吓尿了!自己动手,丰衣足食

      看到微软的源码后,只能表示尿了,不可能继承Form实现自定义类名了,那么就自己动手,丰衣足食吧,还记得上面的C++/CLI代码吧,简单的加一些内容,就可以实现我们自定义窗口类名的愿望了,代码如下:  

      1. CustomForm.h  

    #pragma once
    
    #include <Windows.h>
    #include <vcclr.h>
    
    #define CUSTOM_CLASS_NAME  L"Starts2000.Window"
    
    namespace Starts2000
    {
    	namespace WindowsClassName
    	{
    		namespace Core
    		{
    			using namespace System;
    			using namespace System::Windows::Forms;
    			using namespace System::Runtime::InteropServices;
    
    			private delegate LRESULT WndProc(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam);
    
    			public ref class CustomForm
    			{
    			public:
    				static CustomForm();
    				CustomForm();
    				CustomForm(String ^caption);
    				~CustomForm();
    				void Create();
    				void Show();
    			private:
    				String ^_caption;
    				HWND _hWnd;
    				static GCHandle _windowProcHandle;
    				static LRESULT WndProc(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam);
    				static void ProcessExit(Object^ sender, EventArgs^ e);
    			};
    		}
    	}
    }
    

      2. CustomForm.cpp

    #include "CustomForm.h"
    
    namespace Starts2000
    {
    	namespace WindowsClassName
    	{
    		namespace Core
    		{
    			static CustomForm::CustomForm()
    			{
    				WNDCLASSEX wc;
    				Starts2000::WindowsClassName::Core::WndProc ^windowProc =
    					gcnew Starts2000::WindowsClassName::Core::WndProc(CustomForm::WndProc);
    				_windowProcHandle = GCHandle::Alloc(windowProc);
    
    				ZeroMemory(&wc, sizeof(WNDCLASSEX));
    				wc.cbSize = sizeof(WNDCLASSEX);
    				wc.style = CS_DBLCLKS;
    				wc.lpfnWndProc = reinterpret_cast<WNDPROC>(Marshal::GetFunctionPointerForDelegate(windowProc).ToPointer());
    				wc.hInstance = GetModuleHandle(NULL);
    				wc.hCursor = LoadCursor(NULL, IDC_ARROW);
    				wc.hbrBackground = (HBRUSH)COLOR_WINDOW; //(HBRUSH)GetStockObject(HOLLOW_BRUSH);
    				wc.lpszClassName = CUSTOM_CLASS_NAME;
    
    				ATOM classAtom = RegisterClassEx(&wc);
    				DWORD lastError = GetLastError();
    				if (classAtom == 0 && lastError != ERROR_CLASS_ALREADY_EXISTS)
    				{
    					throw gcnew ApplicationException("Register window class failed!");
    				}
    
    				System::AppDomain::CurrentDomain->ProcessExit += gcnew System::EventHandler(CustomForm::ProcessExit);
    			}
    
    			CustomForm::CustomForm() : _caption("Starts2000 Custom ClassName Window")
    			{
    			}
    
    			CustomForm::CustomForm(String ^caption) : _caption(caption)
    			{
    			}
    
    			CustomForm::~CustomForm()
    			{
    				if (_hWnd)
    				{
    					DestroyWindow(_hWnd);
    				}
    			}
    
    			void CustomForm::Create()
    			{
    				DWORD styleEx = 0x00050100;
    				DWORD style = 0x17cf0000;
    
    				pin_ptr<const wchar_t> caption = PtrToStringChars(_caption);
    
    				_hWnd = CreateWindowEx(styleEx, CUSTOM_CLASS_NAME, caption, style,
    					CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT,
    					NULL, NULL, GetModuleHandle(NULL), NULL);
    				if (_hWnd == NULL)
    				{
    					throw gcnew ApplicationException("Create window failed! Error code:" + GetLastError());
    				}
    			}
    
    			void CustomForm::Show()
    			{
    				if (_hWnd)
    				{
    					ShowWindow(_hWnd, SW_NORMAL);
    				}
    			}
    
    			LRESULT CustomForm::WndProc(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam)
    			{
    				System::Windows::Forms::Message message = System::Windows::Forms::Message::Create((IntPtr)hWnd,
    					(int)msg, (IntPtr)((void*)wParam), (IntPtr)((void*)lParam));
    				System::Diagnostics::Debug::WriteLine(message.ToString());
    				
    				if (msg == WM_DESTROY)
    				{
    					PostQuitMessage(0);
    					return 0;
    				}
    
    				return DefWindowProc(hWnd, msg, wParam, lParam);
    			}
    
    			void CustomForm::ProcessExit(Object^ sender, EventArgs^ e)
    			{
    				UnregisterClass(CUSTOM_CLASS_NAME, GetModuleHandle(NULL));
    				if (CustomForm::_windowProcHandle.IsAllocated)
    				{
    					CustomForm::_windowProcHandle.Free();
    				}
    			}
    		}
    	}
    }
    

      最后仍然用我们熟悉的C#来调用:

    using System;
    using System.Windows.Forms;
    using Starts2000.WindowsClassName.Core;
    
    namespace Starts2000.WindowClassName.Demo
    {
        static class Program
        {
            /// <summary>
            /// 应用程序的主入口点。
            /// </summary>
            [STAThread]
            static void Main()
            {
                //Application.EnableVisualStyles();
                //Application.SetCompatibleTextRenderingDefault(false);
                //Application.Run(new FormMain());
    
                CustomForm form = new CustomForm();
                form.Create();
                form.Show();
                Application.Run();
            }
        }
    }
    

      编译,运行,拿出神器SPY++看一看:

      目标终于达成。

      最后,所有代码的下载(项目使用的是VS2013编译、调试,不保证其他版本VS能正常编译):猛击我

  • 相关阅读:
    Pycharm简单使用教程
    【Jenkins学习】【第二节】 jenkins构建触发器定时任务
    Docker之从零开始制作docker镜像
    手机APP自动化环境搭建
    格式字符详解
    Bash Shell之内建命令和保留字
    asp.net 实现后台异步处理的方式
    Spring3.2.0 之后各个版本完整包下载地址
    Oracle的rollup、cube、grouping sets函数
    C# 委托类型及使用
  • 原文地址:https://www.cnblogs.com/Starts_2000/p/Winform-Custom-ClassName.html
Copyright © 2020-2023  润新知