回 帖 发 新 帖 刷新版面

主题:VB编程实现XP风格之终结篇

VB编程实现XP风格之终结篇

                                            南京 宋陈三  

                               

    (本文发表于《软件报》2006年第17期)网上讨论编程实现XP风格已经很久了,但对于VB编程实现XP风格,却终没有一个完美的解决方案。笔者通过N个日夜的刻苦钻研终于揭开其中奥秘。下面分为三个方面与大家共享之。

一.用manifest文件实现XP风格

正常情况下,在Windows XP系统中,用VB6开发的应用程序只有窗口标题条具备XP风格,窗体上的按钮、文本框等控件仍然显示Windows传统风格。如图1所示:
[img]http://www.asanscape.com/vbxpimg/std.jpg[/img]

通过查阅MSDN里的Visual Style章节知道,Windows XP通过Comctl32.dll(版本6)来加载具备XP风格的组件,应用程序则通过一个XML资源文件来通知系统来做这些。XML文件的内容如下:

<?xml version="1.0" encoding="UTF-8" standalone="yes"?>  
  <assembly xmlns="urn:schemas-microsoft-com:asm.v1" manifestVersion="1.0">  
  <assemblyIdentity  
   name="XP style manifest"   
   processorArchitecture="x86"  
   version="1.0.0.0"  
   type="win32"/>   
  <dependency>   
   <dependentAssembly>   
   <assemblyIdentity  
   type="win32"   
   name="Microsoft.Windows.Common-Controls"   
   version="6.0.0.0"   
   processorArchitecture="x86"   
   publicKeyToken="6595b64144ccf1df"   
   language="*"   
   />   
   </dependentAssembly>   
  </dependency>   
  </assembly>

假设你最终编译的程序名是abc.exe,工作目录是d:\vbxp。复制上述XML内容并保存为文本文件。然后将该文件改名为abc.exe.manifest(注意.txt扩展名要去掉)。在VB程序中,我们要在所有窗体加载之前调用InitCommonControlsEx函数从comctl32.dll(版本6)中对组件类进行初始化。API函数InitCommonControlsEx及相关常数、数据类型的声明如下:

Private Declare Function InitCommonControlsEx Lib "comctl32.dll" _        (iccex As tagInitCommonControlsEx) As Boolean
Private Type tagInitCommonControlsEx        
    lngSize As Long   
     lngICC As Long
End Type
Private Const ICC_USEREX_CLASSES = &H200    
    这里我们编写一个函数封装初始化操作:
Public Function InitCommonControlsVB() As Boolean
        On Error Resume Next  
      Dim iccex As tagInitCommonControlsEx   
     With iccex       
           .lngSize = LenB(iccex)  
           .lngICC = ICC_USEREX_CLASSES   
     End With   
     InitCommonControlsEx iccex   
     InitCommonControlsVB = (Err.Number = 0)    
    On Error Goto 0
End Function
    注意初始化动作必须在所有窗体加载前完成,所以要把相关语句放到Sub Main()中,并设置工程从Sub Main()启动。代码如下:

Sub Main()

     InitCommonControlsVB

     Form1.Show

End Sub

至此,你编译后的abc.exe将具备XP风格,如图2所示:
[img]http://www.asanscape.com/vbxpimg/xpstyle.jpg[/img]

二.在VB设计时或运行时实现XP风格

读者可能已经注意到,用上述方法实现的XP风格必须在编译成exe文件后才可以显示出来。那么是否可以实现在设计时或运行时的“所见即所得”呢。答案是肯定的。把上面提到的abc.exe.manifest文件改名为vb6.exe.manifest并拷贝到vb6的安装目录下(如C:\Program Files\Microsoft Visual Studio\VB98),然后运行VB6,向窗体上画几个控件试试。再按F5运行程序看看效果。我们可以发现,此时的控件已经具备了XP风格。如图3所示。
[img]http://www.asanscape.com/vbxpimg/vbxp.jpg[/img]

经笔者测试,此时VB6中的颜色选择框会显示不正常,但你仍然可以通过输入数值来改变颜色。

读者在看完这一部分以后往往会触类旁通,将其他程序的安装目录中也放入manifest文件来使其具备XP风格。但要注意此法并非适用于所有程序。如果这个程序没有对引用组件的初始化操作,那么放入manifest文件后将可能导致该程序无法运行。在VC编译的程序中,MFC在注册窗口类时会调用_AfxInitCommonControls函数来加载comctl32.dll(版本6)。  而VB中的初始化操作需要程序员额外声明,所以要特别注意这一点。

三.让VB实现XP风格“无负担”

