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

.NET Windows Form 改变窗体类名(Class Name)有多难?的更多相关文章

  1. 变不可能为可能 - .NET Windows Form 改变窗体类名(Class Name)有多难?续篇

    发布<.NET Windows Form 改变窗体类名(Class Name)有多难?>转眼大半年过去了,要不是在前几天有园友对这篇文章进行评论,基本上已经很少关注它了,毕竟那只是一个解惑 ...

  2. windows form (窗体) 之间传值小结

    windows form (窗体) 之间传值小结   windows form (窗体) 之间传值小结 在windows form之间传值,我总结了有四个方法:全局变量.属性.窗体构造函数和deleg ...

  3. windows sdk编程禁止改变窗体大小

    #include <windows.h> /*消息处理函数声明*/ HRESULT CALLBACK WindowProc(HWND hwnd, UINT message, WPARAM ...

  4. 如何用Web技术开发Windows Form应用

    现在H5很热,很多互联网公司的产品都采用混合编程,其中各个平台客户端的“壳”为原生控件,但是内容很多都是Web网页,因此可以做出很多炫酷的效果.随着Node.js和Ionic等框架的出现,现在感觉Ja ...

  5. PyQt通过resize改变窗体大小时ListWidget显示异常

    前几天开始的pygame音乐播放器Doco,做的差不多了,上午做到了歌词显示和搜索页面.遇到bug,即通过resize改变ui大小时ListWidget显示异常 #目的: 增加一部分窗口用来显示歌词和 ...

  6. windows form参数传递过程

    三.windows form参数传递过程 在Windows 程序设计中参数的传递,同样也是非常的重要的. 这里主要是通过带有参数的构造函数来实现的, 说明:Form1为主窗体,包含控件:文本框text ...

  7. 【译】.NET 5. 0 中 Windows Form 的新特性

    自从 Windows Form 在 2018 年底开源并移植到 .NET Core 以来,团队和我们的外部贡献者都在忙于修复旧的漏洞和添加新功能.在这篇文章中,我们将讨论 .NET 5.0 中 Win ...

  8. WPF实现无边框窗体拖拽右下角▲ 改变窗体大小【framwork4.0】 谢谢大家关注

    效果图:(右下角拖拽改变窗体大小) 第一步:添加xaml代码: <Border Name="ResizeBottomRight" MouseMove="Resize ...

  9. Windows Form 中快捷键设置

    在Windows Form程序中使用带下划线的快捷键只需要进行设置: 就能够工作.

随机推荐

  1. 通过BeanShell获取UUID并将参数传递给Jmeter

    有些HTTPS请求报文的报文体中包含由客户端生成的UUID,在用Jmeter做接口自动化测试的时候,因为越过了客户端,直接向服务器端发送报文,所以,需要在Jmeter中通过beanshell获取UUI ...

  2. 13.11.20 jquery 核心 siblings() 获得同类(不包含自己)循环所有,

    jquery 核心1.选择器,2. 创建dom 元素 3. jquery 执行时 4. 延迟执行 5. 循环 6. 计算长度.7.8 获得选择器和所在节点 9. 获得下标 10. 元素存放数据  11 ...

  3. IntelliJ IDEA 2017版 编译器使用学习笔记(二) (图文详尽版);IDE快捷键使用

    补充介绍IntellJ 介绍主菜单功能及相关用途: File -------------> 对文件进行操作 Edit ------------> 对文本进行操作 View -------- ...

  4. 基于SceneControl单击查询功能的实现

    private void HandleIdentify_MouseDown(object sender, ISceneControlEvents_OnMouseDownEvent e) { this. ...

  5. MemCache 安全使用原则(自己整理,仅供参考)

    // 触发器:作用是检查缓存时否可用(不用add做触发器),避免缓存不可用时add返回false按非首次登陆处理,导致不能增加成长值. memCachedClient.get(memCacheKey) ...

  6. 新建maven web 项目后,出现的小问题

    问题一:Description Resource Path Location TypeCannot change version of project facet Dynamic Web Module ...

  7. 11.字符串{a,b}的幂集[回溯递归]

    我一直在想着这个事,早晨起来五六点,躺在床上冥想.突然悟解了,真如某些书上写的,大道不过三言两语,说破一文不值.还是按照老方法,把问题最大程度的精简,现在求集合A={a,b}的幂集,只有两个元素,应该 ...

  8. 201709013工作日记--Android消息机制HandlerThread

    1.首先来看一个常规的handler用法: 在主线程中建立一个handler: private Handler mHandler = new Handler() { @Override public ...

  9. 点云库PCL学习

    1. 点云的提取 点云的获取:RGBD获取 点云的获取:图像匹配获取(通过摄影测量提取点云数据) 点云的获取:三维激光扫描仪 2. PCL简介 PCL是Point Cloud Library的简称,是 ...

  10. 75. Sort Colors(颜色排序) from LeetCode

      75. Sort Colors   给定一个具有红色,白色或蓝色的n个对象的数组,将它们就地 排序,使相同颜色的对象相邻,颜色顺序为红色,白色和蓝色. 这里,我们将使用整数0,1和2分别表示红色, ...