WINX如何做到可视化界面开发

概要

先简单回顾一下。到目前为止,我们已经介绍了: 

  • WINX独特的消息分派机制。简洁(使用上)、高效(性能上)。
  • Windows编程入门。
  • Windows窗口类精解。
  • SW系统的窗口类。一个比较传统的窗口体系。

这里需要解释一个细节。上面提到的两个窗口类(Windows窗口类和SW系统的窗口类)概念有细节差异,不能完全等同。前者是Windows SDK中的概念,后者指的是C++窗口类(实现某一类窗口逻辑的C++类)。不过,由于C++窗口类最终也提供RegisterClass函数,因此把它可以理解为是Windows SDK中的窗口类之C++表现。后文除非特殊情况下两者均简称窗口类,不做细节差异上的深究。

WINX一直强调简单易用,可视化开发。前文我亦已经给过在WINX中进行可视化开发的例子:

  • 如何实现Custom Control?如何进行可视化开发?

那么,现在就让我们开始剖析一下,WINX是如何做到可视化界面开发的?

 

可视化开发的途径

 

1. 制作专用的可视化编辑工具。

为特定的界面库制作专用的可视化编辑工具,这种方式看起来虽然是一种高代价的方式,但几乎所有的界面库无一例外地走了这一步。如:VCL,MFC,wxWidgets,QT,SmartWin,等等。WINX未来亦可能提供专用的可视化编辑工具。

 

2.  使用Windows资源编辑工具。

在没有专用的可视化编辑工具时,使用通用的Windows资源编辑工具是一个不错的主意。这种编辑工具不少,如Visual C++的资源编辑器(MFC的可视化编辑环境依附于它),Borland的Workshop等等。特点:

  • 兼容性好(Windows直接支持)。
  • 由于是纯粹的资源编辑,故此无法提供一些高级特性,如事件响应。
  • 由于通用,故此无法识别用户自定义控件(虽然允许出现用户自定义控件)。
  • 支持ActiveX控件。

WINX目前推荐你使用资源编辑工具进行可视化开发。

 

如何提供可视化的界面控件?

 

1. 提供ActiveX控件。

不可否认,ATL一定程度上是因为ActiveX控件而存在。如果你采用ATL/WTL开发,这可能是唯一的途径。虽然ActiveX是复杂的(无论是理解上还是实现上)。以后我们会有相应的内容描述ActiveX的相关内容,以及如何在WINX中实现ActiveX控件。

 

2. 提供Windows控件。

WINX是有界面库以来,第一个支持这种方式开发控件的库。通过它你可以让你的控件象Windows标准控件(Static、Edit、ListBox、ComboBox等等)一样使用。最为关键的是,这种控件比之ActiveX控件容易理解很多,也轻量很多。通过我下面的解释你将迅速理解并掌握它。

无论是MFC,ATL/WTL,还是我之前写的SW系统,无一例外地在窗口的Create函数上加点东西。

  • SW
创建窗口代码:
 调用CreateWindow创建窗口,并将窗口对象pWnd通过附加参数lpParam传递;
  • MFC
BOOL CWnd::CreateEx(DWORD dwExStyle, LPCTSTR lpszClassName,
    LPCTSTR lpszWindowName, DWORD dwStyle,
    
int  x,  int  y,  int  nWidth,  int  nHeight,
    HWND hWndParent, HMENU nIDorHMenu, LPVOID lpParam)
{
    CREATESTRUCT cs;
    cs.dwExStyle 
=  dwExStyle;
    cs.lpszClass 
=  lpszClassName;
    cs.lpszName 
=  lpszWindowName;
    cs.style 
=  dwStyle;
    cs.x 
=  x;
    cs.y 
=  y;
    cs.cx 
=  nWidth;
    cs.cy 
=  nHeight;
    cs.hwndParent 
=  hWndParent;
    cs.hMenu 
=  nIDorHMenu;
    cs.hInstance 
=  AfxGetInstanceHandle();
    cs.lpCreateParams 
=  lpParam;

    
if  ( ! PreCreateWindow(cs))
    {
        PostNcDestroy();
        
return  FALSE;
    }

    AfxHookWindowCreate(
this );
    HWND hWnd 
=  ::CreateWindowEx(cs.dwExStyle, cs.lpszClass,
            cs.lpszName, cs.style, cs.x, cs.y, cs.cx, cs.cy,
            cs.hwndParent, cs.hMenu, cs.hInstance, cs.lpCreateParams);

    
if  ( ! AfxUnhookWindowCreate())
        PostNcDestroy();        
//  cleanup if CreateWindowEx fails too soon

    ...
}
  • ATL/WTL