用上述方法实现XP风格还有一个遗憾,就是总有一个manifest文件如影随形。VB的程序员们为了使应用程序保持“绿色”而尽量少用ActiveX控件,却为实现XP风格而带上这个累赘的家伙。MSDN告诉VC程序员们可以把manifest文件编译到文件资源中,而VB程序员们照葫芦画个瓢时却得到程序无法运行的结果。

问题出在哪里?接着往下看。

把manifest文件编译进文件资源必须保证其资源ID为CREATEPROCESS_MANIFEST_RESOURCE_ID (即数值1),资源的类型为RT_MANIFEST(即数值24),而此类资源无法用VB资源编辑器直接编译,这是其一;其二,XML文件的第一句就提醒我们”encoding=UTF-8”,所以要把manifest保存为UTF-8编码格式。这里我们要让manifest文件的总字节数能够被4整除。例如你复制XML后得到的manifest文件是690字节,那么你就在文首或文尾补两个空格,使之成为692字节。这样得到的manifest文件就可以编译进资源了。如果不满足此要求,编译出来的文件将会出错(而VC中并无此额外要求)。具体编译方法如下:

在abc.exe.manifest文件的相同目录下新建一个文本文件,输入下行代码:

1 24 abc.exe.manifest

保存后将文件改名为xp.rc。如果您的电脑上装有VC,则直接双击xp.rc文件用VC运行之,然后另存为xp.res(这个另存为不简单,还具有编译功能)。如果没装VC,则从DOS下进入VB安装目录的Wizard目录(如C:\Program Files\Microsoft Visual Studio\VB98\Wizards),在提示符处输入下行命令:

rc /r d:\vbxp\xp.rc

这样,资源文件xp.res就编译好了。打开VB工程文件abc.vbp,按Ctrl +D加入该资源文件,编译生成abcd.exe(这里就不用abc.exe为文件名了,以免混淆),双击运行……。哈哈。大功告成。

最后说一下需要注意的地方。在Frame内的单选按钮运行后显示为黑色,解决方法是把它们先放入一个PictureBox内,并设置PictureBox的BorderStyle属性为2-None,然后再连同PictureBox一起剪切到一个Frame中。

本文提及的方法不仅可以使应用程序内部的控件具备XP风格,也可以使其调用的消息框及ActiveX控件具备XP风格。但要注意,在使用工具栏、进度条等ActiveX控件时要引用Microsoft Windows Common Controls 5.0 版本,否则不能使控件具备XP风格。

为了网友们的方便,我把manifest文件、rc文件、res文件和工程示例放在这里http://www.asanscape.com/vbxp.zip。本文内容在Windows XP+VB6中调试通过。欢迎加入VB技术讨论QQ群12960265,和众多VB爱好者共同进步。

  

回复列表 (共32个回复)

21 楼

我也觉得VB那个风格太旧了,应该换个新的了,才好撒!

22 楼

过了一年又一年,啊,一生只为这一天;让血脉再相连......擦干心中的血和泪痕...留住我们的根....

23 楼

不是伤大家的心,这个与VB6.0有些冲突。
表现为Text控件中文取值不准确  .SelLength
                              .SelStart     可以试试。
更过分的一般是工程发布后才会体现出来,我真实遇到过,最后发布后才查出Bug最后告诉人家删除安装目录的 .manifest 大家留神一下就行了。

24 楼

我是楼主~~.能把文件发给我看看吗?

25 楼

~~~~~~~~~

26 楼

发这个没别的意思。我也是VB爱好者。从这个VB区长大的。只是借此寻求解决方法.

27 楼

~

28 楼

感谢linxuanxu兄台.我试验之下果真如此.但随即看到了nahuhcnat兄台的解决方案,试验之下,确实完美解决.nahuhcnat兄台的解决方法是用API来代替VB的函数/方法.代码如下:
Option Explicit

Private Const EM_GETSEL = &HB0
Private Declare Function SendMessage Lib "user32" Alias "SendMessageA" (ByVal hwnd As Long, ByVal wMsg As Long, ByVal wParam As Long, lParam As Any) As Long

Private Declare Function InitCommonControls Lib "comctl32.dll" () As Long

Private Sub Form_Initialize()
    InitCommonControls
End Sub

Private Sub Command1_Click()
    Dim nStart  As Long, nEnd As Long
    SendMessage Text1.hwnd, EM_GETSEL, VarPtr(nStart), nEnd
    Debug.Print "开始位置:" & nStart, "结束位置:" & nEnd
End Sub

29 楼

~

30 楼

呵呵是啊,这个解决方案确实不错~

我来回复

您尚未登录,请登录后再回复。点此登录或注册