你的位置:首页 > ASP.net教程

[ASP.net教程].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能正常编译):猛击我。