研究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. 设计资源:三个精美APP原型例子下载

    原型设计是整个产品生产过程中不可或缺的一环,无论你是移动端UI设计师或是网页设计师,原型设计都会让整个设计过程更加轻松.原型是产品概念的具象化,它让每个项目参与者都能查看并提出意见以便在产品发布前日臻 ...

  2. jqueryAjax的使用

    1. 导入等下我们要使用的文件AjaxMsgHelper.cs和DataHelper.cs他们的代码如下 using System;using System.Collections.Generic;u ...

  3. failed creating java jvm.dll

    启动tomcat服务时出现错误failed creating java jvm.dll的解决办法 把jdk\bin目录下的msvcr71.dll 或msvcr100.dll 复制到tomcat安装目录 ...

  4. window.load 和$(document).ready() 、window.load和body onload区别

    1.执行时间 window.onload必须等到页面内包括图片的所有元素加载完毕后才能执行. $(document).ready()是DOM结构绘制完毕后就执行,不必等到加载完毕.2.编写个数不同 w ...

  5. 2018.10.13 bzoj1070: [SCOI2007]修车(费用流)

    传送门 费用流经典题目. 自我感觉跟TheWindy′sThe Windy'sTheWindy′s很像. 利用费用提前计算的思想来建图就行了. 代码: #include<bits/stdc++. ...

  6. 2018.10.08 NOIP模拟 序列(主席树)

    传送门 T2防ak题? 其实也不是很难(考试时sb了). 直接变形一下求出区间长度在[l2,r2][l2,r2][l2,r2]之间,中位数≤l1−1\le l1-1≤l1−1的区间数,和区间长度在[l ...

  7. 2018.09.05 bzoj1010: [HNOI2008]玩具装箱toy(斜率优化dp)

    传送门 一道经典的斜率优化dp. 推式子ing... 令f[i]表示装前i个玩具的最优代价. 然后用老套路. 我们只考虑把第j+1" role="presentation" ...

  8. UVa 11294 Wedding (TwoSat)

    题意:有 n-1 对夫妻参加一个婚宴,所有人都坐在一个长长的餐桌左侧或者右侧,新郎和新娘面做面坐在桌子的两侧.由于新娘的头饰很复杂,她无法看到和她坐在同一侧餐桌的人,只能看到对面餐桌的人.任意一对夫妻 ...

  9. cmd运行命令

    winver检查Windows版本 dxdiag检查DirectX信息 mem.exe显示内存使用情况 Sndvol32音量控制程序 sfc.exe系统文件检查器 gpedit.msc 组策略 reg ...

  10. 201709012工作日记--activity与service的通信机制

    service生命周期 Service主要包含本地类和远程类. Service不是Thread,Service 是android的一种机制,当它运行的时候如果是Local Service,那么对应的 ...