.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能正常编译):猛击我

原文地址:https://www.cnblogs.com/Starts_2000/p/Winform-Custom-ClassName.html