template  < class  TBase,  class  TWinTraits >
HWND CWindowImplBaseT
<  TBase, TWinTraits  > ::Create(HWND hWndParent, RECT &  rcPos, LPCTSTR szWindowName,
        DWORD dwStyle, DWORD dwExStyle, UINT nID, ATOM atom, LPVOID lpCreateParam)
{
    ATLASSERT(m_hWnd 
==  NULL);

    
if (atom  ==   0 )
        
return  NULL;

    _Module.AddCreateWndData(
&m_thunk.cd, this );

    
if (nID  ==   0   &&  (dwStyle  &  WS_CHILD))
        nID 
=  (UINT) this ;

    HWND hWnd 
=  ::CreateWindowEx(dwExStyle, (LPCTSTR)MAKELONG(atom,  0 ), szWindowName,
        dwStyle, rcPos.left, rcPos.top, rcPos.right 
-  rcPos.left,
        rcPos.bottom 
-  rcPos.top, hWndParent, (HMENU)nID,
        _Module.GetModuleInstance(), lpCreateParam);

    ATLASSERT(m_hWnd 
==  hWnd);

    
return  hWnd;
}

 

这意味着,通过程序代码动态创建的窗口,与通过对话框资源中创建创建子窗口的流程并不一致。所以,通过这些界面库实现的窗口类,如果不是ActiveX控件,将不能直接插入到对话框中。而在WINX中,Create窗口对象的过程,直接就是CreateWindow这个Windows API。那么,问题的关键在于,CreateWindow这个API(或者换句话说对话框)为什么居然可以知道WINX中C++窗口类,并且把它new出来?这的确是WINX让人感到magic的地方。我们下面来拨开这个谜团。

 

对话框创建WINX窗口类的过程

对话框CreateDialog初始化流程大概如下:

  1. 根据对话框资源ID,取得对话框资源数据。
  2. 由对话框资源数据,取得对话框各个子控件的初始化数据(详细参见CreateWindowEx),大概如下:
    • 子控件的窗口类名
    • 子控件的位置
    • 子控件的ID
    • 子控件的风格(Sytle,StyleEx)
  3. 调用CreateWindowEx函数创建子控件。

CreateWindowEx流程如下:

  1. 根据窗口类名,取得窗口类信息(如窗口过程等)。这一步要求该窗口类已经RegisterClass。
  2. 通过窗口类信息,创建Windows内核窗口对象。
  3. 发送WM_NCCREATE, WM_CREATE等消息给窗口过程。
  4. 返回Windows内核窗口对象的句柄(HWND)。

这所有的过程中,除了窗口过程获得了WM_NCCREATE, WM_CREATE等消息外,所有的流程都不受干预。所以关键在于WINX的窗口过程了。我们看看代码:

template  < class  WindowClass,  class  HandleClass  =  DefaultWindowHandle >
class  Window
{
public :
    
static  LRESULT CALLBACK WindowProc(
        HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
    {
        WindowClass
*  pWnd  =  (WindowClass * )WindowMap::GetWindow(hWnd);
        
if  (pWnd  ==  NULL)
        {
            
if  (message  !=  WM_NCCREATE)
                
return  pWnd -> InternalDefault(hWnd, message, wParam, lParam);
            LPCREATESTRUCT lpCS 
=  (LPCREATESTRUCT)lParam;
            
if  (lpCS -> lpCreateParams) {
                pWnd 
=  (WindowClass * )lpCS -> lpCreateParams;
                lpCS
-> lpCreateParams  =  NULL;
            }
            
else  {
                
if  (WindowClass::StackWindowObject) {
                    WINX_ASSERT(
! " WindowClass::StackWindowObject - unexpected! " );
                    
return  FALSE;
                }
                
else  {
                    pWnd 
=  WINX_NEW(WindowClass);
                }
            }
            WindowMap::SetWindow(hWnd, pWnd);
        }
        
return  pWnd -> ProcessMessage(hWnd, message, wParam, lParam);
    }
};

注:关于其中的WindowMap、StackWindowObject等,要了解详细的信息,请参见《剖析WINX的Hello程序》一文。

分析一下,你会觉得很有意思,因为WINX支持两种窗口对象的创建方式:

  1. 和MFC、ATL/WTL、SW系统类似,C++窗口对象(pWnd)先于Windows内核窗口对象(hWnd)生成。首先我们生成窗口对象pWnd,并通过CreateWindowEx附加参数lpParam传递。在WM_NCCREATE消息中取得pWnd,并将其与窗口句柄hWnd关联。
     
  2. C++窗口对象(pWnd)后于Windows内核窗口对象(hWnd)生成。CreateWindowEx 附加参数lpParam传入NULL(对话框就是这样做),此时WINX会主动new出一个窗口对象,并将其与窗口句柄hWnd关联。

哈哈,看到这里你可能会心一笑。现在,你无疑已经知道WINX是如何制作Windows控件了。你得承认,没有比这更容易实现的控件了。—— 那么,请始终牢牢记住一点:在WINX中,所有窗口类都可以直接放入对话框。甚至包括对话框,你亦可以将他插入到另一个对话框中(代码参考:tutorials/winx/step004-user-ctrl/3.superdialog/hello.cpp)。

 

你可能感兴趣的:(C++,windows,null,Class,mfc,工具)