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