涉及的几项技术:
- Aero 效果的实现
- ListView 控件的系统风格化
- 控件默认使用 GDI+ 绘制文本
- 异步数据传输
- 全局热键的使用
- 在 .NET 中使用回调
一、Aero效果的实现
关键 API:
窗体的扩展样式必须带有 WS_EX_LAYERED
,并部分(或全部)透明,因为 DWM 只会在窗体透明的部分进行渲染。
Declare Function DwmExtendFrameIntoClientArea Lib "dwmapi.dll" (ByVal hWnd As IntPtr, ByRef margin As MARGINS) As Integer
函数讲解:将 DWM 的渲染范围向窗体内拓展。hWnd
为窗体句柄,margin
为 MARGINS
结构,表示拓展的量。MARGINS
和 RECT
本质上相同,但是 MARGINS
表示向内范围,RECT
表示向外范围。
Declare Function DwmEnableBlurBehindWindow Lib "dwmapi.dll" (ByVal hWnd As IntPtr, ByRef pBlurBehind As DWM_BLURBEHIND) As Integer
函数讲解:启用窗口的 DWM 模糊渲染。hWnd
为窗体句柄,pBlurBehind
为 DWM_BLURBEHIND
结构,指明如何进行渲染。
Public Function DwmIsCompositionEnabled Lib "dwmapi.dll" (ByRef enabledPtr As Integer) As Integer
函数讲解:判断DWM 渲染是否已经启用。enablePtr
为是否已经开启的判断变量(注意传址的方式),enabledPtr
返回1为已经启用,0为未启用。
关键常数:
'是否只是渲染区域
Public Const DWM_BB_BLURREGION As Integer = &H2
'是否启用渲染
Public Const DWM_BB_ENABLE As Integer = &H1
'最大化窗口时是否渲染
Public Const DWM_BB_TRANSITIONONMAXIMIZED As Integer = &H4
关键结构:.
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
结构讲解:四个数值分别是四周向内拓展的大小(以像素为单位)。
Public Structure DWM_BLURBEHIND
Public dwFlags As Integer
Public fEnable As Integer
Public hRgnBlur As Integer
Public fTransitionOnMaximized As Integer
End Structure
结构讲解:dwFlags
是 DWM_BB_BLURREGION
、DWM_BB_ENABLE
、DWM_BB_TRANSITIONONMAXIMIZED
的组合。若指定一项,相应的值必须置为非零。其中 hRgnBlur
为目标渲染区域句柄(似乎没用),fEnable
和 fTransitionOnMaximized
置为1为打开,0为关闭。
使用 Windows.Forms.TransparencyKey
可以轻易实现透明效果。
综合使用效果如图:
二、ListView控件的系统风格化
关键函数:
Public Function SetWindowTheme Lib "uxtheme.dll" (ByVal hWnd As IntPtr, ByVal textSubAppName As String, ByVal textSubIdList As String) As Integer
函数讲解:该函数用于开启或关闭或改变某窗口(及子窗口)的窗口主题。hWnd
为窗口句柄,textSubAppName
为主程序样式名称,textSubIdList
为子样式名称。
函数使用:若 textSubAppName
和 textSubIdList
使用 ByVal
传递整数零则可以启用一个窗口的主题,若使用 ByRef
传递空格字符(Chr(32)
)则会停用一个窗口的主题(停用后窗口类似 Windows 98 中的窗口)。
使用 SetWindowTheme(ListViewCtrl.Handle, "Explorer", Nothing)
可以启用 ListView、TreeView 的资源管理器效果:
上面是使用了 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)
。defaultValue
为 True
时所有支持 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 类的 DownloadDataAsync
、UploadDataAsync
等函数进行异步数据传输。这里以 DownloadDataAsync
函数为例。
DownloadDataAysnc
的好处在于不占用系统时间,应用程序不需要等待数据返回才响应,之间可以完成其他工作。如果使用 DownloadData
函数,则在数据接收到之前无法进行其他操作。
那么控制权交给了其他函数,如何确定接收过程呢?
使用 AddHandler
语句注册回调函数。AddHandler
使用方法如下:AddHandler ControlName.EventName, AddressOf CallbackProc
。CallbackProc
的声明要与 ControlName.EventName
的声明参数一致,否则会造成“签名不一致”错误。实际上,AddHandler
是注册隐式委托的一个有效方法。关于委托请见第六点。
比如,注册 WebClient 的 DownloadDataComplete
事件函数例子:
Public Sub DDACallback(ByVal sender AsObject, ByVal e As System.Net.DownloadDataCompletedEventArgs)
然后在里面处理就行了。
五、全局热键的使用
关键函数:
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_CONTROL
,MOD_ALT
,MOD_SHIFT
,MOD_WIN
),nVK
为虚拟键码。注册热键之后按下热键系统会将 WM_HOTKEY
发往 hWnd
指定的窗口的消息循环,wParam
为热键 ID,lParam
的高位为虚拟键码,低位为修饰键值。
使用方法:重写窗口消息处理函数。
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
自然之后要解除注册:
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++:
int doSomething(int, int, float);
int doSomething(int a, int b, float c) {...}
VB .NET:
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 说“委托是一种类型”。那么,我们就可以用委托进行回调。
声明一个影子:
Delegate Function EnumWindowsProc(ByVal hWnd As IntPtr, ByVal lParam As Integer) As Integer
此时,EnumWindowsProc
已经成为一个“委托”类型。在函数中可以使用这种类型,可以且仅可以接受 AdressOf
对正确函数实体的返回。
API 函数也要重新声明:
Declare Function EnumWindows Lib "user32.dll" (ByVal EnumProc As EnumWindowsProc, ByVal lParam As Integer) As Integer
然后接下来声明真正的函数:
Function EnumWindowProcRealizer(ByVal Handle As IntPtr, ByVal lParam As Integer) As Integer
SetWindowText(Handle, "窗口标题")
Return 1
End Function
接着进行调用:
EnumWindows(AddressOf EnumWindowProcRealizer, 0)
这样进行的调用就会传到实际的函数 EnumWindowProcRealizer
。
另外,和 C++ 里的影子函数一样,实际的函数和形式函数要有相同的参数、参数类型和参数顺序(本质上是要保证堆栈一致),否则会引发“签名不匹配”的错误。