关于换肤、子类化,征求解决方案

(转我自己在论坛上发的老帖2004-06-25 14:52:52 在 VC/MFC / 界面 提问)

 

对于应用程序的换肤及子类化。下面是我尝试过一些方法,   
  以在CAboutDlg中子类化其中的Button为例:   
  第一种:直接用现成的类   
  1.自己写一个类class   CButtonXP   :   public   CButton{/*...*/}   
      用MessageMap处理感兴趣的消息。   
  2.用CButtonXP代替CButton来声明变量m_btn;   
    
  3.在void   CAboutDlg::DoDataExchange(CDataExchange*   pDX)中加上一句:   
      DDX_Control(pDX,   IDB_BUTTON1,   m_edit);   
      或者在InitDialog()中加上   
      m_btn.SubclassDlgItem(IDB_BUTTON1,   this);   
      这两种效果差不多的。   
    
  第二种:在Hook中使用现成的类。   
  1.自己写一个类class   CButtonXP   :   public   CButton{/*...*/}   
      用MessageMap处理感兴趣的消息。   
  2,用g_hWndProcHook   =     ::SetWindowsHookEx(WH_CALLWNDPROC,   
                      WndProcHook,   NULL,   ::GetCurrentThreadId());安装一个钩子   
  3.   在WndProcHook中处理窗口创建和销毁的消息   
  LRESULT   CALLBACK   WndProcHook(int   code,   WPARAM   wParam,   LPARAM   lParam)   
  {   
          if   (code   ==   HC_ACTION)   
          {   
                  switch   (((CWPSTRUCT*)   lParam)->message)   
                  {   
                          case   WM_CREATE:   
                                  BeginSubclassing(((CWPSTRUCT*)   lParam)->hwnd);   
                                  break;   
    
                          case   WM_NCDESTROY:   
                                  //   TODO:   clear   subclass   info.   
                                  EndSubclassing(((CWPSTRUCT*)   lParam)->hwnd);   
                                  break;   
    
                          default:   
                                  break;   
                  }   
          }   
    
          return   CallNextHookEx(g_hWndProcHook,   code,   wParam,   lParam);   
  }   
  4.   在BeginSubclassing中用GetClassName得到类名,例如"Button",然后用CButtonXP类进行子类化.   
  CButtonXP   pButton   =   new   CButtonXP     
  VERIFY(pButton   ->SubclassWindow(hWnd));   
    
  第三种   在Hook中使用窗口过程。   
  1.   自己写一个按钮的窗口过程   
  WNDPROC   oldProc;   
  LRESULT   CALLBACK   ProcButton(   
          HWND         hWnd,   
          UINT         uMsg,   
          WPARAM     wParam,   
          LPARAM     lParam)   
  {   
          ASSERT(oldProc   !=   0);   
          if   (oldProc   ==   0)   return   TRUE;   
          switch   (uMsg)   
          {   
                  case   WM_ERASEBKGND:   
                          break;   
                  //......   
                  default:   
                          break;   
          }   
    
          return   CallWindowProc(oldProc,   hWnd,   uMsg,   wParam,   lParam);   
  }   
  2.同第二种   
  3.同第二种   
  4.在BeginSubclassing中得到类名后,用SetWindowLong的方式子类化   
          oldProc   =   (WNDPROC)   GetWindowLong(hWnd,   GWL_WNDPROC);   
          SetWindowLong(hWnd,   GWL_WNDPROC,   (LONG)   ProcButton);   
    
  第四种:不用Hook   
  在一个对话框的OnInitDialog中枚举它的所有子窗体   
  例如用下面两句来实现   
  hWnd=GetWindow(hDlg,GW_CHILD);     
  hWnd=GetWindow(hWnd,GW_HWNDNEXT);   
  对每个子窗体进行子类化处理,处理过程同第二种与第三种。   
        
  第五种:如果是在XP下运行,可以使用manifest,也就是如下的一个XML文件   
  <?xml     version="1.0"     encoding="UTF-8"     standalone="yes"?>       
  <assembly     xmlns="urn:schemas-microsoft-com:asm.v1"     manifestVersion="1.0">       
  <assemblyIdentity       
                name="Microsoft.Windows.XXXX"       
                processorArchitecture="x86"       
                version="5.1.0.0"       
                type="win32"/>       
  <description>Windows     Shell</description>       
  <dependency>       
                <dependentAssembly>       
                                <assemblyIdentity       
                                                type="win32"       
                                                name="Microsoft.Windows.Common-Controls"       
                                                version="6.0.0.0"       
                                                processorArchitecture="x86"       
                                                publicKeyToken="6595b64144ccf1df"       
                                                language="*"       
                                />       
                </dependentAssembly>       
  </dependency>       
  </assembly>       
  把它存为   应用程序名.manifest,放到和应用程序对应的目录下   
  或者把它作为资源类型为24的资源编译进应用程序中。   
  这样程序在XP下就自动拥有了XP的风格。   
    
  第六种:使用第三方的库skinmagic等实现换肤。   
  第七种:用第三方应用程序给整个windows换肤。   
    
  以上七种方式各有优缺点。我在使用过程中也遇到不少问题,现在一一道来,希望和大家共同解决问题。   
  先排除几种不准备深入探讨的方式:   
  第五种manifest方式   最快速和简洁,但是功能有限,存在严重的平台限制,   
  不过好处在于应用程序可以和windows共一种风格。   
  第六种skinmagic方式   使用起来很简单,定制性也不错,如果他肯开源,并且能在贴图之外提供超强的自绘方式,   
  那么可以称得上是换肤功能的终结者,否则,对开发者来说,意义并不大   
  第七种,   属于自娱性质的,也就不多说了。   
  第一种,   直接使用现成的类,属于很常见的一种用法,一般来说使用上不会出什么问题,缺点就不说了,   
  如果这种方式让我满意,我就不必发这篇帖子了。   
    
  下面看看第二三四种:   
  第二种是用HOOK+窗口类,实现起来比较方便,和做一个自绘控件的工作量其实是一样的。   
  第三种是用HOOK+窗口过程,实现起来比较麻烦,需要自己处理一堆switch   case,   自己转换消息参数,  
  自己找地方维护一堆状态变量,工作量很大。   
  第四种不用HOOK的方式,有个缺点:对被换肤的程序的源代码的修改比较多。当然,直接到进程中去找窗口句柄,   
  然后子类化那么就不用源代码了,不过这样的话还不如用HOOK呢。   
  实际上,HOOK机制和枚举窗体虽然过程不同,不过最终目的是一样的,都是为了子类化窗口。所以在此不去探讨孰优孰劣了。   
    
  现在切入正题,谈谈在子类化过程中遇到的问题:   
  一)   重复subclass的问题   
  上面提到,子类化的两种方式:用窗口类或者用窗口过程。   
  使用窗口类是从CWnd派生一个类,调用CWnd的protected函数SubclassWindow.   
  可是如果正常使用一个窗口类(声明成员变量,加入DDX_Control),实际上在DDX_Control中也是是用了SubclassWindow的。   
  假如为一个控件声明变量,而在Hook中又进行了子类化,结果会怎么样呢?   
  答案是,程序崩溃或弹出消息框"不支持的操作"。   
  因为SubclassWindow函数调用前是要先Attach到一个HWND上去的。重复的Attach看来是不允许。   
  要避免程序崩溃也有办法:   
  1.   只为控件声明一个指针变量,动态的去获取CWnd类的实例,但是这样就达不到换肤的目的了。   
  2.   还有一种方法,经过我试验,如果两个SubclassWindow的调用位于不同的模块,例如一个位于exe,一个位于dll(我是通过exe中调用dll中的函数显示该dll中的对话框来测试的),那么就不会出现问题。在还没有找到更好的方法之前,这也姑且算是一种解决方法吧。   
    
  但是如果使用窗口过程来子类化,就不存在重复subclass的问题了,只要小心处理,子类化无数次都没问题,但是对于复杂的自绘事件,在一个窗口过程中来写switch语句,好像很麻烦。   
  我尝试过自己写一个新的SubclassWindow函数来尝试借用CWnd的窗口过程,这样就可以按照MFC的方式来写消息响应函数了。只可惜,最终还是无功而返,因为SubclassWindow不是虚函数,而CWnd的窗口过程是作为一个protected成员存在的。所以没法在外部借用MFC的消息机制。   
  所以,自己写代码处理wParam和lParam看来在所难免。   
    
  二)   子类化系统对话框的问题。   
  系统的对话框和自己的对话框表现的总不一样。   
  目前我还没有对所有的系统对话框进行测试。在MessageBox弹出的对话框中遇到的问题可以见我这一片帖子:   
  http://community.csdn.net/Expert/TopicView1.asp?id=3103399   
  在文件对话框中我遇到一个问题,子类化过的CStatic的背景好像没有重绘一样,照理说应该由CStatic的父窗体负责背景的。   
  我在我的CStaticNew类中只重载了OnPaint,里面只处理文字和图标的绘制,背景的绘制留给父窗体完成。这样的处理在MessageBox和自己的AboutDlg中都没有问题,Static控件的背景就是父窗口的背景,可是在CFileDlg中背景就没有重绘了   
  void   CStaticNew::OnPaint()     
  {   
  CPaintDC   dc(this);   //   device   context   for   painting   
  //   TODO:   Add   your   message   handler   code   here   
  CRect   rt;   
  GetWindowRect(rt);   
    
  //   绘制背景   
    
  dc.SetBkMode(TRANSPARENT);   
  //   绘制文字   
  CFont   *pfont,   *   pOldFont;   
  pfont   =   GetFont();   
  if   (pfont)   
  pOldFont   =   dc.SelectObject(pfont);   
    
  CString   szTitle;   
  GetWindowText(szTitle);   
  dc.DrawText(szTitle,   CRect(0,   0,   rt.Width(),   rt.Height()),   DT_LEFT   |   DT_WORDBREAK   );   
    
  if   (pfont)   
  dc.SelectObject(pOldFont);   
  //   绘制图标   
  if   ((GetStyle()   &   SS_ICON)   !=   0)   
  {   
  dc.DrawIcon(0,   0,   GetIcon());   
  }   
  //   Do   not   call   CStatic::OnPaint()   for   painting   messages   
  }   
    
  三)   类名的识别问题   
  到现在为止,我所使用的子类化方法都是基于GetClassName这个函数获得窗口类名,再根据用spy++所得到的知识,   
  如"#32770"表示对话框,"ToolbarWindow32"是工具栏,等等。但是窗口类名是可以在创建时任意指定的呀,   
  而像CMainFrame的类名根本就不能够确定,例如记事本主窗体的类名是"Notepad",写字板主窗体的类名是"WordPadClass"   
  这样的话,子类化如何去进行呢。真想知道windows是怎么做的,skinmagic又是怎么做的。   
    
  目前主要就是这三个问题了。   
  希望大家能展开讨论,给出一个换肤的完善的解决方案

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值