Zinc 技术披露

涉及的几项技术:

  1. Aero 效果的实现
  2. ListView 控件的系统风格化
  3. 控件默认使用 GDI+ 绘制文本
  4. 异步数据传输
  5. 全局热键的使用
  6. 在 .NET 中使用回调

一、Aero效果的实现

关键 API:

窗体的扩展样式必须带有 WS_EX_LAYERED,并部分(或全部)透明,因为 DWM 只会在窗体透明的部分进行渲染。

1
Declare Function DwmExtendFrameIntoClientArea Lib "dwmapi.dll" (ByVal hWnd As IntPtr, ByRef margin As MARGINS) As Integer

函数讲解:将 DWM 的渲染范围向窗体内拓展。hWnd 为窗体句柄,marginMARGINS 结构,表示拓展的量。MARGINSRECT 本质上相同,但是 MARGINS 表示向内范围,RECT 表示向外范围。

1
Declare Function DwmEnableBlurBehindWindow Lib "dwmapi.dll" (ByVal hWnd As IntPtr, ByRef pBlurBehind As DWM_BLURBEHIND) As Integer

函数讲解:启用窗口的 DWM 模糊渲染。hWnd 为窗体句柄,pBlurBehindDWM_BLURBEHIND 结构,指明如何进行渲染。

1
Public Function DwmIsCompositionEnabled Lib "dwmapi.dll" (ByRef enabledPtr As Integer) As Integer

函数讲解:判断DWM 渲染是否已经启用。enablePtr 为是否已经开启的判断变量(注意传址的方式),enabledPtr 返回1为已经启用,0为未启用。

关键常数:

1
2
3
4
5
6
'是否只是渲染区域
Public Const DWM_BB_BLURREGION As Integer = &H2
'是否启用渲染
Public Const DWM_BB_ENABLE As Integer = &H1
'最大化窗口时是否渲染
Public Const DWM_BB_TRANSITIONONMAXIMIZED As Integer = &H4

关键结构:.

1
2
3
4
5
6
Public Structure MARGINS
Public m_Left As Integer
Public m_Right As Integer
Public m_Top As Integer
Public m_Bottom As Integer
End Structure

结构讲解:四个数值分别是四周向内拓展的大小(以像素为单位)。

1
2
3
4
5
6
Public Structure DWM_BLURBEHIND
Public dwFlags As Integer
Public fEnable As Integer
Public hRgnBlur As Integer
Public fTransitionOnMaximized As Integer
End Structure

结构讲解:dwFlagsDWM_BB_BLURREGIONDWM_BB_ENABLEDWM_BB_TRANSITIONONMAXIMIZED 的组合。若指定一项,相应的值必须置为非零。其中 hRgnBlur 为目标渲染区域句柄(似乎没用),fEnablefTransitionOnMaximized 置为1为打开,0为关闭。

使用 Windows.Forms.TransparencyKey 可以轻易实现透明效果。

综合使用效果如图:

Z1


二、ListView控件的系统风格化

关键函数:

1
Public Function SetWindowTheme Lib "uxtheme.dll" (ByVal hWnd As IntPtr, ByVal textSubAppName As String, ByVal textSubIdList As String) As Integer

函数讲解:该函数用于开启或关闭或改变某窗口(及子窗口)的窗口主题。hWnd 为窗口句柄,textSubAppName 为主程序样式名称,textSubIdList 为子样式名称。

函数使用:若 textSubAppNametextSubIdList 使用 ByVal 传递整数零则可以启用一个窗口的主题,若使用 ByRef 传递空格字符(Chr(32))则会停用一个窗口的主题(停用后窗口类似 Windows 98 中的窗口)。

使用 SetWindowTheme(ListViewCtrl.Handle, "Explorer", Nothing) 可以启用 ListView、TreeView 的资源管理器效果:

Z2

上面是使用了 SetWindowTheme 函数之后的效果,下面是未使用的效果。


三、控件默认使用GDI+ 绘制文本

为什么要用 GDI+ 绘制文本?这是因为,如果启用了 Aero 效果,则窗体的所有颜色将会使用ARGB 方式参与计算。普通的 GDI 文本绘制函数如 DrawText 等绘制的文本会造成很奇特的效果,严重影响文本可读性。由于 GDI+ 也是使用 ARGB 方式计算颜色,就可以在使用 Aero 效果的窗体上绘制正常的文本。

在 Visual Studio .NET 2005(.NET Framework 版本 2.0)及以上的版本中,可以使用两种方式启用控件的 GDI+ 文本绘制。

1、使用 Application.SetCompatibleTextRenderingDefault

原型:SetCompatibleTextRenderingDefault(defaultValue As Boolean)defaultValueTrue 时所有支持 GDI+ 绘制文本的控件都启用 GDI+ 绘制,为 False 时使用 GDI 绘制。默认值为 False

在应用的时候会有一个问题。该函数需要在创建第一个窗口前调用,否则引发异常 InvalidOperationException。如果启用应用程序框架,则无法在第一个窗体加载之前调用函数。

解决方法如下:取消应用程序框架,使用 Sub Main() 启动,调用 Application.SetCompatibleTextRenderingDefault(True) 之后在结束之前调用 Application.Run(MainForm),其中 MainForm 为主窗体名称。

2、使用控件的 UseCompatibleTextRendering 属性。

