zoukankan      html  css  js  c++  java
  • WIN32界面开发之三:DUI雏形开发(一)

    http://blog.csdn.net/harvic880925/article/details/9491387

     

    目录(?)[+]

     

    前言:这部分涉及工程比较大,所以我打算分开为两篇来写,第一篇完成基本框架的构建,第二篇添加上EVENT和NOTIFY机制。

    完成目标:仿照DirectUI,完成一个基本雏形,开发一个布局控件(Dialog),和一个按钮控件(Button),通过XML来布局窗体,最后按钮响应点击、鼠标移动等事件信息,用户还可以通过NOTIFY机制来定制,用户具体行为时,界面所要做的动作。给大家看下最终界面吧,一个背景和四个按钮。
    正常状态:

    点击按钮状态(点击按钮时,并拖动窗体)

    正文

    一、CDialogBuilder的构建

    1、先看下我们布局的XML

    [html] view plaincopy
     
    1. "<XML>"  
    2. "<Dialog bk="C:\bg.png" pos="0 0 390 212">"  
    3.     "<Canvas pos="0 0 100 212">"  
    4.         "<Button pos="0 0 60 60" />"  
    5.         "<Button pos="70 70 100 100" />"  
    6.     "</Canvas>"  
    7.     "<Canvas pos="100 0 300 212">"   
    8.         "<Button pos="120 20 160 60" />"  
    9.         "<Button pos="170 170 200 200" />"  
    10.     "</Canvas>"  
    11. "</Dialog>"  
    12. "</XML>"  
    2、两个文件:UIMarkup.h和UIMarkup.cpp
    这两个文件里主要完成的功能就是加载XML,并且对XML对容进行分析,构建出结点树,具体代码就不讲了,只要知道所完成的功能就可以了,大家有兴趣可以看看,

    但有几个接口,要注意一下:

    [cpp] view plaincopy
     
    1. CMarkup::Load(LPCTSTR pstrXML) //加载并分析XML,构建XML结点树  
    2. CMarkup::GetRoot()         //获取XML树的根结点  
    3.   
    4. CMarkupNode CMarkupNode::GetParent();//获取父结点  
    5. CMarkupNode CMarkupNode::GetSibling();//获取下一紧临的兄弟结点  
    6. CMarkupNode CMarkupNode::GetChild(); //获取孩子结点  
    7. CMarkupNode CMarkupNode::GetChild(LPCTSTR pstrName);//根据名字获取孩子结点  
    8.   
    9. bool CMarkupNode::HasAttributes();  
    10. bool CMarkupNode::HasAttribute(LPCTSTR pstrName);  //指定结点是否具有属性  
    11. int CMarkupNode::GetAttributeCount();          //具有的属性个数  
    12. LPCTSTR CMarkupNode::GetAttributeName(int iIndex);//根据索引获取属性名  
    13.   
    14. LPCTSTR CMarkupNode::GetAttributeValue(int iIndex);  
    15. LPCTSTR CMarkupNode::GetAttributeValue(LPCTSTR pstrName);  
    16. bool CMarkupNode::GetAttributeValue(int iIndex, LPTSTR pstrValue, SIZE_T cchMax);  
    17. bool CMarkupNode::GetAttributeValue(LPCTSTR pstrName, LPTSTR pstrValue, SIZE_T cchMax);//获取对应属性名的属性的值的几个函数  
    3、CDialogBuilder讲解

    完成功能:

    1、利用上面的CMarkUp类分析XML,构建出XML结点树
    2、然后根据XML中的结点,NEW 出控件,并利用XML结点树的属性,构建出控件的属性

    先看定义:

    [cpp] view plaincopy
     
    1. class CDialogBuilder  
    2. {  
    3. public:  
    4.     CDialogBuilder();  
    5.     ~CDialogBuilder();  
    6. public:  
    7.    CControlUI* Create(LPCTSTR pstrXML);  
    8.   
    9. private:  
    10.    CControlUI* _Parse(CMarkupNode* parent, CControlUI* pParent = NULL);  
    11.   
    12.    CMarkup m_xml;  
    13. };  
    可以看到,CDialogBuilder比较简单,只有两个函数,Create()和_Parse(),另一个变量m_xml是用来分析XML,并构建XML结点树的。
    看下Create函数
    [cpp] view plaincopy
     
    1. CControlUI* CDialogBuilder::Create(LPCTSTR pstrXML)  
    2. {  
    3.    if( !m_xml.Load(pstrXML) ) return NULL;//加载XML,并生成XML结点树  
    4.    // NOTE: The root element is actually discarded since the _Parse() methods is  
    5.    //       parsing children and attaching to the current node.  
    6.    CMarkupNode root = m_xml.GetRoot();//获取XML结点树的根结点  
    7.    return _Parse(&root);//分析结点树  
    8. }  
    下面看看_Parse函数完成的功能:
    [cpp] view plaincopy
     
    1. CControlUI* CDialogBuilder::_Parse(CMarkupNode* pRoot, CControlUI* pParent)  
    2. {  
    3.    IContainerUI* pContainer = NULL;  
    4.    CControlUI* pReturn = NULL;  
    5.    for( CMarkupNode node = pRoot->GetChild() ; node.IsValid(); node = node.GetSibling() ) {  
    6.       LPCTSTR pstrClass = node.GetName();  
    7.       SIZE_T cchLen = _tcslen(pstrClass);  
    8.       CControlUI* pControl = NULL;  
    9.       switch( cchLen ) {  
    10.       case 6:  
    11.          if( _tcscmp(pstrClass, _T("Canvas")) == 0 )                pControl = new CContainerUI;  
    12.          else if( _tcscmp(pstrClass, _T("Button")) == 0 )           pControl = new CButtonUI;  
    13.          else if ( _tcscmp(pstrClass, _T("Dialog")) == 0)           pControl=new CDialogUI;  
    14.          break;  
    15.       }/////根据XML树,生成对应的控件  
    16.   
    17.       ASSERT(pControl);  
    18.       if( pControl == NULL ) return NULL;  
    19.       // Add children  
    20.       if( node.HasChildren() ) {  
    21.          _Parse(&node, pControl);//利用递规,遍历XML树  
    22.       }  
    23.       // Attach to parent  
    24.       if( pParent != NULL ) {//如果它的父亲不为空,说明它的父结点肯定具有CONTAINER属性,所以把它加到它的父结点的CONTAINER中  
    25.          if( pContainer == NULL ) pContainer = static_cast<IContainerUI*>(pParent->GetInterface(_T("Container")));  
    26.          ASSERT(pContainer);  
    27.          if( pContainer == NULL ) return NULL;  
    28.          pContainer->Add(pControl);  
    29.       }  
    30.       // Process attributes  
    31.       if( node.HasAttributes() ) {//分析属性,然后设置控件属性  
    32.          TCHAR szValue[500] = { 0 };  
    33.          SIZE_T cchLen = lengthof(szValue) - 1;  
    34.          // Set ordinary attributes  
    35.          int nAttributes = node.GetAttributeCount();  
    36.          for( int i = 0; i < nAttributes; i++ ) {  
    37.             pControl->SetAttribute(node.GetAttributeName(i), node.GetAttributeValue(i));  
    38.          }  
    39.       }  
    40.       // 返回根结点,对于我们的XML,返回的是CDialogUI的实例指针  
    41.       if( pReturn == NULL ) pReturn = pControl;  
    42.    }  
    43.    return pReturn;  
    44. }  

    二、控件与容器的构建

    一、总体关系

    这里我们先讲下,控件类和布局类的区别,在这里我们在构建几个类,先看下继承图(这篇文章先不管INotifyUI的事)

    1、一个控件基类(CControlUI)和一个按钮控件类(CButtonUI)
    控件基类,包含有控件绘制的最基本的几个函数,当然都是虚函数,比如:SetAttribute(设置控件属性),GetInterface(获取控件的this指针),DoPaint(绘图)
    2、一个容器基类,故名思义,容器是用来盛控件的,所以它必须具有的几个函数:
    Add(将控件添加到此容器的子队列中),Remove(删除某个子控件)、RemoveAll(清空子控件队列)、GetItem(获取某个子控件)、GetCount(获取子控件的个数)
    所以这几个函数也是容器最基类IContainerUI的函数,
    3、CContainerUI,这个才是稍微有意义点的布局类,它用于在窗体上划分出不同的区域,然后在区域中布局控件.这里最值得注意的地方是派生出它的基类,它是两个基类的派生类(IContainerUI和CControlUI),所以容器也是控件,这一点在后面绘图代码中特点重要,在绘图时,我们将所有的容器和控件全部都定义为CControlUI,所以如果当前的如果是CDialogUI的指针的话,它就会根据继承关系,先到CDialogUI中执行相关函数,这点尤其注意!!!!(这里暂时搞不明白也没关系,后面遇到的时候,我会重新讲)
    4、CDialogUI,这是窗体的构建类,里面包含所在构建窗体的大小、背景图案,现在只加了这两个属性,其它都没加

    二、控件类的实现
    1、CContolUI的实现

    [cpp] view plaincopy
     
    1. class CControlUI  
    2. {  
    3. public:  
    4.     CControlUI();  
    5.     virtual ~CControlUI();  
    6. public:  
    7.     virtual void SetAttribute(LPCTSTR pstrName, LPCTSTR pstrValue);//设置属性  
    8.     virtual LPVOID GetInterface(LPCTSTR pstrName);//获取当前control的this指针  
    9.     virtual void DoPaint(HWND hwnd,HDC hDC)=0;//控件的绘制函数,注意这里把它声明为纯虚函数,强制子类中必须对其进行实现  
    10.       
    11. //设置控件属性的几个函数  
    12.     void SetPos(RECT rc);///设置控件位置  
    13.     RECT GetPos();  
    14.     virtual CControlUI* GetParent();//设置当前控件的父结点  
    15.     virtual void SetParent(CControlUI* parent);  
    16.       
    17.     virtual void SetHwnd(HWND hwnd);//设置窗体句柄  
    18.   
    19. public:  
    20.     void Invalidate();  
    21. protected:  
    22.     RECT m_RectItem;///控件位置  
    23.     CControlUI *m_pParent;//父结点指针  
    24.   
    25.     HWND m_hwnd;//保存传过来的窗体的句柄  
    26. };  
    这里大家可能会有个疑问,控件要窗体句柄干嘛,这里呢,因为我们只有一个窗体,也就是CreateWindowEx函数创建后,返回的那个HWND,我们这里保存的就是这个HWND,那我们要它有什么用呢,这是因为我们这里的控件全部都是模拟的控件的行为而已,是并没有句柄的,所以也不可能像MFC那样根据控件的句柄单独刷新了,所以我们要刷新控件的话,就得刷新整个窗体(当然,我们会利用缓存技术局部绘制来提高效率),我们要刷新窗体就得利用SendMessage(hwnd,msg,wparam,lparam),注意这里的第一个变量就是窗体的句柄,当然这里要保存了,就这样的道理。大笑

    看各个函数的具体实现

    [cpp] view plaincopy
     
    1. void CControlUI::SetPos(RECT rc)  
    2. {  
    3.     m_RectItem = rc;  
    4. }  
    5. RECT CControlUI::GetPos()  
    6. {  
    7.     return m_RectItem;  
    8. }  
    这俩函数很简单,就是设置控件位置和获取控件位置的。
    [cpp] view plaincopy
     
    1. void CControlUI::SetAttribute(LPCTSTR pstrName, LPCTSTR pstrValue)  
    2. {  
    3.     if( _tcscmp(pstrName, _T("pos")) == 0 ) {  
    4.         RECT rcPos = { 0 };  
    5.         LPTSTR pstr = NULL;  
    6.         rcPos.left = _tcstol(pstrValue, &pstr, 10);  ASSERT(pstr);      
    7.         rcPos.top = _tcstol(pstr + 1, &pstr, 10);    ASSERT(pstr);      
    8.         rcPos.right = _tcstol(pstr + 1, &pstr, 10);  ASSERT(pstr);      
    9.         rcPos.bottom = _tcstol(pstr + 1, &pstr, 10); ASSERT(pstr);      
    10.         SetPos(rcPos);  
    11.     }  
    12. }  
    这个函数是设置控件属性,哪里用到这个函数了呢,嘿嘿,还记得不,CDialogBuilder的_Parse函数里
    [cpp] view plaincopy
     
    1. void CControlUI::SetParent(CControlUI* parent)  
    2. {  
    3.     m_pParent=parent;  
    4. }  
    5. CControlUI* CControlUI::GetParent()  
    6. {  
    7.     return m_pParent;  
    8. }  
    设置父结点,也是在CDialogBuilder的_Parse函数里用到
    [cpp] view plaincopy
     
    1. LPVOID CControlUI::GetInterface(LPCTSTR pstrName)  
    2. {  
    3.     if( _tcscmp(pstrName, _T("Control")) == 0 ) return this;  
    4.     return NULL;  
    5. }  
    根据要获取的指针名,返回当前实例的this指针,哪里用到了呢,也是CDialogBuilder的_Parse函数里,下面是_Parse里的,我摘过来
    [cpp] view plaincopy
     
    1. if( pParent != NULL ) {  
    2.    if( pContainer == NULL ) pContainer = static_cast<IContainerUI*>(pParent->GetInterface(_T("Container")));  
    3.    ASSERT(pContainer);  
    4.    if( pContainer == NULL ) return NULL;  
    5.    pContainer->Add(pControl);  
    6. }  
    看到了吧,获取的是父结点的Container的指针,然后将控件加到父结点中,下面继续
    [cpp] view plaincopy
     
    1. void CControlUI::SetHwnd(HWND hwnd)  
    2. {  
    3.     m_hwnd=hwnd;  
    4. }  
    5.   
    6. void CControlUI::Invalidate()  
    7. {  
    8.     SendMessage(m_hwnd,WM_PAINT,NULL,NULL);  
    9. }  
    SetHwnd用来保存窗体的HWND,然后是Invalidate函数的实现,也就是向窗体发送WM_PAINT消息
    2、CButtionUI的实现
    定义:
    [cpp] view plaincopy
     
    1. class CButtonUI:public CControlUI  
    2. {  
    3. public:  
    4.     CButtonUI();  
    5.     ~CButtonUI();  
    6. public:  
    7.     virtual void SetAttribute(LPCTSTR pstrName, LPCTSTR pstrValue);  
    8.     virtual LPVOID GetInterface(LPCTSTR pstrName);  
    9.     virtual void DoPaint(HWND hwnd,HDC hDC);  
    10. };  
    可以看到,很简单,设置属性、获取THIS指针,然后就是绘图类,看具体实现
    [cpp] view plaincopy
     
    1. void CButtonUI::SetAttribute(LPCTSTR pstrName, LPCTSTR pstrValue)  
    2. {//因为实现的是最简易版,Button里也就不设置什么独有的属性了,直接返回基类的SetAttribute(),设置Pos属性  
    3.     return CControlUI::SetAttribute(pstrName,pstrValue);  
    4. }  
    5. LPVOID CButtonUI::GetInterface(LPCTSTR pstrName)  
    6. {  
    7.     if( _tcscmp(pstrName, _T("Button")) == 0 ) return this;  
    8.     return CControlUI::GetInterface(pstrName);  
    9.   
    10. }  
    11. void CButtonUI::DoPaint(HWND hwnd,HDC hDC)  
    12. {  
    13.     assert(hDC);  
    14.     Graphics graph(hDC);  
    15.     graph.FillRectangle(&SolidBrush(Color::Green),m_RectItem.left,m_RectItem.top,m_RectItem.right-m_RectItem.left,m_RectItem.bottom-m_RectItem.top);  
    16.     graph.ReleaseHDC(hDC);  
    17.   
    18. }  
    这里要说明的一点就是,DoPaint()函数,因为我们这篇文章里面还没有添加EVENT事件通知功能,所以我们就先初始化的时候,把按钮统一画成绿色。
    三、容器类的实现
    1、容器最基类IContainerUI的实现
    [cpp] view plaincopy
     
    1. class IContainerUI  
    2. {  
    3. public:  
    4.     virtual CControlUI* GetItem(int iIndex) const = 0;  
    5.     virtual int GetCount() const = 0;  
    6.     virtual bool Add(CControlUI* pControl) = 0;  
    7.     virtual bool Remove(CControlUI* pControl) = 0;  
    8.     virtual void RemoveAll() = 0;  
    9. };  
    其实这个类没有任何的实际意义,就是个接口类,它的这几个函数全部都声明为纯虚函数
    2、CContainerUI的实现
    定义:
    [cpp] view plaincopy
     
    1. class CContainerUI : public CControlUI, public IContainerUI  
    2. {  
    3. public:  
    4.     CContainerUI();  
    5.     virtual ~CContainerUI();  
    6. public:  
    7.     ///先实现继承的纯虚函数  
    8.     virtual CControlUI* GetItem(int iIndex) const;  
    9.     virtual int GetCount() const;  
    10.     virtual bool Add(CControlUI* pControl);  
    11.     virtual bool Remove(CControlUI* pControl);  
    12.     virtual void RemoveAll();   
    13. public:  
    14.     void SetAttribute(LPCTSTR pstrName, LPCTSTR pstrValue);///设置属性  
    15.     virtual LPVOID GetInterface(LPCTSTR pstrName);//获取THIS指针  
    16.     virtual  void DoPaint(HWND hwnd,HDC hDC);//绘图函数  
    17.   
    18.     void LoadBackground(LPCTSTR Parth);//根据路径加载图像,并保存在m_pImage中  
    19. protected:  
    20.     CStdPtrArray m_items;   //当前容器中的子变量队列  
    21.   
    22.     Gdiplus::Image* m_pImage;//容器的背景  
    23. };  
    大家看到了吧,这里除了实现IContainerUI几个纯虚函数以外,其它几个函数SetAttribute、GetInterface、DoPaint也都是控件所具有的。看下具体实现:
    [cpp] view plaincopy
     
    1. CControlUI* CContainerUI::GetItem(int iIndex) const  
    2. {  
    3.     if( iIndex < 0 || iIndex >= m_items.GetSize() ) return NULL;  
    4.     return static_cast<CControlUI*>(m_items[iIndex]);  
    5. }  
    6.   
    7. int CContainerUI::GetCount() const  
    8. {  
    9.     return m_items.GetSize();  
    10. }  
    11.   
    12. bool CContainerUI::Add(CControlUI* pControl)  
    13. {  
    14.     return m_items.Add(pControl);  
    15. }  
    16.   
    17. bool CContainerUI::Remove(CControlUI* pControl)  
    18. {  
    19.     for( int it = 0;it < m_items.GetSize(); it++ ) {  
    20.         if( static_cast<CControlUI*>(m_items[it]) == pControl ) {  
    21.             delete pControl;  
    22.             return m_items.Remove(it);  
    23.         }  
    24.     }  
    25.     return false;  
    26. }  
    27.   
    28. void CContainerUI::RemoveAll()  
    29. {  
    30.     for( int it = 0;it < m_items.GetSize(); it++ ) delete static_cast<CControlUI*>(m_items[it]);  
    31.     m_items.Empty();  
    32. }  
    这几个就不讲了,就是向队列里添加、删除变量的操作。
    [cpp] view plaincopy
     
    1. LPVOID CContainerUI::GetInterface(LPCTSTR pstrName)  
    2. {  
    3.     if( _tcscmp(pstrName, _T("Container")) == 0 ) return static_cast<IContainerUI*>(this);  
    4.     return CControlUI::GetInterface(pstrName);  
    5. }  
    获取this指针
    [cpp] view plaincopy
     
    1. void CContainerUI::LoadBackground(LPCTSTR Parth)  
    2. {  
    3.     m_pImage = Gdiplus::Image::FromFile(Parth);  
    4. }  
    5.   
    6. void CContainerUI::SetAttribute(LPCTSTR pstrName, LPCTSTR pstrValue)  
    7. {  
    8.     if( _tcscmp(pstrName, _T("bk")) == 0 ) LoadBackground(pstrValue);  
    9.     else CControlUI::SetAttribute(pstrName, pstrValue);  
    10. }  
    设置属性,这里只在CControlUI的基础上增加了一个属性,bk-----背景
    [cpp] view plaincopy
     
    1. void CContainerUI::DoPaint(HWND hwnd,HDC hDC)  
    2. {  
    3.     for( int it = 0; it < m_items.GetSize(); it++ ) {  
    4.         CControlUI* pControl = static_cast<CControlUI*>(m_items[it]);  
    5.         pControl->DoPaint(hwnd,hDC);  
    6.     }  
    7. }  
    最重要的,绘制函数,我这里没有对它本身进行绘制,只是遍历当前容器中所有的控件,然后再它里面的控件进行逐个绘制,这里我要留下一个疑问,如果容器里嵌套有容器是怎么完成绘制的呢????!!!!
    3、CDialogUI实现
    定义:
    [cpp] view plaincopy
     
    1. class CDialogUI:public CContainerUI  
    2. {  
    3. public:  
    4.     CDialogUI();  
    5.     ~CDialogUI();  
    6. public:  
    7.     void DoPaintBackground(HDC hdc);//画背景  
    8.     virtual LPVOID GetInterface(LPCTSTR pstrName);//获取this指针  
    9. };  

    大家可以看到这个CDialogUI的实现是非常简单的,只有一个DoPaintBackground(),这个函数只是用来画背景的

    实现:

    [cpp] view plaincopy
     
    1. void CDialogUI::DoPaintBackground(HDC hdc)  
    2. {  
    3.     Gdiplus::Graphics graph(hdc);  
    4.     graph.SetSmoothingMode(Gdiplus::SmoothingModeNone);  
    5.     graph.DrawImage(m_pImage, 0, 0, m_RectItem.right-m_RectItem.left, m_RectItem.bottom-m_RectItem.top);  
    6.     graph.ReleaseHDC(hdc);  
    7. }  
    8. LPVOID CDialogUI::GetInterface(LPCTSTR pstrName)  
    9. {  
    10.     if( _tcscmp(pstrName, _T("Dialog")) == 0 ) return static_cast<IContainerUI*>(this);  
    11.     return CContainerUI::GetInterface(pstrName);  
    12. }  
    好了,到这几个控件的实现就全部实现了,下面看窗体的创建。

    三、窗体创建

    基本思想,我们为了让用户不必做太多的工作,我们建一个基类,这个基类完成窗口类注册、创建窗口、消息响应等功能,而用户只需要从我们的基类派生,并且简单调用Create()函数就可以了。
    一、创建窗体基类(CWindowWnd)

    定义

    [cpp] view plaincopy
     
    1. class CWindowWnd{  
    2. public:  
    3.     CWindowWnd();  
    4.     ~CWindowWnd();  
    5.   
    6.     HWND GetHWND() const{return m_hWnd;}  
    7.   
    8.     bool RegisterWindowClass(WNDPROC fWndProc,HINSTANCE hInstance,LPCTSTR szClassName);//注册窗口类  
    9.   
    10.     HWND Create();//创建窗体  
    11.   
    12.     void ShowWindow(bool bShow = true, bool bTakeFocus = true);  
    13.     void SetInstance(HINSTANCE instance){m_instance=instance;}  
    14.   
    15. protected:  
    16.     virtual LRESULT HandleMessage(UINT uMsg, WPARAM wParam, LPARAM lParam);  
    17.   
    18.     static LRESULT CALLBACK __WndProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam);  
    19. protected:  
    20.     HWND m_hWnd;// 保存所创建窗体的句柄  
    21.   
    22.     HINSTANCE m_instance;//保存WinMain入口,参数里的hInstance,因为注册窗口类要用到  
    23. };  
    注意一点:这里必须把窗口消息处理函数(_WndProc)定义为静态函数,否则就定义为全局函数
    我们看下具体实现,由简到难讲:
    [cpp] view plaincopy
     
    1. bool CWindowWnd::RegisterWindowClass(WNDPROC fWndProc,HINSTANCE hInstance,LPCTSTR szClassName)  
    2. {  
    3.     WNDCLASSEX wce={0};  
    4.     wce.cbSize=sizeof(wce);  
    5.     wce.style=CS_HREDRAW|CS_VREDRAW;  
    6.     wce.lpfnWndProc=fWndProc;  
    7.     wce.cbClsExtra=0;  
    8.     wce.cbWndExtra=0;  
    9.     wce.hInstance=hInstance;  
    10.     wce.hIcon=NULL;  
    11.     wce.hCursor=LoadCursor(NULL,IDC_ARROW);  
    12.     wce.hbrBackground=(HBRUSH)(6);//(HBRUSH)(COLOR_WINDOW+1);  
    13.     wce.lpszMenuName=NULL;  
    14.     wce.lpszClassName=szClassName;  
    15.     wce.hIconSm=NULL;  
    16.     ATOM nAtom=RegisterClassEx(&wce);  
    17.     if(nAtom==0) return false;  
    18.     return true;  
    19.   
    20. }  
    注册窗口类,没什么好讲的了,标准流程。不过这里要注意一下,fWndProc是消息处理函数,这个函数必须是静态的或是全局的!!!!!
    [cpp] view plaincopy
     
    1. void CWindowWnd::ShowWindow(bool bShow /*= true*/, bool bTakeFocus /*= false*/)  
    2. {  
    3.     ASSERT(::IsWindow(m_hWnd));  
    4.     if( !::IsWindow(m_hWnd) ) return;  
    5.     ::ShowWindow(m_hWnd, bShow ? (bTakeFocus ? SW_SHOWNORMAL : SW_SHOWNOACTIVATE) : SW_HIDE);  
    6. }  
    显示窗口,调用系统API ----ShowWindow()
    [cpp] view plaincopy
     
    1. HWND CWindowWnd::Create()  
    2. {  
    3.     if (!RegisterWindowClass(__WndProc,m_instance,L"transparent"))  
    4.     {//注册窗口类,看到了吧,窗口处理函数的函数名为:_WndProc,后面讲  
    5.         assert(L"注册窗口失败");  
    6.     }  
    7.   
    8.     assert(this);  
    9.     m_hWnd = ::CreateWindowEx(WS_EX_LAYERED, L"transparent", _T(""),WS_POPUP|WS_MAXIMIZE|WS_MINIMIZE|WS_SYSMENU,  
    10.         CW_USEDEFAULT, CW_USEDEFAULT,   
    11.         CW_USEDEFAULT, CW_USEDEFAULT,   
    12.         NULL, NULL, (HINSTANCE)::GetModuleHandle(NULL),(LPVOID)this);   
    13.   
    14.     if(m_hWnd == NULL || !::IsWindow(m_hWnd))  
    15.         return NULL;  
    16.   
    17.     return m_hWnd;    
    18. }  
    创建窗口,流程是,先注册窗口类,如果注册成功,就调用CreateWindowEx创建窗口。这里最注意的一个地方,单独把CreateWindowEx调出来
    [cpp] view plaincopy
     
    1. m_hWnd = ::CreateWindowEx(WS_EX_LAYERED, L"transparent", _T(""),WS_POPUP|WS_MAXIMIZE|WS_MINIMIZE|WS_SYSMENU,CW_USEDEFAULT, CW_USEDEFAULT,CW_USEDEFAULT, CW_USEDEFAULT, NULL, NULL, (HINSTANCE)::GetModuleHandle(NULL),(LPVOID)this);   
    注意最后一个参数:传进去的当前CWindowWnd的this指针!!!!这个参数会做为lparam传进CREATESTRUCT结构体里的lpCreateParams参数里,在WM_NCCREATE消息里可以获取到。那传进去他有什么用呢?往下看,_WndProc消息处理函数
    [cpp] view plaincopy
     
    1. LRESULT CWindowWnd::HandleMessage(UINT uMsg, WPARAM wParam, LPARAM lParam)  
    2. {  
    3.     return ::CallWindowProc(::DefWindowProc, m_hWnd, uMsg, wParam, lParam);  
    4. }  
    5. LRESULT CALLBACK CWindowWnd::__WndProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam)  
    6. {  
    7.     CWindowWnd* pThis = NULL;  
    8.     if( uMsg == WM_NCCREATE ) {  
    9.         LPCREATESTRUCT lpcs = reinterpret_cast<LPCREATESTRUCT>(lParam);  
    10.         pThis = static_cast<CWindowWnd*>(lpcs->lpCreateParams);  
    11.         pThis->m_hWnd = hWnd;  
    12.         ::SetWindowLongPtr(hWnd, GWLP_USERDATA, reinterpret_cast<LPARAM>(pThis));  
    13.     }   
    14.     else {  
    15.         pThis = reinterpret_cast<CWindowWnd*>(::GetWindowLongPtr(hWnd, GWLP_USERDATA));  
    16.         if( uMsg == WM_NCDESTROY && pThis != NULL ) {  
    17.             ::SetWindowLongPtr(pThis->m_hWnd, GWLP_USERDATA, 0L);  
    18.   
    19.             pThis->m_hWnd = NULL;  
    20.             return true;  
    21.         }  
    22.     }  
    23.     if( pThis != NULL ) {  
    24.         return pThis->HandleMessage(uMsg, wParam, lParam);  
    25.     }   
    26.     else {  
    27.         return ::DefWindowProc(hWnd, uMsg, wParam, lParam);  
    28.     }  
    29. }  
    注意在遇到WM_NCCREATE消息时,
    首先将lParam强转成LPCREATESTRUCT 变量,我们上面说过我们传过来的CWindowWnd的this指针,保存在CREATESTRUCT结构体的lpCreateParams参数里
    [cpp] view plaincopy
     
    1. pThis = static_cast<CWindowWnd*>(lpcs->lpCreateParams);  
    这句,就是取出this指针!,有同学可能会问,为什么非要这样得到this指针,_WndProc不是CWindowWnd的成员函数么,不是直接有this么?注意,_WndProc是静态函数,会在CWindowWnd创建之前编译,所以,由于在编译_WndProc时,没有CWindowWnd还没有被实例化,当然要报错了。所以我们要通过传参的方式得到this指针,那接下来的问题就是,那下次我怎么再在这个函数里得到this指针呢,这里我们用SetWindowLongPtr把他保存在GWLP_USERDATA域,然后调用GetWindowLongPtr就可以得到了。
    [cpp] view plaincopy
     
    1. ::SetWindowLongPtr(hWnd, GWLP_USERDATA, reinterpret_cast<LPARAM>(pThis));  
    这句的意思就是把THIS指针保存在GWLP_USERDATA域。

    在其它消息到来的是,我们先通过GetWindowLongPtr得到THIS指针,然后调用虚函数pThis->HandleMessage(uMsg, wParam, lParam);把消息转到HandleMessage函数中处理,而用户的函数是派生自CWindowWnd的,所以只需要在HandleMessage函数中处理消息就可以了。

    二、用户窗口类

    [cpp] view plaincopy
     
    1. class CStartPage: public CWindowWnd  
    2. {  
    3. public:  
    4.     CStartPage();  
    5.     ~CStartPage();  
    6. public:  
    7.     virtual LRESULT HandleMessage(UINT uMsg, WPARAM wParam, LPARAM lParam);  
    8.   
    9.     LPCTSTR GetDialogResource();//传进去XML字符串  
    10.   
    11.     void Paint(HWND m_hWnd);//响应WM_PAINT消息的函数  
    12. private:  
    13.     WCHAR m_resource[556];//XML字符串  
    14.   
    15.     CDialogBuilder m_dialogBuilder;  
    16.     CControlUI *m_root;  
    17.   
    18.     HDC hdcBKMemory;//内存DC,参见《之二----GDI+中的局部刷新技术》  
    19.     HBITMAP hBKBitmap;  
    20.     HGDIOBJ hBKBitmapOld;  
    21. };  
    看下具体实现:
    [cpp] view plaincopy
     
    1. LPCTSTR CStartPage::GetDialogResource()    
    2. {  
    3.     char temp[]=  
    4.         "<XML>"  
    5.         "<Dialog bk="C:\bg.png" pos="0 0 390 212">"  
    6.         "<Canvas pos="0 0 100 212">"  
    7.         "<Button pos="0 0 60 60" />"  
    8.         "<Button pos="70 70 100 100" />"  
    9.         "</Canvas>"  
    10.         "<Canvas pos="100 0 300 212">" //一定要给canvas加上POS,因为我们根据鼠标点来查找时,首先看是否在CANVAS的区域内,如果不在,就直接返回NULL了  
    11.         "<Button pos="120 20 160 60" />"  
    12.         "<Button pos="170 170 200 200" />"  
    13.         "</Canvas>"  
    14.         "</Dialog>"  
    15.         "</XML>";  
    16.     int iStrlen=sizeof(temp);  
    17.     MultiByteToWideChar(CP_ACP,0,(LPCSTR)(temp),iStrlen,m_resource,iStrlen);  
    18.     return  m_resource;  
    19. }  
    这个函数很简单,就是加载XML,并保存在m_resource字符串中
    [cpp] view plaincopy
     
    1. LRESULT CStartPage::HandleMessage(UINT uMsg, WPARAM wParam, LPARAM lParam)  
    2. {  
    3.     switch(uMsg){  
    4. case WM_DESTROY:  
    5.     {  
    6.         PostQuitMessage(100);  
    7.   
    8.         ::SelectObject( hdcBKMemory, hBKBitmapOld); //不要把默认的位图选回来,如果选回来的话,我们新建的位图就被替换掉了,当然我们上面画的东东也就没有了  
    9.         ::DeleteObject(hBKBitmapOld);//这三个在清除的时候,一块清除  
    10.         ::DeleteObject(hBKBitmap); //先不要删除,先保存起来,后面再跟hmdmDC一起删除  
    11.         ::DeleteDC(hdcBKMemory);  
    12.     }  
    13.     break;  
    14. case WM_CREATE:  
    15.     {  
    16.         m_root=m_dialogBuilder.Create(GetDialogResource());///传入XML文档,让其分析  
    17.   
    18.         SendMessage(GetHWND(),WM_PAINT,NULL,NULL);  
    19.     }  
    20.     break;  
    21. case WM_PAINT:  
    22.     {         
    23.         Paint(GetHWND());  
    24.     }  
    25.     break;  
    26.     }  
    27.     return CWindowWnd::HandleMessage(uMsg, wParam, lParam);  
    注意两个地方,在CREATE的时候,传入XML,然后发送WM_PAIT消息。然后在WM_PAIT中,Paint函数绘图
    [cpp] view plaincopy
     
    1. void CStartPage::Paint(HWND m_hWnd)  
    2. {  
    3.     RECT rcWindow;  
    4.     GetWindowRect(m_hWnd,&rcWindow);  
    5.     SIZE sizeWindow;  
    6.     /////因为根结点,必是DLG结点,肯定是个容器,所以将它强制转换成CContainerUI*,定位到  
    7.     CDialogUI *pdlg=(CDialogUI*)m_root;  
    8.   
    9.     RECT Pos=pdlg->GetPos();  
    10.     sizeWindow.cx=Pos.right-Pos.left;  
    11.     sizeWindow.cy=Pos.bottom-Pos.top;  
    12.   
    13.   
    14.     HDC hDC = ::GetDC(m_hWnd);  
    15.     if (hdcBKMemory==NULL)  
    16.     {  
    17.         hdcBKMemory = CreateCompatibleDC(hDC);  
    18.         //创建背景画布  
    19.         BITMAPINFOHEADER stBmpInfoHeader = { 0 };     
    20.         int nBytesPerLine = ((sizeWindow.cx * 32 + 31) & (~31)) >> 3;  
    21.         stBmpInfoHeader.biSize = sizeof(BITMAPINFOHEADER);     
    22.         stBmpInfoHeader.biWidth = sizeWindow.cx;     
    23.         stBmpInfoHeader.biHeight = sizeWindow.cy;     
    24.         stBmpInfoHeader.biPlanes = 1;     
    25.         stBmpInfoHeader.biBitCount = 32;     
    26.         stBmpInfoHeader.biCompression = BI_RGB;     
    27.         stBmpInfoHeader.biClrUsed = 0;     
    28.         stBmpInfoHeader.biSizeImage = nBytesPerLine * sizeWindow.cy;     
    29.         PVOID pvBits = NULL;     
    30.         hBKBitmap = ::CreateDIBSection(NULL, (PBITMAPINFO)&stBmpInfoHeader, DIB_RGB_COLORS, &pvBits, NULL, 0);  
    31.         assert(hBKBitmap != NULL);  
    32.         hBKBitmapOld = ::SelectObject( hdcBKMemory, hBKBitmap);  
    33.   
    34.         pdlg->DoPaintBackground(hdcBKMemory);//绘制窗体背景  
    35.   
    36.     }  
    37.   
    38.   
    39.     HDC hdcEnd = CreateCompatibleDC(hDC);//新建兼容DC  
    40.     BITMAPINFOHEADER stBmpInfoHeader2 = { 0 };     
    41.     int nBytesPerLine2 = ((sizeWindow.cx * 32 + 31) & (~31)) >> 3;  
    42.     stBmpInfoHeader2.biSize = sizeof(BITMAPINFOHEADER);     
    43.     stBmpInfoHeader2.biWidth = sizeWindow.cx;     
    44.     stBmpInfoHeader2.biHeight = sizeWindow.cy;     
    45.     stBmpInfoHeader2.biPlanes = 1;     
    46.     stBmpInfoHeader2.biBitCount = 32;     
    47.     stBmpInfoHeader2.biCompression = BI_RGB;     
    48.     stBmpInfoHeader2.biClrUsed = 0;     
    49.     stBmpInfoHeader2.biSizeImage = nBytesPerLine2 * sizeWindow.cy;     
    50.     PVOID pvBits2 = NULL;     
    51.     HBITMAP hbmpMem2 = ::CreateDIBSection(NULL, (PBITMAPINFO)&stBmpInfoHeader2, DIB_RGB_COLORS, &pvBits2, NULL, 0);//新建画布  
    52.   
    53.     HGDIOBJ hEndBitmapOld=SelectObject(hdcEnd,hbmpMem2);  
    54.     POINT ptSrc = { 0, 0};  
    55.     BLENDFUNCTION blendFunc;  
    56.     blendFunc.BlendOp = 0;  
    57.     blendFunc.BlendFlags = 0;  
    58.     blendFunc.AlphaFormat = 1;  
    59.     blendFunc.SourceConstantAlpha = 255;//AC_SRC_ALPHA  
    60.     ::AlphaBlend(hdcEnd,0,0,sizeWindow.cx,sizeWindow.cy,hdcBKMemory,0,0,sizeWindow.cx,sizeWindow.cy,blendFunc);//将背景复制到新画布上  
    61.   
    62.     m_root->DoPaint(m_hWnd,hdcEnd);/////////////绘制画布和控件  
    63.   
    64.     POINT ptWinPos = { rcWindow.left, rcWindow.top };  
    65.   
    66.     //UpdateLayeredWindow  
    67.     HMODULE hFuncInst = LoadLibrary(_T("User32.DLL"));  
    68.     typedef BOOL (WINAPI *MYFUNC)(HWND, HDC, POINT*, SIZE*, HDC, POINT*, COLORREF, BLENDFUNCTION*, DWORD);            
    69.     MYFUNC UpdateLayeredWindow;  
    70.     UpdateLayeredWindow = (MYFUNC)::GetProcAddress(hFuncInst, "UpdateLayeredWindow");  
    71.     if(!UpdateLayeredWindow(m_hWnd, hDC, &ptWinPos, &sizeWindow, hdcEnd, &ptSrc, 0, &blendFunc, ULW_ALPHA))  
    72.     {  
    73.         assert(L"UpdateLayeredWindow 调用失败");  
    74.         TCHAR tmp[255] = {_T('')};  
    75.     }//使用UpdateLayeredWindow更新到当前窗体上  
    76.     //释放资源  
    77.   
    78.     SelectObject(hdcEnd,hEndBitmapOld);  
    79.   
    80.     ::DeleteObject(hFuncInst);  
    81.     ::DeleteObject(hEndBitmapOld);  
    82.     ::DeleteObject(hbmpMem2);  
    83.     ::DeleteDC(hdcEnd);  
    84.   
    85.     ::DeleteDC(hDC);  
    86.   
    87. }  
    Paint()函数中用了双缓冲,具体讲解,参看《WIN32界面开发之二:GDI+中的局部刷新技术
    这里我只讲两个地方:
    1、窗体背景的绘制
    [cpp] view plaincopy
     
    1. CDialogUI *pdlg=(CDialogUI*)m_root;  
    因为我们在定义m_root时,定义的是CControlUI,但它实际是CDialogUI的实例,所以将它强转成CDialogUI指针,然后内存内存DC里调用
    [cpp] view plaincopy
     
    1. pdlg->DoPaintBackground(hdcBKMemory);//绘制窗体背景  
    来绘制窗体背景。
    2、容器及控件的绘制
    [cpp] view plaincopy
     
    1. m_root->DoPaint(m_hWnd,hdcEnd);/////////////绘制画布和控件  
    主要是靠这个函数来完成的!
    这个得着重讲一下,看它是怎么完成绘制的。我们先看一下根据XML生成的控件树

    从上面这个图就能看得出来,CDialogUI包含两个子控件(Canvas),这也就是我说的容器里包含容器,而每个Canvas又包含两个Button控件。下面我们看DoPaint的调用顺序,首先m_root虽然被定义为CControlUI的变量,但实际是CDialogUI的实例指针,所以会沿着继承图去找CDialogUI的DoPaint,而CDialogUI是没有DoPaint的,所以会去找它的子类CContainerUI的DoPaint,并且执行,我们重新看下这个DoPaint方法

    [cpp] view plaincopy
     
    1. void CContainerUI::DoPaint(HWND hwnd,HDC hDC)  
    2. {  
    3.     for( int it = 0; it < m_items.GetSize(); it++ ) {  
    4.         CControlUI* pControl = static_cast<CControlUI*>(m_items[it]);  
    5.         pControl->DoPaint(hwnd,hDC);  
    6.     }  
    7. }  
    从这里就可以看到CDialogUI的DoPaint执行顺序了:
    1、找到CDialogUI的第一个孩子结点(一个CContainerUI实例,假设为ContainerA),然后调用这个实例的DoPaint
    2、执行ContainerA->DoPaint(),依然是这个函数,也就是说,它会挨个执行那两个Button的DoPaint,完成之后返回。
    3、找到CDialogUI的第二个孩子结点(同样,一个CContainerUI实例,假设为ContainerB),然后调用这个实例的DoPaint
    4、执行ContainerB>DoPaint(),同样,它也是同样执行这个函数,然后逐个执行它的那个Button的DoPaint,之后返回。
    三、WinMain函数
    [cpp] view plaincopy
     
    1. void Message(){  
    2.     MSG msg={0};  
    3.     while(GetMessage(&msg,NULL,0,0)){  
    4.         TranslateMessage(&msg);  
    5.         DispatchMessage(&msg);  
    6.     }  
    7. }  
    8.   
    9. ULONG_PTR gdiplusToken = 0;  
    10. int WINAPI WinMain(HINSTANCE hInstance,HINSTANCE hPrevInstance,LPSTR lpCmdLine,int nShowCmd){  
    11.     Gdiplus::GdiplusStartupInput gdiplusStartupInput;  
    12.     Gdiplus::GdiplusStartup(&gdiplusToken, &gdiplusStartupInput, NULL);  
    13.   
    14.     CStartPage *startPage=new CStartPage();  
    15.   
    16.     startPage->SetInstance(hInstance);  
    17.     startPage->Create();  
    18.     startPage->ShowWindow(true);  
    19.   
    20.     Message();  
    21.   
    22.     delete startPage;  
    23.   
    24.     Gdiplus::GdiplusShutdown(gdiplusToken);  
    25.     return 0;  
    26. }  
    最终的程序界面:(现在还没有添加EVENT和NOTIFY,还不具有事件响应功能,下篇实现)


    本文由HARVIC完成,转载请标明出处(http://blog.csdn.net/harvic880925/article/details/9491387),谢谢

    源码地址:http://download.csdn.net/detail/harvic880925/5820507


    声明:本文只仅交流,转载请标明出处,感谢金山影音漂亮的界面图片。

     
  • 相关阅读:
    在ubuntu中安装photoshop cs6
    技术博客链接及相关资料
    VIM中括号的自动补全与删除
    ubuntu,jdk安装成功后,点击eclipse,提示信息A Java RunTime Environment (JRE) or Java Development Kit (JDK)
    系统安装
    css实现上下左右布局
    html+css实现选项卡功能
    jsp出现错误can not find the tag directory /web-inf/tags
    数据库类型空间效率探索(二)
    数据库类型空间效率探索(一)
  • 原文地址:https://www.cnblogs.com/dps001/p/4481112.html
Copyright © 2011-2022 走看看