属性为 True 时所有支持 GDI+ 绘制文本的控件都启用 GDI+ 绘制,为 False 时使用 GDI 绘制。默认值为 False

支持 GDI+ 文本绘制的控件有:Label,LinkLabel,Button,CheckBox,RadioButton,CheckedListBox,GroupBox,PropertyGrid。


四、异步数据传输

使用 WebClient 类的 DownloadDataAsyncUploadDataAsync 等函数进行异步数据传输。这里以 DownloadDataAsync 函数为例。

DownloadDataAysnc 的好处在于不占用系统时间,应用程序不需要等待数据返回才响应,之间可以完成其他工作。如果使用 DownloadData 函数,则在数据接收到之前无法进行其他操作。

那么控制权交给了其他函数,如何确定接收过程呢?

使用 AddHandler 语句注册回调函数。AddHandler 使用方法如下:AddHandler ControlName.EventName, AddressOf CallbackProcCallbackProc 的声明要与 ControlName.EventName 的声明参数一致,否则会造成“签名不一致”错误。实际上,AddHandler 是注册隐式委托的一个有效方法。关于委托请见第六点。

比如,注册 WebClient 的 DownloadDataComplete 事件函数例子:

1
Public Sub DDACallback(ByVal sender AsObject, ByVal e As System.Net.DownloadDataCompletedEventArgs)

然后在里面处理就行了。


五、全局热键的使用

关键函数:

1
Public Function RegisterHotKey Lib "user32.dll" (ByVal hWnd As IntPtr, ByVal nHotkeyID As Integer, ByVal fsModifiers As Integer, ByVal nVK As Integer) As Integer

函数讲解:hWnd 为窗口句柄,nHotkeyID 为热键 ID(应用程序范围为 &H0000~&HBFFF,DLL 范围为 &HC000~&HFFFF),fsModifiers 为修饰键(MOD_CONTROLMOD_ALTMOD_SHIFTMOD_WIN),nVK 为虚拟键码。注册热键之后按下热键系统会将 WM_HOTKEY 发往 hWnd 指定的窗口的消息循环,wParam 为热键 ID,lParam 的高位为虚拟键码,低位为修饰键值。

使用方法:重写窗口消息处理函数。

1
2
3
4
5
6
7
8
Protected Overrides Sub WndProc (ByRef m As System.Windows.Forms.Message)
If m.Msg = WM_HOTKEY Then
If m.WParam = ID_HOTKEY Then
'这里做你想做的。
End If
End If
MyBase.WndProc(m)
End Sub

自然之后要解除注册:

1
Public Function UnregisterHotKey Lib "user32.dll" (ByVal hWnd As IntPtr, ByVal nHotkeyID As Integer) As Integer

函数讲解:hWnd 为窗口句柄,nHotkeyID 为已注册的热键 ID。


六、在 .NET 中使用回调

VB6 中使用回调很简单,AddressOf 就行了。比如:EnumWindows(AddressOf EWProc, 0)。.NET 可是宣称有安全性的,怎么会直接采用这种可能引发堆栈错误(比如参数不匹配)的方式呢?所以在 .NET 中,AddressOf 已经成为了创建显式委托的语句。如果直接对一个声明为 lpFunc As Integer 的函数传 AdressOf ProcName 的话,则会引发异常。

那么 Windows 中这么多的回调该怎么使用呢?

题外话:其实一开始我也没有理解 .NET 中所谓“委托”为何物。为什么要选用“委托”一词呢?这个词在中文中可是一个动词啊……看了 MSDN Help Library 的示例之后我终于理解了,原来“委托”就是 C++ 中所谓的影子函数啊!

对比一下:

C++:

1
2
3
int doSomething(int, int, float);

int doSomething(int a, int b, float c) {...}

VB .NET:

1
2
3
4
5
Delegate Function DoSomethingDelegate(ByVal X As Integer, ByVal Y As Integer, ByVal Z As Integer) As Integer

Function DoSomething(ByVal A As Integer, ByVal B As Integer, ByVal C As Integer) As Integer
...
End Function

怎么样?很像吧!

而且 MSDN Help Library 说“委托是一种类型”。那么,我们就可以用委托进行回调。

声明一个影子:

1
Delegate Function EnumWindowsProc(ByVal hWnd As IntPtr, ByVal lParam As Integer) As Integer

此时,EnumWindowsProc 已经成为一个“委托”类型。在函数中可以使用这种类型,可以且仅可以接受 AdressOf 对正确函数实体的返回。

API 函数也要重新声明:

1
Declare Function EnumWindows Lib "user32.dll" (ByVal EnumProc As EnumWindowsProc, ByVal lParam As Integer) As Integer

然后接下来声明真正的函数:

1
2
3
4
Function EnumWindowProcRealizer(ByVal Handle As IntPtr, ByVal lParam As Integer) As Integer
SetWindowText(Handle, "窗口标题")
Return 1
End Function

接着进行调用:

1
EnumWindows(AddressOf EnumWindowProcRealizer, 0)

这样进行的调用就会传到实际的函数 EnumWindowProcRealizer

另外,和 C++ 里的影子函数一样,实际的函数和形式函数要有相同的参数、参数类型和参数顺序(本质上是要保证堆栈一致),否则会引发“签名不匹配”的错误。

分享到 评论