WTL

 /****************************************************************************************************************/
/************************************ ATL ***********************************************************************/
/****************************************************************************************************************/


// 前提技术 /
template <class T>
class B1
{
public:
    void SayHi()
    {
        T* pT = static_cast<T*>(this);   // HUH?? 我将在下面解释
 
        pT->PrintClassName();
    }
protected:
    void PrintClassName() { cout << "This is B1"; }
};
 
class D1 : public B1<D1>
{
    // No overridden functions at all
};
 
class D2 : public B1<D2>
{
protected:
    void PrintClassName() { cout << "This is D2"; }
};
 
main()
{
    D1 d1;
    D2 d2;
 
    d1.SayHi();    // prints "This is B1"
    d2.SayHi();    // prints "This is D2"
}

/

 

CWindow接口 封装了以HWND句柄为第一个参数的窗口API
{
public m_hWnd //用来直接操作窗口句柄

操作符HWND 将CWindow对象传递给以HWND为参数的函数
}

//类
CWindowImpl //窗口类
CDialogImpl //普通对话框类
CAxDialogImpl //含ActiveX控件对话框类


//1. 窗口类的定义
//宏 <- CWndClassInfo结构 <- WNDCLASSEX结构
DECLARE_WND_CLASS(_T("My Window Class")) //指定窗口类的类名
DECLARE_WND_CLASS_EX    //指定窗口类的类型和窗口的背景颜色

//typedef CWinTraits<WS_OVERLAPPEDWINDOW | WS_CLIPCHILDREN | WS_CLIPSIBLINGS,WS_EX_APPWINDOW | WS_EX_WINDOWEDGE>  CFrameWinTraits;
//预定义窗口特征之一 CFrameWinTraits //窗口类型被指定为参数模板


//2. 消息映射链
三种类型消息处理
1 WM_NOTIFY  //NOTIFY_HANDLER宏 已经将WM_NOTIFY消息参数展开
2 WM_COMMAND  //COMMAND_HANDLER宏 已经将消息参数展开
3 是其他窗口消息 //原始WPARAM 和 LPARAM值 参数无展开

//宏
BEGIN_MSG_MAP(CMyWindow)
END_MSG_MAP()
MESSAGE_HANDLER(WM_CLOSE, OnClose)  //消息响应
COMMAND_ID_HANDLER(IDC_ABOUT, OnAbout)  //控件响应
CHAIN_MSG_MAP(CPaintBkgndBase)   //嵌入类响应 P9 P10


typedef CWinTraits<WS_OVERLAPPEDWINDOW | WS_CLIPCHILDREN,WS_EX_APPWINDOW> CMyWindowTraits; //添加窗口特征

class CMyWindow : public CWindowImpl<CMyWindow, CWindow, CFrameWinTraits>,
                  public CPaintBkgnd<CMyWindow, RGB(0,0,255)>
{
typedef CPaintBkgnd<CMyWindow, RGB(0,0,255)> CPaintBkgndBase; //函数定义在 P9 P10

public:
    DECLARE_WND_CLASS(_T("My Window Class")) //窗口类的定义

    BEGIN_MSG_MAP(CMyWindow)   //消息映射链
        MESSAGE_HANDLER(WM_CLOSE, OnClose) //填写消息映射链
        MESSAGE_HANDLER(WM_DESTROY, OnDestroy)
  MESSAGE_HANDLER(WM_CREATE, OnCreate)
  COMMAND_ID_HANDLER(IDC_ABOUT, OnAbout)
  COMMAND_ID_HANDLER(IDC_ABOUT, OnAbout)
  CHAIN_MSG_MAP(CPaintBkgndBase)
    END_MSG_MAP()
 
    //bHandled参数: 默认=TRUE,需要ATL调用默认WindowProc()处理该消息=FALSE
    LRESULT OnClose(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled)
    {
        DestroyWindow();
        return 0;
    }
 
    LRESULT OnDestroy(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled)
    {
        PostQuitMessage(0);
        return 0;
    }

    LRESULT OnAbout(WORD wNotifyCode, WORD wID, HWND hWndCtl, BOOL& bHandled)
    {
        MessageBox ( _T("Sample ATL window"), _T("About MyWindow") );
        return 0;
    }

    LRESULT OnCreate(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled)
    {
 // P15
     HMENU hmenu = LoadMenu ( _Module.GetResourceInstance(), MAKEINTRESOURCE(IDR_MENU1) );
        SetMenu ( hmenu );
        return 0;
    }
 
    LRESULT OnAbout(WORD wNotifyCode, WORD wID, HWND hWndCtl, BOOL& bHandled)
    {
     CAboutDlg dlg;
        dlg.DoModal();
        return 0;
    }
};


//嵌入类
template <class T, COLORREF t_crBrushColor>
class CPaintBkgnd : public CMessageMap
{
public:
    CPaintBkgnd() { m_hbrBkgnd = CreateSolidBrush(t_crBrushColor); }
    ~CPaintBkgnd() { DeleteObject ( m_hbrBkgnd ); }
 
    BEGIN_MSG_MAP(CPaintBkgnd)
        MESSAGE_HANDLER(WM_ERASEBKGND, OnEraseBkgnd)
    END_MSG_MAP()
 
    LRESULT OnEraseBkgnd(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled)
    {
        T*   pT = static_cast<T*>(this);
        HDC  dc = (HDC) wParam;
        RECT rcClient;
 
        pT->GetClientRect ( &rcClient );
        FillRect ( dc, &rcClient, m_hbrBkgnd );
        return 1;    // we painted the background
    }
 
protected:
    HBRUSH m_hbrBkgnd;
};

 

//3. 对话框类的定义
WM_CLOSE  //关闭按钮消息
WM_INITDIALOG  //对话框初始化消息

//显示对话框: 创建一个对话框类的实例,然后调用DoModal()方法
CAboutDlg dlg;
dlg.DoModal();


class CAboutDlg : public CDialogImpl<CAboutDlg>
{
public:
    enum { IDD = IDD_ABOUT };    //需要定义名称为IDD的公有成员用来保存对话框资源的ID
 
    BEGIN_MSG_MAP(CAboutDlg)
        MESSAGE_HANDLER(WM_INITDIALOG, OnInitDialog)
        MESSAGE_HANDLER(WM_CLOSE, OnClose)
        COMMAND_ID_HANDLER(IDOK, OnOKCancel)  //自定义消息
        COMMAND_ID_HANDLER(IDCANCEL, OnOKCancel) //ATL没有包含OK CANCEL消息定义
    END_MSG_MAP()
 
    LRESULT OnInitDialog(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled)
    {
        CenterWindow();
        return TRUE;    // let the system set the focus
    }
 
    LRESULT OnClose(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled)
    {
        EndDialog(IDCANCEL);
        return 0;
    }
 
    LRESULT OnOKCancel(WORD wNotifyCode, WORD wID, HWND hWndCtl, BOOL& bHandled)
    {
 //wID参数就已经指明了消息是来自“OK”按钮还是来自“Cancel”按钮
 //属于 WM_COMMAND 消息类型
        EndDialog(wID);
        return 0;
    }
};

 


//4. ATL程序的结构

stdafx.h: //
#define STRICT
#define VC_EXTRALEAN
 
#include <atlbase.h>        // 基本的ATL类
extern CComModule _Module;  // 全局_Module
#include <atlwin.h>         // ATL窗口类


main.cpp: //
#include "MyWindow.h"
CComModule _Module;
 
int WINAPI WinMain(HINSTANCE hInst, HINSTANCE hInstPrev,
                   LPSTR szCmdLine, int nCmdShow)
{
    _Module.Init(NULL, hInst); //初始化函数 //参数1 COM服务程序才有用
 
    CMyWindow wndMain;
    MSG msg;
 
    // Create & show our main window
    //和调用CreateWindow() API时使用CW_USEDEFAULT指定窗口的宽度和高度一样,ATL使用rcDefault作为窗口的最初大小
    if ( NULL == wndMain.Create ( NULL, CWindow::rcDefault, _T("My First ATL Window") ))
    {
        // Bad news, window creation failed
        return 1;
     }
 
    wndMain.ShowWindow(nCmdShow);
    wndMain.UpdateWindow();
 
    // Run the message loop
    while ( GetMessage(&msg, NULL, 0, 0) > 0 )
    {
        TranslateMessage(&msg);
        DispatchMessage(&msg);
    }
 
    _Module.Term();  //关闭函数
    return msg.wParam;
}

 


/****************************************************************************************************************/
/************************************ WTL ***********************************************************************/
/****************************************************************************************************************/

/* WTL 类型
1. 主框架窗口的实现 -  CFrameWindowImpl, CMDIFrameWindowImpl
2. 控件的封装   -  CButton, CListViewCtrl
3. GDI 对象的封装  -  CDC, CMenu
4. 一些特殊的界面特性  -  CSplitterWindow, CUpdateUI, CDialogResize, CCustomDraw
5. 实用的工具类和宏 -  CString, CRect, BEGIN_MSG_MAP_EX
*/

//窗口类
CFrameWindowImplBase //CFrameWindowImpl的基类
 //以下三个属于CFrameWindowImplBase类的成员变量
 m_hWndClient  //它是填充主窗口客户区的“视图”窗口的句柄
 m_hWndToolBar  //工具条或Rebar的窗口句柄
 m_hWndStatusBar  //状态条的窗口句柄
CFrameWindowImpl  //SDI窗口 <- CFrameWindowImpl <- CWindow (在调用CMainFrame::OnCreate()时以上三个HWND类型的变量被赋值,有用该变量的话)
DECLARE_FRAME_WND_CLASS(_T("First WTL window"), IDR_MAINFRAME) //窗口类的定义,代替DECLARE_WND_CLASS
//参数1 窗口名(可以是NULL);参数2 资源ID 用于装载图标&菜单&加速键表


stdafx.h: //
#define STRICT
#define WIN32_LEAN_AND_MEAN
#define _WTL_USE_CSTRING   //使用CString类 先要进行预定义
 
#include <atlbase.h>       // 基本的ATL类
#include <atlapp.h>        // 基本的WTL类
extern CAppModule _Module; // WTL 派生的CComModule版本
#include <atlwin.h>        // ATL 窗口类
#include <atlframe.h>      // WTL 主框架窗口类
#include <atlmisc.h>       // WTL 实用工具类,例如:CString
#include <atlcrack.h>      // WTL 增强的消息宏,将WPARAM和LPARAM的数据还原出来

MyWindow.h: //

class CMyWindow : public CFrameWindowImpl<CMyWindow>
{
public:
 //未使用增强的消息宏
    DECLARE_FRAME_WND_CLASS(_T("First WTL window"), IDR_MAINFRAME);  //用IDR_MAINFRAME作为ID定义各种资源 (资源ID)
    BEGIN_MSG_MAP(CMyWindow)
        CHAIN_MSG_MAP(CFrameWindowImpl<CMyWindow>)  //将消息链入窗口的消息中
    END_MSG_MAP()
};

main.cpp: //
//创建窗口

#include "stdafx.h"
#include "MyWindow.h"
 
CAppModule _Module;
 
int APIENTRY WinMain ( HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow )
{
    _Module.Init ( NULL, hInstance );
 CMyWindow wndMain;
 MSG msg;
    // Create the main window
    if ( NULL == wndMain.CreateEx() ) //和ATL比较,只是这行不同
        return 1;       // Window creation failed
    // Show the window
    wndMain.ShowWindow ( nCmdShow );
    wndMain.UpdateWindow();
    // Standard Win32 message loop
    while ( GetMessage ( &msg, NULL, 0, 0 ) > 0 )
    {
     TranslateMessage ( &msg );
     DispatchMessage ( &msg );
    }
    _Module.Term();
    return msg.wParam;
}


//增强消息映射宏
class CMyWindow : public CFrameWindowImpl<CMyWindow>
{
public:
    BEGIN_MSG_MAP_EX(CMyWindow)      //可以处理界面消息,MFC只能在视图处理
     MSG_WM_CREATE(OnCreate)      //宏:MSG_(前缀) + WM_CREATE(消息响应处理的名称)
        CHAIN_MSG_MAP(CFrameWindowImpl<CMyWindow>)
    END_MSG_MAP()
   
    LRESULT OnCreate(LPCREATESTRUCT lpcs)  //LPCREATESTRUCT lpcs参数从MSG_WM_CREATE宏定义查看
    {
     //设置定时器
        SetTimer ( 1, 1000 );
        SetMsgHandled(false);  //代替ATL宏的bHandled参数,让消息通过CHAIN_MSG_MAP宏链入基类
               //即使CFrameWindowImpl类不需要处理WM_CREATE消息
        return 0;
    }
};


/****************************************************************************************************************/
//向导生成代码

_tWinMain()
//初始化COM环境,公用控件和_Module,再全局函数Run()

Run()
//创建主窗口,再调用CMessageLoop::Run()开始消息循环

CMessageFilter //类
{
 PreTranslateMessage() //函数
}

CIdleHandler //类
{
 OnIdle() //函数
}

CUpdateUIBase //CUpdateUI的基类
{
 //5种不同的界面元素 对应的常量
 UPDUI_MENUBAR   //菜单条项
 UPDUI_MENUPOPUP  //弹出式菜单项
 UPDUI_TOOLBAR   //工具条按钮
 UPDUI_STATUSBAR  //状态条格子
 UPDUI_CHILDWINDOW  //子窗口

}

CUpdateUI:CUpdateUIBase //类
{
 BOOL bSelected = GetSelected(); //bSelected 是UIEnable()第二个参数
 CUpdateUI::UIEnable(?,?); //改变菜单项使用状态
 //参数1:界面元素ID,参数2:界面元素可否用bool型变量(true可用,false不可用灰色)
 
 CUpdateUI::UISetCheck();
 CUpdateUI::UISetRadio();
 CUpdateUI::UISetText();
 //以上可以放入OnIdle()来改变状态
 UIUpdateToolBar(); //update更新后的状态
 UIUpdateMenuBar(); // *把菜单恢复到初始状态 并不是更新为最新状态*
}

//在主窗口中添加 UPDATE_UI_MAP 宏
BEGIN_UPDATE_UI_MAP(CMainFrame)
    UPDATE_ELEMENT(IDC_START, UPDUI_MENUPOPUP)
    UPDATE_ELEMENT(IDC_STOP, UPDUI_MENUPOPUP)
END_UPDATE_UI_MAP()

 

//缩略版本
class CMainFrame : public CFrameWindowImpl<CMainFrame>,
                   public CUpdateUI<CMainFrame>,
                   public CMessageFilter,
                   public CIdleHandler
{
public:
    DECLARE_FRAME_WND_CLASS(NULL, IDR_MAINFRAME)
    CWTLClockView m_view;
    virtual BOOL PreTranslateMessage(MSG* pMsg);
    virtual BOOL OnIdle();
    BEGIN_UPDATE_UI_MAP(CMainFrame)
    END_UPDATE_UI_MAP()
    BEGIN_MSG_MAP(CMainFrame)
        // ...
        CHAIN_MSG_MAP(CUpdateUI<CMainFrame>)
        CHAIN_MSG_MAP(CFrameWindowImpl<CMainFrame>)
    END_MSG_MAP()
     
    OnCreate(...)
};
CMainFrame::OnCreate //创建视图窗口 保存窗口句柄 将CMainFrame对象添加到由CAppModule维持的消息过滤器队列和空闲处理队列

 


/****************************************************************************************************************/
//创建窗口流程 (1)
//单线程 SDI

//_tWinMain():使用UNICODE时,编译器会将它替换为wWinMain(),否则为WinMain()
_tWinMain(...)
{
 Run(...) //创建窗口 并 进入消息循环
 {
  CMessageLoop theLoop;
  _Module.AddMessageLoop(&theLoop); //theLoop 挂接消息到 _Module模块(程序)对象上

  //--------------------------------------------------------------------------------------------------------
  //创建窗口 最外层
  CMainFrame wndMain.CreateEx(...) //创建窗口 class CMainFrame : public CFrameWindowImpl (CMainFrame找不到CreateEx 而在基类CFrameWindowImpl中)
  {
   T* pT = static_cast<T*>(this);
   // pT调用派生类Create函数,由于派生类没有改写,调用的是基类中的Create函数 (问题:基类是谁?)
   // 这里正式进行窗口注册 和 窗口创建
   pT->Create(...)
   {
    /****窗口注册****/
    // T是派生类CMainFrame
    ATOM atom =
    T::GetWndClassInfo().Register(&m_pfnSuperWindowProc)
    {
     //在CMainFrame中的一句宏调用:DECLARE_FRAME_WND_CLASS(NULL, IDR_MAINFRAME) 查到宏定义出处
     static CFrameWndClassInfo& GetWndClassInfo()
     {
      //包含WNDCLASS信息,和创建窗口时需要提供的一些信息
      class CFrameWndClassInfo
      {
       // WNDCLASS结构包含WNDPROC
       WNDPROC pWndProc; //窗口回调函数 =StartWndProc被传入的值
       ATOM m_atom; //这个变量判断是否已经注册 ==0未注册
       ATOM Register(WNDPROC* pProc)
       {
        m_atom = ::RegisterClassEx(...);
        return m_atom;
       }
      }
     }
    }
   
    /****窗口创建****/
    return CFrameWindowImplBase< TBase, TWinTraits >::Create(..., atom, ...)
    {
     if(atom == 0) //判断是否已经注册
      return NULL;
     ...
     return HWND hWnd=::CreateWindowEx(...,(LPCTSTR)(LONG_PTR)MAKELONG(atom, 0),...) //调用 WindowsAPI 创建窗口
    }
   }
  }
  wndMain.ShowWindow(nCmdShow);
  //--------------------------------------------------------------------------------------------------------
  // 单线程消息循环 最外层
  int nRet = theLoop.Run() // int CMessageLoop.Run()
  {
   BOOL bDoIdle = TRUE;
   int nIdleCount = 0;
   BOOL bRet;
   for(;;)
   {
    while(!::PeekMessage(&m_msg, NULL, 0, 0, PM_NOREMOVE) && bDoIdle) //判断消息队列中是否有消息 没有则OnIdle()
    {
     if(!OnIdle(nIdleCount++)) //空闲时间处理
     bDoIdle = FALSE;
    }
    bRet = ::GetMessage(&m_msg, NULL, 0, 0); //get消息
    ...// 随后两个判断:(1)如果是错误返回,不处理消息(2)如果是WM_QUIT消息,则退出消息循环,从而结束该界面线程
   
    // PreTranslateMessage() 消息过滤机制 提供了在不同窗口之间传递消息 预先对消息进行处理
    // CMessageFilter抽象虚拟函数 配合PreTranslateMessage 可实现消息过滤 (可以查看WTL此部分代码是如何写)
    // WTL通过 AddMessageFilter() 和 RemoveMessageFilter() 把对象加入CMessageLoop中
    // ---- ---- ---- ----
    // 遍历CMessageLoop.CMessageFilterd.PreTranslateMessage()
    // 有一个返回true,即对消息处理了,不再调用TranslateMessage(),而是进入下一个循环
    if(!PreTranslateMessage(&m_msg))
    {
     ::TranslateMessage(&m_msg);
     ::DispatchMessage(&m_msg);
    }
    if(IsIdleMessage(&m_msg))
    {
     bDoIdle = TRUE;
     nIdleCount = 0;
    }
   }
   return (int)m_msg.wParam;
  }
  _Module.RemoveMessageLoop(); //从模块(程序)对象上删除消息
 
 }
}

//消息的处理
//消息一开始进入的就是这个回调函数
template <class TBase, class TWinTraits>
LRESULT CALLBACK CWindowImplBaseT< TBase, TWinTraits >::
StartWindowProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
 // pThis是从_Module中取出来的,是窗口创建的时候记录(类型是CWindowImplBaseT,详细参照源码上面几行)
 // class CWndProcThunk{...} m_thunk
 // Init中的机器码:(1)把wndproc的hwnd参数替换为pThis (2)跳转到相应窗口的真实的wndproc中
 // GetWindowProc() 返回实际处理消息的地方
 pThis->m_thunk.Init(pThis->GetWindowProc(), pThis);
 WNDPROC pProc = (WNDPROC)&(pThis->m_thunk.thunk);
 // 将GWL_WNDPROC(也就是wndproc)地址修正为pProc
 WNDPROC pOldProc = (WNDPROC)::SetWindowLong(hWnd, GWL_WNDPROC, (LONG)pProc);
}

virtual WNDPROC GetWindowProc()
{
 return //GetWindowProc()就只有一行return代码
 template <class TBase, class TWinTraits>
 LRESULT CALLBACK CWindowImplBaseT< TBase, TWinTraits >::
 WindowProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
 {
  //BEGIN_MSG_MAP宏实质就是ProcessWindowMessage函数 (详细宏定义查看源码)
  BOOL ProcessWindowMessage(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam, LRESULT& lResult, DWORD dwMsgMapID = 0)
 }

}

 


/****************************************************************************************************************/
//创建窗口流程 (2)
//多线程 Mutli-SDI

// _tWinMain最外层完整结构 中间部分替换单线程与多线程
int WINAPI _tWinMain(HINSTANCE hInstance, HINSTANCE /*hPrevInstance*/, LPTSTR lpstrCmdLine, int nCmdShow)
{
 // 以下部分与单线程相同
 HRESULT hRes = ::CoInitialize(NULL);
 ATLASSERT(SUCCEEDED(hRes));
 ::DefWindowProc(NULL, 0, 0, 0L);
 AtlInitCommonControls(ICC_COOL_CLASSES | ICC_BAR_CLASSES);
 hRes = _Module.Init(NULL, hInstance);
 ATLASSERT(SUCCEEDED(hRes));
 int nRet = 0;
 
 
 //--------------------------------------------------------------------------------------------------------
    // 以下部分与单线程“不”同
    // 先创建一个CThreadManager类的实例,然后调用该类的Run()函数
 {
  CThreadManager mgr;
  nRet = mgr.Run(lpstrCmdLine, nCmdShow)
  {
   MSG msg;
   ::PeekMessage(&msg, NULL, WM_USER, WM_USER, PM_NOREMOVE);
   // AddThread() 创建界面主线程
   AddThread(lpstrCmdLine, nCmdShow) //在class CThreadManager
   {
    // ...
    // RunThread作为参数传递给CreateThread(),RunThread在class CThreadManager
    // 当建立了新线程后,就会进入回调函数RunThread(),在这里是真正的创建主窗口,并进行消息循环
    HANDLE hThread = ::CreateThread(NULL, 0, RunThread, pData, 0, &dwThreadID);
    // ...
    m_arrThreadHandles[m_dwCount] = hThread; //m_arrThreadHandles用于存放运行的线程的句柄 在class CThreadManager
    m_dwCount++; //AddThread之后 线程的计数加1
    return dwThreadID;
   }
   int nRet = m_dwCount; //m_dwCount是当前有多少线程的计数值 在class CThreadManager
   DWORD dwRet;
   //进入线程循环
   while(m_dwCount > 0)
   {
    dwRet = ::MsgWaitForMultipleObjects(m_dwCount,
    m_arrThreadHandles, FALSE, INFINITE, QS_ALLINPUT);
    if(dwRet == 0xFFFFFFFF)
     ::MessageBox(NULL, _T("ERROR: Wait for multiple objects failed!!!"), _T("test2"), MB_OK);
    else if(dwRet >= WAIT_OBJECT_0 && dwRet <= (WAIT_OBJECT_0 + m_dwCount - 1))
     RemoveThread(dwRet - WAIT_OBJECT_0); //在class CThreadManager
    else if(dwRet == (WAIT_OBJECT_0 + m_dwCount))
    {
     ::GetMessage(&msg, NULL, 0, 0);
     if(msg.message == WM_USER)
      AddThread("", SW_SHOWNORMAL);
     else
      ::MessageBeep((UINT)-1); //发声
    }
    else
     ::MessageBeep((UINT)-1);
   }
   return nRet;
  }
 }
 //--------------------------------------------------------------------------------------------------------
 
 
 // 以下部分与单线程相同
 _Module.Term();
 ::CoUninitialize();
 return nRet;
}

//多线程管理类
class CThreadManager
{
 public:
 // thread init param
 struct _RunData
 {
  LPTSTR lpstrCmdLine;
  int nCmdShow;
 };
 static DWORD WINAPI RunThread(LPVOID lpData); //建立线程完成后进入的回调函数,用于建立窗口和消息循环
 DWORD m_dwCount;  //当前有多少线程的计数值
 HANDLE m_arrThreadHandles[MAXIMUM_WAIT_OBJECTS - 1]; //用于存放运行的线程的句柄
 CThreadManager() : m_dwCount(0) {}
 DWORD AddThread(LPTSTR lpstrCmdLine, int nCmdShow);
 void RemoveThread(DWORD dwIndex);
 int Run(LPTSTR lpstrCmdLine, int nCmdShow); //_tWinMain函数创建CThreadManager对象之后调用Run来启动线程并完成创建窗口
};

//创建线程后的回调函数
static DWORD WINAPI RunThread(LPVOID lpData)
{
 CMessageLoop theLoop;
 _Module.AddMessageLoop(&theLoop); //每个线程的消息循环被加入到_Module消息循环Map中
 _RunData* pData = (_RunData*)lpData;
 CMainFrame wndFrame;
 //下面是创建窗口 和 消息循环
 //参考“创建窗口流程 (1)”此部分相同的代码流程
 if(wndFrame.CreateEx() == NULL)
 {
  ATLTRACE(_T("Frame window creation failed!/n"));
  return 0;
 }
 wndFrame.ShowWindow(pData->nCmdShow);
 ::SetForegroundWindow(wndFrame); // Win95 needs this
 delete pData;
 int nRet = theLoop.Run();
 _Module.RemoveMessageLoop();
 return nRet;
}

 


/****************************************************************************************************************/
//向导生成 工具条 & 状态条

// OnSize函数说明----------------------
CFrameWindowImpl::OnSize()
{
 UpdateLayout()
 {
  UpdateBarsPosition()
  {
   // 从新定位所有控制条和改变视图窗口的大小使之填充整个客户区
   // 向工具条和状态条发送WM_SIZE消息
   // 由这些控制条的默认窗口处理过程将它们定位到主窗口的顶部或底部
  }
 }
}


//创建工具条和状态条----------------------
LRESULT CMainFrame::OnCreate(UINT /*uMsg*/, WPARAM /*wParam*/,
                             LPARAM /*lParam*/, BOOL& /*bHandled*/)
{
    CreateSimpleToolBar();
    //CreateSimpleToolBar ( 0, ATL_SIMPLE_TOOLBAR_STYLE | TBSTYLE_FLAT | TBSTYLE_LIST ); //这组参数创建一个IE风格的工具条
 /*BOOL CFrameWindowImpl::CreateSimpleToolBar(
     UINT nResourceID = 0,      // 资源得ID:默认值0将用DECLARE_FRAME_WND_CLASS宏指定资源,这里使用的IDR_MAINFRAME是向导生成的代码(主窗框ID)
     DWORD dwStyle = ATL_SIMPLE_TOOLBAR_STYLE, // 样式:默认ATL_SIMPLE_TOOLBAR_STYLE定义为TBSTYLE_TOOLTIPS,子窗口和可见三种风格的结合,这使得鼠标移到按钮上时工具条会弹出工具提示
     UINT nID = ATL_IDW_TOOLBAR)     // 工具条的窗口ID,通常都会使用默认值
 {
     ATLASSERT(!::IsWindow(m_hWndToolBar));  // 先检查是否已经创建了一个工具条
     if(nResourceID == 0)
         nResourceID = T::GetWndClassInfo().m_uCommonResourceID;
     m_hWndToolBar = T::CreateSimpleToolBarCtrl(m_hWnd, nResourceID, TRUE, dwStyle, nID); // 读出资源并创建相应的工具条按钮,返回工具条控制句柄保存在m_hWndToolBar中
     return (m_hWndToolBar != NULL);
 }
 */

    CreateSimpleStatusBar();
    /*
 BOOL CFrameWindowImpl::CreateSimpleStatusBar(
     UINT nTextID = ATL_IDS_IDLEMESSAGE, // 用于在状态条上显示的字符串的资源ID,向导生成的ATL_IDS_IDLEMESSAGE对应的字符串是"Ready"
     DWORD dwStyle = ... SBARS_SIZEGRIP, // 状态条的样式。默认值包含了SBARS_SIZEGRIP风格,这使得状态条的右下角会显示一个改变窗口大小的标志
     UINT nID = ATL_IDW_STATUS_BAR)  // 状态条的窗口ID,通常都会使用默认值
 {
     TCHAR szText[128];    // max text lentgth is 127 for status bars
     szText[0] = 0;
     ::LoadString(_Module.GetResourceInstance(), nTextID, szText, 128); // 状态条上显示的字符串
     return CreateSimpleStatusBar(szText, dwStyle, nID);     // 重载函数创建状态条
  BOOL CFrameWindowImpl::CreateSimpleStatusBar(      // 重载版本
      LPCTSTR lpstrText,
      DWORD dwStyle = ... SBARS_SIZEGRIP,
      UINT nID = ATL_IDW_STATUS_BAR)
  {
      ATLASSERT(!::IsWindow(m_hWndStatusBar));      // 先检查是否已经创建了状态条
      m_hWndStatusBar = ::CreateStatusWindow(dwStyle, lpstrText, m_hWnd, nID); // 创建状态条,状态条的句柄存放在m_hWndStatusBar中
      return (m_hWndStatusBar != NULL);
  }
 }
    */
   
    m_hWndClient = m_view.Create(...);
 // ...
    // register object for message filtering and idle updates
    CMessageLoop* pLoop = _Module.GetMessageLoop();
    ATLASSERT(pLoop != NULL);
    pLoop->AddMessageFilter(this);
    pLoop->AddIdleHandler(this);
    return 0;
}


//显示&隐藏 工具条/状态条----------------------
//CMainFrame类有一个视图菜单,有两个命令显示/隐藏工具条和状态条,它们的ID是ID_VIEW_TOOLBAR和ID_VIEW_STATUS_BAR
//两个命令的响应函数,分别显示和隐藏相应的控制条
//下面函数 翻转控制条的显示状态,相应的翻转View|Toolbar菜单上的检查标记,然后调用UpdateLayout()重新定位控制条并改变视图窗口的大小
LRESULT CMainFrame::OnViewToolBar(WORD /*wNotifyCode*/, WORD /*wID*/,
                                  HWND /*hWndCtl*/, BOOL& /*bHandled*/)
{
    BOOL bVisible = !::IsWindowVisible(m_hWndToolBar);
    ::ShowWindow(m_hWndToolBar, bVisible ? SW_SHOWNOACTIVATE : SW_HIDE);
    UISetCheck(ID_VIEW_TOOLBAR, bVisible);
    UpdateLayout();
    return 0;
}


//工具条按钮提示 & 菜单项掠过式帮助----------------------

//菜单项掠过式帮助
OnMenuSelect()  //处理WM_MENUSELECT消息
/* 上面函数,先装载与菜单资源ID相同的字符串资源,在字符串中查找 /n 字符,使用/n "之前" 的内容作为掠过帮助的内容 */

//工具条按钮提示
OnToolTipTextA() //处理TTN_GETDISPINFOA消息
OnToolTipTextW() //处理TTN_GETDISPINFOW消息
/* 上面函数,先装载相应的字符串,使用/n "后面" 的字符串 */


//工具条按钮的UI状态更新----------------------
//首先添加到UI更新表
BEGIN_UPDATE_UI_MAP(CMainFrame)
    UPDATE_ELEMENT(ID_VIEW_TOOLBAR, UPDUI_MENUPOPUP)
END_UPDATE_UI_MAP()


LRESULT CMainFrame::OnCreate( ... )
{
 // ...
    UIAddToolBar(m_hWndToolBar); //将工具条的窗口句柄传给CUpdateUI,所以当需要更新按钮的状态时CUpdateUI会向这个窗口发消息
 // ...
}

BOOL CMainFrame::OnIdle()
{
    UIUpdateToolBar(); //遍历UI更新表,寻找有UPDUI_TOOLBAR标志又被UISetCheck()改变了状态工具条元素,改变按钮的状态
 // ...
}

// 更新弹出式菜单: CUpdateUI响应WM_INITMENUPOPUP消息,只有接到此消息时才更新菜单状态


//创建 Rebar (多工具条)----------------------
//如果你使用多个工具条,只需像向导为我们生成的关于第一个工具条的代码那在OnCreate()创建它们并调用AddSimpleReBarBand()添加到Rebar就行了

LRESULT CMainFrame::OnCreate(...)
{
    HWND hWndToolBar = CreateSimpleToolBarCtrl ( m_hWnd,
                           IDR_MAINFRAME, FALSE,
                           ATL_SIMPLE_TOOLBAR_PANE_STYLE ); //这是使工具条作为Rebar的子窗口能够正常工作所必需的新风格
                                    //相比 ATL_SIMPLE_TOOLBAR_STYLE(IE风格) 只附加一些如CCS_NOPARENTALIGN之类的风格
 
    CreateSimpleReBar(ATL_SIMPLE_REBAR_NOBORDER_STYLE);  //创建Rebar控件并将句柄存到m_hWndToolBar中 (m_hWndToolBar是工具条句柄,不是上面那个hWndToolBar不要看错)
    AddSimpleReBarBand(hWndToolBar);      //创建一个条位并告诉Rebar这个条位上是一个工具条
// ...
}


//多窗格的状态条----------------------

CMultiPaneStatusBarCtrl()
//在CMainFrame中声明一个CMultiPaneStatusBarCtrl类型的成员变量
class CMainFrame : public ...
{
//...
protected:
    CMultiPaneStatusBarCtrl m_wndStatusBar;
};

LRESULT CMainFrame::OnCreate(...)
{
    m_hWndStatusBar = m_wndStatusBar.Create ( *this ); //状态条的句柄存放在m_hWndStatusBar中 (像CreateSimpleStatusBar函数那样)
    UIAddStatusBar ( m_hWndStatusBar );
 // ...
 
 int anPanes[] = { ID_DEFAULT_PANE, IDPANE_STATUS,
               IDPANE_CAPS_INDICATOR };    // 字符串"CAPS"
 m_wndStatusBar.SetPanes ( anPanes, 3, false );
}

//建立窗格
//调用方式包含在上面
BOOL CMultiPaneStatusBarCtrl::SetPanes(int* pPanes, int nPanes, bool bSetText = true)
*pPanes: // 存放窗格ID的数组,ID可以是ID_DEFAULT_PANE,此ID用于创建支持掠过式帮助的窗格,窗格ID也可以是字符串资源ID
nPanes: // 窗格ID数组中元素的个数(窗格数)
bSetText: // 如果是true,所有的窗格被立即设置文字,否则窗格就被置空


//窗格的UI状态更新----------------------
//首先添加到UI更新表
BEGIN_UPDATE_UI_MAP(CMainFrame)
    //...
    UPDATE_ELEMENT(1, UPDUI_STATUSBAR)  // 参数1:是窗格的索引而不是ID
    UPDATE_ELEMENT(2, UPDUI_STATUSBAR)  // 如果重新排列了窗格,要更新UI更新表
END_UPDATE_UI_MAP()

//设置窗格文本
UISetText ( 1, _T("Running") ); //参数1:是窗格的索引 (UISetText()是状态条唯一支持的UI更新函数,之前出现过)

BOOL CMainFrame::OnIdle()
{
 //...
    UIUpdateStatusBar(); //空闲时更新
    /*
  使用UIUpdateStatusBar()时,菜单项再使用UISetText()就不会起作用
  菜单项UISetText()函数移到菜单项命令的响应处理函数中设置菜单项的文本
    */
    //...
}

 


/****************************************************************************************************************/
//对话框与控件

int WINAPI _tWinMain (
    HINSTANCE hInstance, HINSTANCE /*hPrevInstance*/,
    LPTSTR lpstrCmdLine, int nCmdShow )
{
    HRESULT hRes = ::CoInitialize(NULL); //首先初始化COM并创建一个单线程公寓 (对于使用ActiveX控件的对话框是有必要得)
    AtlInitCommonControls(ICC_COOL_CLASSES | ICC_BAR_CLASSES); //是InitCommonControlsEx()的封装,全局对象_Module被初始化,主对话框显示出来
    hRes = _Module.Init(NULL, hInstance);
    int nRet = 0;
    // BLOCK: Run application
    {
        CMainDlg dlgMain;   //如果CMainDlg的变量不放在区块里面,会造成崩溃,因为CMainDlg会在_Module.Term()之后再释放自己,并试图使用ATL/WTL的特性
        nRet = dlgMain.DoModal(); //用DoModal()创建的ATL对话框是模式的 (MFC所有对话框是非模式,通过代码禁用对话框的父窗口来模拟模式对话框的行为)
    }
    _Module.Term();   //_Module释放
    ::CoUninitialize();  //COM释放
    return nRet;   //DoModal()的返回值被用来作为程序的结束码
}

//使用控件的封装类*****************************************

//连接一个CWindow对象,建立变量与控件的联系----------------
HWND hwndList = GetDlgItem(IDC_LIST);
CListViewCtrl wndList1(hwndList);     // use constructor
CListViewCtrl wndList2.Attach(hwndList);    // use Attach method
CListViewCtrl wndList3 = hwndList;          // use assignment operator
//wndList1变量,hwndList窗口句柄,CListViewCtrl是window接口类名


//包容器窗口(CContainedWindow)----------------
//CContainedWindow不能处理WM_COMMAND, WM_NOTIFY和其他通知消息,因为这些消息是发给控件的父窗口的
/*
CContainedWindow 是 CContainedWindowT 定义的一个数据类型
CContainedWindowT 才是真正的模板类
通常的:将不同的window接口类名作为模板参数就行了
 这样声明就可以使用button类功能:CContainedWindowT<CButton> m_wndOKBtn
特殊的:CContainedWindowT<CWindow>和CWindow功能一样
*/
钩住一个CContainedWindow对象需要做四件事:
// 1)-----------------------------------在对话框中创建一个CContainedWindowT 成员变量
class CMainDlg : public CDialogImpl<CMainDlg>
{
// ...
protected:
    CContainedWindow m_wndOKBtn, m_wndExitBtn;
}
// 2)-----------------------------------将消息处理添加到对话框消息映射的ALT_MSG_MAP小节
class CMainDlg : public CDialogImpl<CMainDlg>
{
public:
    BEGIN_MSG_MAP_EX(CMainDlg)
        MESSAGE_HANDLER(WM_INITDIALOG, OnInitDialog)
  // ...
    ALT_MSG_MAP(1)
        MSG_WM_SETCURSOR(OnSetCursor_OK) //OK按钮设置为小节1,所有发送给OK按钮的消息将由ALT_MSG_MAP(1)小节处理
    ALT_MSG_MAP(2)
        MSG_WM_SETCURSOR(OnSetCursor_Exit)
    END_MSG_MAP()
 //下面是消息的响应函数
    LRESULT OnSetCursor_OK(HWND hwndCtrl, UINT uHitTest, UINT uMouseMsg);
    LRESULT OnSetCursor_Exit(HWND hwndCtrl, UINT uHitTest, UINT uMouseMsg);
};

// 3)-----------------------------------在对话框的构造函数中调用CContainedWindowT 构造函数并告诉它哪个ALT_MSG_MAP小节的消息需要处理
CMainDlg::CMainDlg() : m_wndOKBtn(this, 1),
                       m_wndExitBtn(this, 2)
//第一个参数this,就是使用对话框自己的消息映射链
//第二个参数告诉对象将消息发给ALT_MSG_MAP的哪个小节
{
//...
}
// 4)-----------------------------------在OnInitDialog()中调用CContainedWindowT::SubclassWindow() 方法与控件建立关联
LRESULT CMainDlg::OnInitDialog(...)
{
// ...
    // Attach CContainedWindows to OK and Exit buttons
    m_wndOKBtn.SubclassWindow ( GetDlgItem(IDOK) );
    m_wndExitBtn.SubclassWindow ( GetDlgItem(IDCANCEL) );
    return TRUE;
}


//子类化(Subclassing)------------------
// 创建一个CWindowImpl派生类并用它子类化一个控件

//创建CWindowImpl派生类
class CButtonImpl : public CWindowImpl<CButtonImpl, CButton>
{
    BEGIN_MSG_MAP_EX(CButtonImpl)
        MSG_WM_SETCURSOR(OnSetCursor)
    END_MSG_MAP()
 
    LRESULT OnSetCursor(HWND hwndCtrl, UINT uHitTest, UINT uMouseMsg)
    {
  // ...
    }
};

//在主对话框声明一个 派生类CButtonImpl 的成员变量
class CMainDlg : public CDialogImpl<CMainDlg>
{
// ...
protected:
    CContainedWindow m_wndOKBtn, m_wndExitBtn;
    CButtonImpl m_wndAboutBtn;
};

//在OnInitDialog()中子类化About按钮
LRESULT CMainDlg::OnInitDialog(...)
{
 // ... 可以添加到上面相同的代码部分
    m_wndAboutBtn.SubclassWindow ( GetDlgItem(ID_APP_ABOUT) );
  // ...
    return TRUE;
}


// WTL 方式 - 对话框数据交换(DDX) *****************************************
//将#include atlddx.h 添加到stdafx.h中,这样才可以使用DDX代码

//从CWindowImpl派生一个新类
class CEditImpl : public CWindowImpl<CEditImpl, CEdit>
{
    BEGIN_MSG_MAP_EX(CEditImpl)
        MSG_WM_CONTEXTMENU(OnContextMenu)  //处理WM_CONTEXTMENU消息
    END_MSG_MAP()
    void OnContextMenu ( HWND hwndCtrl, CPoint ptClick )
    {
        MessageBox("Edit control handled WM_CONTEXTMENU");
    }
};

//将CWinDataExchange添加到继承列表中
class CMainDlg : public CDialogImpl<CMainDlg>,
                 public CWinDataExchange<CMainDlg>
{
 //...
    BEGIN_DDX_MAP(CMainDlg)
        DDX_CONTROL(IDC_EDIT, m_wndEdit) //DDX宏:使用DDX_CONTROL宏用来连接变量和控件
    END_DDX_MAP()
protected:
 // ...
    CEditImpl   m_wndEdit;
};

//调用DoDataExchange()函数完成相关控件的子类化工作
LRESULT CMainDlg::OnInitDialog(...)
{
 // ...
    // First DDX call, hooks up variables to controls.
    DoDataExchange(false); //子类化ID为IDC_EDIT的控件,将其与m_wndEdit建立关联
 /*
 //更新成功DoDataExchange()返回TRUE,失败返回FALSE (DoDataExchange():CWinDataExchange)
 //处理数据交换错误:CWinDataExchange::OnDataExchangeError()--作用仅是驱动PC喇叭发出一声蜂鸣并将出错的控件设为当前焦点
           OnDataValidateError()--在DDV中
 BOOL DoDataExchange ( BOOL bSaveAndValidate = FALSE, UINT nCtlID = (UINT)-1 )
 bSaveAndValidate :
  TRUE:数据从 控件 传输给 变量。
  FALSE:数据从 变量 传输给 控件。
  默认值:FALSE。为了方便记忆,可以使用DDX_SAVE 和 DDX_LOAD标号(分别表示TRUE和FALSE)。
 nCtlID :
  使用-1可以更新所有控件,如果只想DDX宏作用于一个控件就使用控件的ID。
 */
    // ...
}

//DDX 6种宏-------------------------
DDX_CONTROL
 用来连接CWindowImpl派生控件类对象的
DDX_TEXT
 在字符串和edit box控件之间传输数据,变量类型可以是CString, BSTR, CComBSTR或者静态分配的字符串数组,但是不能使用new动态分配的数组。
DDX_INT
 在edit box控件和数字变量之间传输int型数据。
DDX_UINT
 在edit box控件和数字变量之间传输无符号int型数据。
DDX_CHECK
 在check box控件和int型变量之间转换check box控件的状态。 未选择BST_UNCHECKED (0),选择BST_CHECKED (1),不确定BST_INDETERMINATE (2)
 判断关联的变量0|1,(e.g.) DoDataExchange(true) if ( m_nShowMsg ){...}
DDX_RADIO
 在radio buttons控件组和int型变量之间转换radio buttons控件组的状态。
DDX_FLOAT
 在edit box控件和数字变量之间传输浮点型(float)数据或双精度型数据(double)。
 特殊:要使用DDX_FLOAT宏需要在stdafx.h文件的所有WTL头文件包含之前添加一行定义:
 #define _ATL_USE_DDX_FLOAT
 这个定义是必要的,因为默认状态为了优化程序的大小而不支持浮点数。

 

//额外使用的函数
m_wndList.DeleteAllItems();
str.Format ( _T("%d"), m_nEditNumber );
m_wndList.InsertItem ( 1, _T("DDX_INT") );
m_wndList.SetItemText ( 1, 1, str );
MessageBox ( str, _T("ControlMania1"), MB_ICONWARNING );
::SetFocus ( GetDlgItem(nCtrlID) );


//在父窗口(对话框)中响应控件的通知消息
//消息映射宏------------------------------------------------------
//在消息映射链中使用BEGIN_MSG_MAP_EX 要包含atlcrack.h文件
两种消息类型 WM_NOTIFY & WM_COMMAND
WM_COMMAND(控件ID,控件窗口句柄,通知代码)
WM_NOTIFY(控件ID,控件窗口句柄,通知代码,NMHDR数据结构的指针)

//处理WM_COMMAND消息的宏:
COMMAND_HANDLER_EX(id, code, func)
 处理从某个控件发送得某个通知代码。
COMMAND_ID_HANDLER_EX(id, func)
 处理从某个控件发送得所有通知代码。
COMMAND_CODE_HANDLER_EX(code, func)
 处理某个通知代码得所有消息,不管是从那个控件发出的。
COMMAND_RANGE_HANDLER_EX(idFirst, idLast, func)
 处理ID在idFirst和idLast之间得控件发送的所有通知代码。
COMMAND_RANGE_CODE_HANDLER_EX(idFirst, idLast, code, func)
 处理ID在idFirst和idLast之间得控件发送的某个通知代码。
//处理WM_NOTIFY消息的宏:
// 和WM_COMMAND的宏功能类似,只是它们的名字开头以“NOTIFY_”代替“COMMAND_”
// 反射宏在上面这两种宏的基础上,只是前面加了“REFLECTED_”,例如REFLECTED_COMMAND_HANDLER_EX,REFLECTED_NOTIFY_HANDLER_EX

// WM_COMMAND 消息处理函数原型
void func ( UINT uCode, int nCtrlID, HWND hwndCtrl );


// WM_NOTIFY 消息处理函数原型
LRESULT func ( NMHDR* phdr );
struct NMHDR {
 code  //通知代码
 hendFrom //发送通知消息的控件的窗口句柄
}
// e.g.
class CMainDlg : public ...
{
    BEGIN_MSG_MAP_EX(CMainDlg)
        NOTIFY_HANDLER_EX(IDC_LIST, LVN_ITEMCHANGED, OnListItemchanged)
    END_MSG_MAP()
    LRESULT OnListItemchanged(NMHDR* phdr);
//...
};
下面是消息处理函数:
LRESULT CMainDlg::OnListItemchanged ( NMHDR* phdr )
{
NMLISTVIEW* pnmlv = (NMLISTVIEW*) phdr; //处理函数要phdr参数转换成正确类型
//...
}


//在控件类的内部处理通知消息
//反射通知消息------------------------------------------------------
//要对话框将通知消息反射给控件封装类,要在对话框的消息映射链中添加 REFLECT_NOTIFICATIONS()宏:
class CMainDlg : public ...
{
public:
    BEGIN_MSG_MAP_EX(CMainDlg)
        //...
        NOTIFY_HANDLER_EX(IDC_LIST, LVN_ITEMCHANGED, OnListItemchanged)
        REFLECT_NOTIFICATIONS()
    END_MSG_MAP()
};

//控件类添加相应的消息处理代码
class CODButtonImpl : public CWindowImpl<CODButtonImpl, CButton>
{
public:
    BEGIN_MSG_MAP_EX(CODButtonImpl)
        MSG_OCM_DRAWITEM(OnDrawItem) //反射的消息,代码值用OCM_xxx代替了WM_xxx,即是这里的OCM_DRAWITEM消息 (MSG_OCM_DRAWITEM处理宏)
        DEFAULT_REFLECTION_HANDLER() //将未被处理的消息交给DefWindowProc()正确处理
    END_MSG_MAP()
    void OnDrawItem ( UINT idCtrl, LPDRAWITEMSTRUCT lpdis )
    {
        // do drawing here...
    }
};

/*
有18中被反射的消息:
 控件通知消息: WM_COMMAND, WM_NOTIFY, WM_PARENTNOTIFY
 自画消息: WM_DRAWITEM, WM_MEASUREITEM, WM_COMPAREITEM, WM_DELETEITEM
 List box 键盘消息: WM_VKEYTOITEM, WM_CHARTOITEM
 其它: WM_HSCROLL, WM_VSCROLL, WM_CTLCOLOR*
反射后的消息用OCM_xxx代替了WM_xxx
*/


//用来处理反射消息的WTL宏------------------------------------------------------
//由于WM_NOTIFY和WM_COMMAND消息带的参数需要展开 (上面例子处理的只是WM_DRAWITEM)
//所以WTL提供了特殊的宏MSG_OCM_COMMAND和MSG_OCM_NOTIFY做这些事情
//与COMMAND_HANDLER_EX和NOTIFY_HANDLER_EX宏相同,只是前面加了“REFLECTED_”
//反射宏在上面这两种宏的基础上,只是前面加了“REFLECTED_”,例如REFLECTED_COMMAND_HANDLER_EX,REFLECTED_NOTIFY_HANDLER_EX

 


/****************************************************************************************************************/
//高级对话框用户界面类
//控件自画要用无模式对话框

//COwnerDraw类 (atlframe.h)-----------------------------------------------------------------------
//控件自画 要响应四个消息:WM_MEASUREITEM, WM_DRAWITEM, WM_COMPAREITEM, WM_DELETEITEM
//COwnerDraw类已经处理这四个消息 (只需将消息链入COwnerDraw,它会调用你的类中的重载函数)
template <class T> class COwnerDraw
{
//...
public:
  BEGIN_MSG_MAP(COwnerDraw<T>)
    //处理 WM_* 消息
    MESSAGE_HANDLER(WM_DRAWITEM, OnDrawItem)
    MESSAGE_HANDLER(WM_MEASUREITEM, OnMeasureItem)
    MESSAGE_HANDLER(WM_COMPAREITEM, OnCompareItem)
    MESSAGE_HANDLER(WM_DELETEITEM, OnDeleteItem)
  ALT_MSG_MAP(1)   //处理反射消息 OCM_*
    MESSAGE_HANDLER(OCM_DRAWITEM, OnDrawItem)
    MESSAGE_HANDLER(OCM_MEASUREITEM, OnMeasureItem)
    MESSAGE_HANDLER(OCM_COMPAREITEM, OnCompareItem)
    MESSAGE_HANDLER(OCM_DELETEITEM, OnDeleteItem)
  END_MSG_MAP()
//...
 void SetMsgHandled(BOOL bHandled) //如果不想处理某个消息,可以调用SetMsgHandled(false),消息会被传递给消息映射链中的其他响应者
 {
  m_bHandledOD = bHandled;
 }
};

//使用方法:
//父窗口对话框处理消息
class CSomeDlg : public COwnerDraw<CSomeDlg>, ...
{
  BEGIN_MSG_MAP(CSomeDlg)
    //...
    CHAIN_MSG_MAP(COwnerDraw<CSomeDlg>)
  END_MSG_MAP()
  void DrawItem ( LPDRAWITEMSTRUCT lpdis );  //重载后的方法
};
//反射到控件处理消息,添加CHAIN_MSG_MAP_ALT宏,消息就会进入ALT_MSG_MAP(1)部分
class CSomeButtonImpl : public COwnerDraw<CSomeButtonImpl>, ...  //将COwnerDraw类添加到继承列表
{
  BEGIN_MSG_MAP(CSomeButtonImpl)
    //...
    CHAIN_MSG_MAP_ALT(COwnerDraw<CSomeButtonImpl>, 1) //新添加的宏
    DEFAULT_REFLECTION_HANDLER() //将未被处理的消息交给DefWindowProc()正确处理,反射消息都要添加这个宏
  END_MSG_MAP()
  void DrawItem ( LPDRAWITEMSTRUCT lpdis );  //重载后的方法
};

//对应4个消息,可以重载的4个方法
void DrawItem(LPDRAWITEMSTRUCT lpDrawItemStruct);
void MeasureItem(LPMEASUREITEMSTRUCT lpMeasureItemStruct);
int  CompareItem(LPCOMPAREITEMSTRUCT lpCompareItemStruct);
void DeleteItem(LPDELETEITEMSTRUCT lpDeleteItemStruct);


//CCustomDraw类-----------------------------------------------------------------------
//用于处理NM_CUSTOMDRAW消息,和COwnerDraw类相同的方式
//CCustomDraw类的SetMsgHandled()函数,和COwnerDraw类那个一样 (如果不想处理某个消息,可以调用SetMsgHandled(false),消息会被传递给消息映射链中的其他响应者)
//重载函数: (默认返回CDRF_DODEFAULT,如果想自画控件或返回一个不同的值,就需要重载这些函数)
DWORD OnPrePaint(int idCtrl, LPNMCUSTOMDRAW lpNMCD);
DWORD OnPostPaint(int idCtrl, LPNMCUSTOMDRAW lpNMCD);
DWORD OnPreErase(int idCtrl, LPNMCUSTOMDRAW lpNMCD);
DWORD OnPostErase(int idCtrl, LPNMCUSTOMDRAW lpNMCD);
DWORD OnItemPrePaint(int idCtrl, LPNMCUSTOMDRAW lpNMCD);
DWORD OnItemPostPaint(int idCtrl, LPNMCUSTOMDRAW lpNMCD);
DWORD OnItemPreErase(int idCtrl, LPNMCUSTOMDRAW lpNMCD);
DWORD OnItemPostEraset(int idCtrl, LPNMCUSTOMDRAW lpNMCD);
DWORD OnSubItemPrePaint(int idCtrl, LPNMCUSTOMDRAW lpNMCD);


//CBitmapButton类 (atlctrlx.h) -----------------------------------------------------------------------

//CBitmapButtonImpl(DWORD dwExtendedStyle = BMPBTN_AUTOSIZE,HIMAGELIST hImageList = NULL)
class CBitmapButton : public CBitmapButtonImpl<CBitmapButton>
{
 BOOL SubclassWindow(HWND hWnd)   //完成控件的子类化和初始化控件类保有的内部数据
 DWORD GetBitmapButtonExtendedStyle() //获取扩展样式
 DWORD SetBitmapButtonExtendedStyle(DWORD dwExtendedStyle, DWORD dwMask = 0) //设置扩展样式
 {
  /*
  BMPBTN_HOVER
   使用hot-tracking,当鼠标移到按钮上时按钮被画成焦点状态。
  BMPBTN_AUTO3D_SINGLE, BMPBTN_AUTO3D_DOUBLE
   在按钮图像周围自动产生一个三维边框,当按钮拥有焦点时会显示一个表示焦点的虚线矩形框。另外如果你没有指定按钮按下状态的图像,将会自动生成一个。BMPBTN_AUTO3D_DOUBLE样式生成的边框稍微粗一些,其他特征和BMPBTN_AUTO3D_SINGLE一样。
  BMPBTN_AUTOSIZE
   按钮调整自己的大小以适应图像大小,这是默认样式。
  BMPBTN_SHAREIMAGELISTS
   如果指定这个样式,按钮不负责销毁按钮使用的image list,如果不使用这个样式,CBitmapButton的析构函数会销毁按钮使用的image list。
  BMPBTN_AUTOFIRE
   如果设置这个样式,在按钮上按住鼠标左键不放将会产生连续的WM_COMMAND消息。
   调用SetBitmapButtonExtendedStyle()时,dwMask参数控制着那个样式将被改变,默认值是0,意味着用新样式完全替换旧的样式。
  */
 }
 HIMAGELIST GetImageList() //获取按钮使用的image list
 HIMAGELIST SetImageList(HIMAGELIST hImageList) //设置按钮使用的image list
 int  GetToolTipTextLength()        //获取显示工具提示文字长度
 bool GetToolTipText(LPTSTR lpstrText, int nLength)  //显示工具提示
 bool SetToolTipText(LPCTSTR lpstrText)     //设置显示工具提示文字
 void SetImages(int nNormal, int nPushed = -1,int nFocusOrHover = -1, int nDisabled = -1) //设置image list
}

 

//CCheckListViewCtrl() (atlctrlx.h) -----------------------------------------------------------------------
//实现了一个带检查框的list view控件
//如果不设置CCheckListViewCtrlImplTraits辅助类扩展样式属性,将使用样式:LVS_EX_CHECKBOXES | LVS_EX_FULLROWSELECT
chass CCheckListViewCtrl():CWindowImpl()
{
 CCheckListViewCtrlImplTraits() //扩展属性必须包含LVS_EX_CHECKBOXES,否则会因起断言错误消息
 SubclassWindow()    //查看CCheckListViewCtrlImplTraits的扩展样式属性并将之应用到控件上
 SetCheckState()     //使用行的索引和一个布尔类型参数,该布尔参数的值表示是否check这一行
 GetCheckState()     //以行索引未参数,返回改行的checked状态
 CheckSelectedItems()   //翻转这个item的check状态,这个item必须是被选定的,同时还将其他所有被选择的item设置成相应状态
         //*在check box被单击或用户按下了空格键时设置相应的item的状态*
}


//CTreeItem() & CTreeViewCtrlEx() ----
//CTreeItem类封装了HTREEITEM
class CTreeItem()
{
    AddTail();
    SetData();
 //...
}
//CTreeViewCtrlEx的方法操作CTreeItem而不是HTREEITEM (CTreeViewCtrl操作HTREEITEM)
class CTreeViewCtrlEx()
{
 InsertItem();
 //...
}


//CHyperLink() 可点击的超链接 ----
class CHyperLink()
{
 CHyperLinkImpl() //供重载用
 SubclassWindow() //完成控件子类化,然后初始化该类保有的内部数据
 bool GetLabel(LPTSTR lpstrBuffer, int nLength) //获得控件显示的文字
 bool SetLabel(LPCTSTR lpstrLabel)    //设置控件显示的文字
 bool GetHyperLink(LPTSTR lpstrBuffer, int nLength) //获得控件关联超链接的URL
 bool SetHyperLink(LPCTSTR lpstrLink)   //设置控件关联超链接的URL
 bool Navigate()  //导航到当前超链接的URL,该URL或者是由SetHyperLink()函数指定的URL,或者就是控件的窗口文字
 //设置链接提示
 //直接使用CToolTipCtrl成员m_tip
}


//对话框中控件的UI Updating----------------------------------------------
/*
 CUpdateUI 的对话框必须是无模式,需要在程序的消息循环控制下工作。
 模式对话框:系统处理消息循环,空闲处理函数不会调用我们的程序。而 CUpdateUI 是在空闲时间工作
*/
// 无模式对话框,OnIdle()函数漏了一句代码:UIUpdateChildWindows();
class CMainDlg : public CDialogImpl<CMainDlg>, public CUpdateUI<CMainDlg>,
                 public CMessageFilter, public CIdleHandler
{
 //...
 LRESULT CMainDlg::OnInitDialog(...)
 {
  //CMainDlg类从CUpdateUI派生并含有一个update UI链,OnInitDialog()做了这些工作
     CMessageLoop* pLoop = _Module.GetMessageLoop();
     ATLASSERT(pLoop != NULL);
     pLoop->AddMessageFilter(this);
     pLoop->AddIdleHandler(this);
  // ...
  UIAddChildWindowContainer(m_hWnd); //告诉CUpdateUI我们的对话框含有需要updating的字窗口
 }
 
 BOOL CMainDlg::OnIdle()
 {
     UIUpdateChildWindows();  //漏掉的一句代码,要自行添加
     return FALSE;
 }
 //...
}


//DDV 对话框数据验证------------------------------------------------------
//DDV链中三个数据验证宏:(结合DDX宏比较)
DDX_TEXT_LEN
 和DDX_TEXT一样,只是还要验证字符串的长度(不包含结尾的空字符)小于或等于限制长度。
DDX_INT_RANGE and DDX_UINT_RANGE
 和DDX_INT,DDX_UINT一样,还加了对数字的最大最小值的验证。
DDX_FLOAT_RANGE
 除了像DDX_FLOAT一样完成数据交换之外,还验证数字的最大最小值。

// e.g.
BEGIN_DDX_MAP(CMainDlg)
 //...
 DDX_INT_RANGE(IDC_FAV_SEASON, m_nSeason, 1, 7) //假设有效值1-7,验证是在1-7之间
END_DDX_MAP()

//处理DDV验证失败 ----
//验证失败:CWinDataExchange::OnDataValidateError()--默认是驱动PC喇叭发声
//或者重载OnDataValidateError输出其他错误提示
void OnDataValidateError ( UINT nCtrlID, BOOL bSave, _XData& data );
struct _XData
{
    _XDataType nDataType;
    //nDataType指示联合中的三个成员那个是有意义的,nDataType 的取值可以是:
    /*
 enum _XDataType
 {
     ddxDataNull = 0,
     ddxDataText = 1,
     ddxDataInt = 2,
     ddxDataFloat = 3,
     ddxDataDouble = 4
 };
   
    */
    union
    {
        _XTextData textData; //每种类型也是一个结构 (按_XDataType的结构来看,应该有4个这些结构)
        _XIntData intData;
        /* e.g.
  struct _XIntData
  {
      long nVal;
      long nMin;
      long nMax;
  };
  */       
        _XFloatData floatData;
    };
};

// 例子:
//重载OnDataValidateError()函数,显示错误信息并告诉用户允许的数值范围:
void CMainDlg::OnDataValidateError ( UINT nCtrlID, BOOL bSave, _XData& data )
{
 CString sMsg;
    sMsg.Format ( _T("Enter a number between %d and %d"),
                  data.intData.nMin, data.intData.nMax );  //显示数值范围,就是读取出结构的值
    MessageBox ( sMsg, _T("ControlMania2"), MB_ICONEXCLAMATION );
    ::SetFocus ( GetDlgItem(nCtrlID) );
}


//改变对话框的大小 ------------------------------------------------------
//详细信息自己查询
1) 将CDialogResize类添加到对话框的继承列表
2) 在OnInitDialog()中调用DlgResize_Init()
3) 然后将消息链入CDialogResize

 


/****************************************************************************************************************/
//分隔窗口
(atlsplit.h) 三个分隔窗口类:CSplitterImpl,CSplitterWindowImpl,CSplitterWindowT

CSplitterImpl //模板类
参数1:窗口界面类的类名
参数2:布尔型表示分隔窗口的方向:true表示垂直方向,false表示水平方向

CSplitterWindowImpl : CWindowImpl, CSplitterImpl

CSplitterWindowT : CSplitterImpl
窗口类名是:WTL_SplitterWindow


两个自定义数据类型取代上面三个类:
CSplitterWindow用于垂直分隔窗口
CHorSplitterWindow用于水平分隔窗口

//使用CSplitterWindow类
//(1)
class CMainFrame : public ...
{
//...
 //在CMainFrame类添加一个CSplitterWindow类型的变量
protected:
    CSplitterWindow  m_wndVertSplit;
};

//(2)
LRESULT CMainFrame::OnCreate ( LPCREATESTRUCT lpcs )
{
//...
    // Create the splitter window
const DWORD dwSplitStyle = WS_CHILD | WS_VISIBLE |
                           WS_CLIPCHILDREN | WS_CLIPSIBLINGS,
            dwSplitExStyle = WS_EX_CLIENTEDGE;
    m_wndVertSplit.Create ( *this, rcDefault, NULL,   //创建分隔窗口作为主窗口的子窗口
                            dwSplitStyle, dwSplitExStyle );
    m_hWndClient = m_wndVertSplit;       //将分隔窗口设置为主窗口的客户区窗口
    UpdateLayout();  //将分隔窗口设置为初始时的大小,否则分隔窗口的大小将不确定,
         //可能小于200个象素点的宽度,导致SetSplitterPos()出现意想不到的结果 (除非预先获得正确的客户区坐标)
    m_wndVertSplit.SetSplitterPos ( 200 );
    return 0;
}

LRESULT CMainFrame::OnCreate ( LPCREATESTRUCT lpcs )
{
 // ...
 const DWORD dwSplitStyle = WS_CHILD | WS_VISIBLE |
                             WS_CLIPCHILDREN | WS_CLIPSIBLINGS,
            dwSplitExStyle = WS_EX_CLIENTEDGE;
    m_wndSplit.Create ( *this, rcDefault, NULL, dwSplitStyle, dwSplitExStyle ); 
    m_hWndClient = m_wndSplit;  /
    //...
}
//(3)成员函数
bool SetSplitterPos(int xyPos = -1, bool bUpdate = true) //设置分隔条的位置
 xyPos:设置分隔条的位置,表示距离分隔窗口的上边界(水平分隔窗口)或左边界(垂直分隔窗口)有多少个象素点。默认值-1将分隔条设置到分隔窗口的中间
 bUpdate:true表示在移动分隔条之后相应的改变两个窗格大小
int GetSplitterPos() //返回当前分隔条的位置,这个位置也是相对于分隔窗口的上边界或左边界

bool SetSinglePaneMode(int nPane = SPLIT_PANE_NONE) //设置分隔窗口显示方式
nPane= SPLIT_PANE_LEFT  //显示左边
  SPLIT_PANE_RIGHT //显示右边
  SPLIT_PANE_TOP  //显示上边
  SPLIT_PANE_BOTTOM //显示下边
  SPLIT_PANE_NONE  //两个都显示
int GetSinglePaneMode()  //返回五个SPLIT_PANE_*值中的一个表示当前的模式

DWORD SetSplitterExtendedStyle(DWORD dwExtendedStyle, DWORD dwMask = 0)  //设置当整个分隔窗口改变大小时如何移动分隔条
 SPLIT_PROPORTIONAL: 两个窗格一起改变大小 //默认值
 SPLIT_RIGHTALIGNED: 右边的窗格保持大小不变,只改变左边的窗格大小
 SPLIT_BOTTOMALIGNED: 下部的窗格保持大小不变,只改变上边的窗格大小
 //如果上面4个样式都不使用,分隔窗口会变成左对齐或上对齐
 //如果上面4个样式都使用,则优先选用SPLIT_PROPORTIONAL样式
 //下面的样式用来控制分隔条是否可以被用户移动
 SPLIT_NONINTERACTIVE:分隔条不能被移动并且不相应鼠标
DWORD GetSplitterExtendedStyle() //返回当前扩展样式

bool SetActivePane(int nPane) //将分隔窗口的某个子窗口设置为当前焦点窗口,nPane是SPLIT_PANE_*类型的值 (在上面 e.g. SPLIT_PANE_LEFT)
int GetActivePane()    //返回SPLIT_PANE_*类型值,表示焦点在那个窗口。SPLIT_PANE_NONE表示当前拥有焦点的窗口不是窗格的子窗口

bool ActivateNextPane(bool bNext = true)
/*
如果是单窗格,焦点被设到可见窗格上,
否则的话,ActivateNextPane()函数将调用GetActivePane()查看拥有焦点的窗口。
如果一个窗格(或窗格内的子窗口)拥有检点,分隔窗口就将焦点设给另一个窗格,
否则ActivateNextPane()将判断bNext的值,如果是true就激活left/top窗格,如果是false则激活right/bottom窗格。
*/

bool SetDefaultActivePane(int nPane) //设置默认的活动窗格,SPLIT_PANE_*类型的值
bool SetDefaultActivePane(HWND hWnd) //重载:窗口的句柄
int GetDefaultActivePane()    //返回SPLIT_PANE_*类型值表示当前默认活动窗格

void GetSystemSettings(bool bUpdate) //在OnCreate()函数中自动调用 & 在CSplitterWindow响应WM_SETTINGCHANGE消息自动调用
 bUpdate:true,分隔窗口会根据新的设置重画自己

GetSystemMetrics(SM_CXSIZEFRAME) //返回垂直分隔条的宽度
GetSystemMetrics(SM_CYSIZEFRAME) //返回水平分隔条的宽度
SystemParametersInfo(SPI_GETDRAGFULLWINDOWS) //返回true:拖动分隔条时格大小跟着调整。false:显示一个分隔条的影子

//(4)成员变量
//下面变量在调用GetSystemSettings()时被重置
m_cxySplitBar: 控制分隔条的宽度(垂直分隔条)和高度(水平分隔条)。
    默认值是通过调用GetSystemMetrics(SM_CXSIZEFRAME)(垂直分隔条)或GetSystemMetrics(SM_CYSIZEFRAME)(水平分隔条)得到的。
m_cxyMin:  控制每个窗格的最小宽度(垂直分隔)和最小高度(水平分隔)。
    分隔窗口不允许拖动比这更小的宽度或高度。如果分隔窗口有WS_EX_CLIENTEDGE扩展属性,则这个变量的默认值是0,否则其默认值是2*GetSystemMetrics(SM_CXEDGE)(垂直分隔)或2*GetSystemMetrics(SM_CYEDGE)(水平分隔)。
m_cxyBarEdge: 控制画在分隔条两侧的3D边界的宽度(垂直分隔)或高度(水平分隔),其默认值刚好和m_cxyMin相反。
m_bFullDrag: true:当分隔条被拖动时窗格大小跟着调整
    false:拖动时只显示一个分隔条的影子,直到拖动停止才调整窗格的大小。默认值是调用SystemParametersInfo(SPI_GETDRAGFULLWINDOWS)函数的返回值。


//消息处理------------------------------------------------------------
//list窗口发消息到分隔窗口,分隔窗口转发到父窗口。父窗口直接反射消息到list窗口,不经过分隔窗口
//list窗口和主框架窗口消息传递与,添加和移除分隔窗口,相互无影响
CSplitterWindowImpl 分隔窗口消息链中:
BEGIN_MSG_MAP(thisClass)
 MESSAGE_HANDLER(WM_ERASEBKGND, OnEraseBackground)
 MESSAGE_HANDLER(WM_SIZE, OnSize)
 CHAIN_MSG_MAP(baseClass)
 FORWARD_NOTIFICATIONS()  //将通知消息发送到分隔窗口的父窗口
END_MSG_MAP()


//窗格容器 (像Explorer中左边的窗格那样,顶部有一个可以显示文字的区域,还有一个可选择是否显示的Close按钮)------------------------------------------------------------
//实现窗格容器两个类:
CPaneContainerImpl //供重载使用
CPaneContainer  //通常使用这个

//成员函数
HWND Create(
    HWND hWndParent, LPCTSTR lpstrTitle = NULL,
    DWORD dwStyle = WS_CHILD | WS_VISIBLE | WS_CLIPSIBLINGS | WS_CLIPCHILDREN,
    DWORD dwExStyle = 0, UINT nID = 0, LPVOID lpCreateParam = NULL)
HWND Create(
    HWND hWndParent, UINT uTitleID,
    DWORD dwStyle = WS_CHILD | WS_VISIBLE | WS_CLIPSIBLINGS | WS_CLIPCHILDREN,
    DWORD dwExStyle = 0, UINT nID = 0, LPVOID lpCreateParam = NULL)
//函数1 - 参数1:字符串作为容器顶部区域显示的文字。参数2:字符串资源ID。其他参数:用默认值

DWORD SetPaneContainerExtendedStyle(DWORD dwExtendedStyle, DWORD dwMask = 0) //设置扩展样式
//SetPaneContainerExtendedStyle()函数有BUG,导致窗格从水平切换到垂直时没有调用派生类的CalcSize()方法,可以将CalcSize()调用改为pT->CalcSize()来修补这个BUG
DWORD GetPaneContainerExtendedStyle()           //返回扩展样式
dwExtendedStyle://(默认值0,表示容器窗口是水平放置的,有Close按钮)
 PANECNT_NOCLOSEBUTTON:去掉顶部Close按钮
 PANECNT_VERTICAL:顶部的文字区域将沿着容器窗口的左边界垂直放置

HWND SetClient(HWND hWndClient) //将一个子窗口指派给窗格容器,返回原来客户区窗口句柄 (与CSplitterWindow::SetSplitterPane()方法作用类似)
HWND GetClient()    //返回当前客户区窗口句柄

BOOL SetTitle(LPCTSTR lpstrTitle)     //设置容器窗口顶部显示文字
BOOL GetTitle(LPTSTR lpstrTitle, int cchLength)  //返回当前窗口顶部区域显示文字
int GetTitleLength()        //返回当前显示文字的字符个数(不包括结尾的空字符)

BOOL EnableCloseButton(BOOL bEnable) //如果窗格容器有Close按钮,调用EnableCloseButton()来控制这个按钮的状态
SetSinglePaneMode()      //隐藏窗格

//关闭按钮和消息处理----------------------
// 窗格容器向父窗口发送一个WM_COMMAND消息,命令的ID是ID_PANE_CLOSE
//响应消息:调用SetSinglePaneMode()隐藏窗格

//CPaneContainer的消息链也用到FORWARD_NOTIFICATIONS()宏
//list控件/窗口发消息->窗格容器FORWARD->分隔窗口FORWARD->主窗口


//分隔窗口特殊绘制----------------------
//改变分隔条的外观----------------------
//可以从CSplitterWindowImpl派生新类,并重载DrawSplitterBar()函数
// e.g.
template <bool t_bVertical = true>
class CMySplitterWindowT : public CSplitterWindowImpl<CMySplitterWindowT<t_bVertical>, t_bVertical> //从CSplitterWindowImpl派生
{
public:
    DECLARE_WND_CLASS_EX(_T("My_SplitterWindow"),
                         CS_DBLCLKS, COLOR_WINDOW)
    // Overrideables
    void DrawSplitterBar(CDCHandle dc)
    {
    RECT rect;
        if ( m_br.IsNull() )
            m_br.CreateHatchBrush ( HS_DIAGCROSS,
                                    t_bVertical ? RGB(255,0,0)
                                                : RGB(0,0,255) );
        if ( GetSplitterBarRect ( &rect ) )
        {
            dc.FillRect ( &rect, m_br );
            // draw 3D edge if needed
            if ( (GetExStyle() & WS_EX_CLIENTEDGE) != 0)
                dc.DrawEdge(&rect, EDGE_RAISED,
                            t_bVertical ? (BF_LEFT | BF_RIGHT)
                                        : (BF_TOP | BF_BOTTOM));
        }
    }
protected:
    CBrush m_br;
};
typedef CMySplitterWindowT<true>    CMySplitterWindow;
typedef CMySplitterWindowT<false>   CMyHorSplitterWindow;


//窗格容器特殊绘制----------------------
//需要重载CPaneContainer的函数,可以从CPaneContainerImpl派生新类并重载
// e.g.
class CMyPaneContainer : public CPaneContainerImpl<CMyPaneContainer>
{
public:
    DECLARE_WND_CLASS_EX(_T("My_PaneContainer"), 0, -1)
//... overrides here ...
};

//可供重载的函数
void CalcSize() 
 //该函数为了设置m_cxyHeader变量,这个变量控制着窗格容器的顶部区域的宽度和高度
 //SetPaneContainerExtendedStyle()函数有BUG,导致窗格从水平切换到垂直时没有调用派生类的CalcSize()方法,可以将CalcSize()调用改为pT->CalcSize()来修补这个BUG
HFONT GetTitleFont()
 //绘画顶部区域文字
 //内部是调用GetStockObject(DEFAULT_GUI_FONT)得到MS Sans Serif作为默认字体,可以重载GetTitleFont()返回Tahoma字体or其他
BOOL GetToolTipText(LPNMHDR lpnmh)
 //设置鼠标移到Close按钮时弹出的提示信息
 //是TTN_GETDISPINFO的相应函数,可以将lpnmh转换成NMTTDISPINFO*,并设置相应的结构成员变量
 //但必须检查通知代码,可能是TTN_GETDISPINFO或TTN_GETDISPINFOW,两个不同的数据结构
void DrawPaneTitle(CDCHandle dc)
 //绘画顶部区域(颜色)
 //可以配合使用GetClientRect()和m_cxyHeader来计算顶部区域的范围
 // e.g.
 void CMyPaneContainer::DrawPaneTitle ( CDCHandle dc )
 {
  RECT rect;
  GetClientRect(&rect);
  TRIVERTEX tv[] = {
   { rect.left, rect.top, 0xff00 },
   { rect.right, rect.top + m_cxyHeader, 0, 0xff00 }
  };
  GRADIENT_RECT gr = { 0, 1 };
  dc.GradientFill ( tv, 2, &gr, 1, GRADIENT_FILL_RECT_H );
 }


//在状态栏显示进度条--------------------------------------------
/*
步骤:
1. 得到状态条第一个窗格得坐标范围RECT
2. 创建一个进展条作为状态条得子窗口,窗口大小就是哪个状态条窗格得大小
3. 随着edit控件被填充的同时更新进展条的位置
*/

 


/****************************************************************************************************************/
//属性页与向导

//WTL 的属性表类--------------------------------------------------------------------------------------------------
(atldlgs.h)
CPropertySheetWindow 
/*
窗口接口类,封装了各种PSM_* 消息处理,详细查看atldlgs.h,
e.g. SetActivePageByID()封装PSM_SETCURSELID消息
*/
CPropertySheetImpl  
/*
包含消息映射链和窗口的完整实现
管理一个PROPSHEETHEADER结构 和 一个HPROPSHEETPAGE类型数组
还提供一些方法填充PROPSHEETHEADER结构,添加或删除属性页
使用m_psh成员变量操作PROPSHEETHEADER结构
*/
CPropertySheet //是CPropertySheetImpl类一个特例,可以直接使用它而不需要定制整个属性表

// CPropertySheetImpl 方法 (查看atldlgs.h中完整函数清单)-------------------------
CPropertySheetImpl(_U_STRINGorID title = (LPCTSTR) NULL, UINT uStartPage = 0, HWND hWndParent = NULL)
 _U_STRINGorID: WTL工具类,自动转换LPCTSTR和资源ID,e.g.下面都是正确
  CPropertySheetImpl mySheet ( IDS_SHEET_TITLE );  //IDS_SHEET_TITLE 是字符串的ID
  CPropertySheetImpl mySheet ( _T("My prop sheet") ); //字符串
 title:   标题栏文字
 uStartPage: 属性表启动时激活的属性页,是一个从0开始的索引
 hWndParent: 属性表父窗口句柄


BOOL AddPage(HPROPSHEETPAGE hPage)  //添加属性页,如果已经存在就用这个函数,已有属性页句柄 hPage
BOOL AddPage(LPCPROPSHEETPAGE pPage) //添加属性页,如果本来不存在就用这个函数

BOOL RemovePage(HPROPSHEETPAGE hPage) //使用属性页句柄移除属性页
BOOL RemovePage(int nPageIndex)   //使用属性页索引移除属性页

BOOL SetActivePage(HPROPSHEETPAGE hPage) //使用属性页句柄设置属性表的活动页面
BOOL SetActivePage(int nPageIndex)   //使用属性页索引设置属性表的活动页面

void SetTitle(LPCTSTR lpszText, UINT nStyle = 0 / PSH_PROPTITLE) //设置标题
 lpszText:标题文字
 nStyle:0 / PSH_PROPTITLE,如果是PSH_PROPTITLE样式,标题变成:“Properties for” + lpszText

void SetWizardMode() //设置PSH_WIZARD样式,将属性表改称向导模式,这个函数必须在属性表显示之前调用

void EnableHelp()  //设置PSH_HASHELP样式,将在属性表中添加帮助按钮。注意属性页中帮助按钮可用并提供帮助才能使之生效

INT_PTR DoModal(HWND hWndParent = ::GetActiveWindow())
 //创建并显示一个模式的属性表,返回正值表示操作成功,错误返回-1

HWND Create(HWND hWndParent = NULL)
 //创建并显示一个无模式的属性表,返回值是窗口的句柄,错误返回NULL


//WTL 的属性页类--------------------------------------------------------------------------------------------------

CPropertyPageWindow //窗口接口类
CPropertyPageImpl //实现类 CPropertyPageImpl:CDialogImplBaseT
 1. 管理一个PROPSHEETPAGE数据结构(保存在全局成员变量m_psp中)
 2. 处理所有 PSN_* 通知消息
CPropertyPage  //简单属性页面 (适用于无交互作用 about页面 or 介绍页面)
CAxPropertyPageImpl //ActiveX控件的属性页,代替CPropertyPageImpl
CAxPropertyPage  //ActiveX简单的页面,代替CPropertyPage


// CPropertyPageImpl 方法 ----------------------------------------------------------
CPropertyPageImpl(_U_STRINGorID title = (LPCTSTR) NULL)  //title:Tab标签的文字

HPROPSHEETPAGE Create()
//创建属性页面,内部只是用全局变量m_psp做参数调用了 CreatePropertySheetPage()
//可以向一个存在或不存在的属性表添加属性页
       
void SetTitle(_U_STRINGorID title)      //改变页面标签的文字
void SetHeaderTitle(LPCTSTR lpstrHeaderTitle)   //设置Wizard97样式的向导中属性页顶部的文字
void SetHeaderSubTitle(LPCTSTR lpstrHeaderSubTitle)  //设置Wizard97样式的向导中属性页顶部的文字
void EnableHelp()          //设置m_psp中的PSP_HASHELP标志,当本页面激活时使属性表的帮助按钮可用

//处理通知消息
//由于 WTL3 和 WTL7 设计存在两套不同的通知处理机制
//必须在中including atldlgs.h一行之前添加一行定义:
#define _WTL_NEW_PAGE_NOTIFY_HANDLERS

// CPropertyPageImpl 为所有消息提供了默认的通知消息处理函数,都可以自己重载
int OnSetActive() - 允许页面成为激活状态
BOOL OnKillActive() - 允许页面成为非激活状态
int OnApply() - 返回 PSNRET_NOERROR 表示应用操作成功完成
void OnReset() - 无相应的动作
BOOL OnQueryCancel() - 允许取消操作
int OnWizardBack() - 返回到前一个页面
int OnWizardNext() - 进行到下一个页面
INT_PTR OnWizardFinish() - 允许向导结束
void OnHelp() - 无相应的动作
BOOL OnGetObject(LPNMOBJECTNOTIFY lpObjectNotify) - 无相应的动作
int OnTranslateAccelerator(LPMSG lpMsg) - 返回 PSNRET_NOERROR 表示消息没有被处理
HWND OnQueryInitialFocus(HWND hWndFocus) - 返回 NULL 表示将按Tab Order顺序的第一个控件设为焦点状态


//创建一个属性表 事例-----------------------------------------------------------------
//CPropertySheet 和 CPropertyPage类 (最简单的属性表)
/*
步骤:
1. 用WTL的向导创建一个SDI工程,然后为关于对话框添加一个属性表
2. 去除OK按钮,因为属性表不希望属性页自己关闭。在Style Tab中将对话框样式改为Child,Thin Border,选择Title Bar,在More Styles tab,选择Disabled。
3. 在处理函数中创建一个非定制属性表,使用非定制的CPropertySheet 和 CPropertyPage类
*/
LRESULT CMainFrame::OnAppAbout(...) //处理函数例子
{
CPropertySheet sheet ( _T("About PSheets") );
CPropertyPage<IDD_ABOUTBOX> pgAbout;
 
    sheet.AddPage ( pgAbout );
    sheet.DoModal();
    return 0;
}

//CPropertyPageImpl (完整功能的属性表)
class CBackgroundOptsPage :
    public CPropertyPageImpl<CBackgroundOptsPage>,  //派生自CPropertyPageImpl
    public CWinDataExchange<CBackgroundOptsPage>  //支持DDX
{
public:
    enum { IDD = IDD_BACKGROUND_OPTS };     //IDD的公有成员将对话框于资源联系起来
 
    // Construction
    CBackgroundOptsPage();
    ~CBackgroundOptsPage();
 
    // Maps (消息映射链和CDialogImpl相似)
    BEGIN_MSG_MAP(CBackgroundOptsPage)
        MSG_WM_INITDIALOG(OnInitDialog)
        CHAIN_MSG_MAP(CPropertyPageImpl<CBackgroundOptsPage>) //将消息链入CPropertyPageImpl,能够处理与属性表相关的通知消息
    END_MSG_MAP()
 
    BEGIN_DDX_MAP(CBackgroundOptsPage)
        DDX_RADIO(IDC_BLUE, m_nColor)
        DDX_RADIO(IDC_ALYSON, m_nPicture)
    END_DDX_MAP()
 
    // Message handlers
    BOOL OnInitDialog ( HWND hwndFocus, LPARAM lParam );
 
    // Property page notification handlers
    int OnApply();       //例子中的按钮响应函数
 /*int CBackgroundOptsPage::OnApply() //单击属性表中OK按钮时保存用户的选择
 {
     return DoDataExchange(true) ? PSNRET_NOERROR : PSNRET_INVALID; //更新 DDX 变量,然后返回一个代码标识是否可以关闭这个属性表
 }*/
   
 
    // DDX variables
    int m_nColor, m_nPicture;
};


//添加一个菜单选项来调用创建属性表 (方法1 非优化代码安排)
void CMainFrame::OnOptions ( UINT uCode, int nID, HWND hwndCtrl )
{
CPropertySheet sheet ( _T("PSheets Options"), 0 ); //索引是0的页面初始是可见
CBackgroundOptsPage pgBackground;
CPropertyPage<IDD_ABOUTBOX> pgAbout;
 
    pgBackground.m_nColor = m_view.m_nColor;
    pgBackground.m_nPicture = m_view.m_nPicture;
 
    sheet.m_psh.dwFlags |= PSH_NOAPPLYNOW;
 
    sheet.AddPage ( pgBackground );
    sheet.AddPage ( pgAbout );
 
    if ( IDOK == sheet.DoModal() )    //DoModal建表啦
        m_view.SetBackgroundOptions ( pgBackground.m_nColor,
                                      pgBackground.m_nPicture );
}

//添加一个菜单选项来调用创建属性表 (方法2)
//首先从CPropertySheetImpl派生一个新类,在这个类中完成初始化代码任务
class CAppPropertySheet : public CPropertySheetImpl<CAppPropertySheet>
{
public:
    // Construction
    CAppPropertySheet ( _U_STRINGorID title = (LPCTSTR) NULL,
                        UINT uStartPage = 0, HWND hWndParent = NULL );
    // Maps
    BEGIN_MSG_MAP(CAppPropertySheet)
        CHAIN_MSG_MAP(CPropertySheetImpl<CAppPropertySheet>)
    END_MSG_MAP()
    // Property pages
    CBackgroundOptsPage         m_pgBackground;
    CPropertyPage<IDD_ABOUTBOX> m_pgAbout;
};

//然后在构造函数完成添加页面,并设置其他必需的标志:
CAppPropertySheet::CAppPropertySheet ( _U_STRINGorID title, UINT uStartPage,
                                       HWND hWndParent ) :
    CPropertySheetImpl<CAppPropertySheet> ( title, uStartPage, hWndParent )
{
    m_psh.dwFlags |= PSH_NOAPPLYNOW;
    AddPage ( m_pgBackground );
    AddPage ( m_pgAbout );
}

//最后在菜单选项调用派生类CAppPropertySheet
void CMainFrame::OnOptions ( UINT uCode, int nID, HWND hwndCtrl )
{
CAppPropertySheet sheet ( _T("PSheets Options"), 0 );
    sheet.m_pgBackground.m_nColor = m_view.m_nColor;
    sheet.m_pgBackground.m_nPicture = m_view.m_nPicture;
    if ( IDOK == sheet.DoModal() )
        m_view.SetBackgroundOptions ( sheet.m_pgBackground.m_nColor,
                                      sheet.m_pgBackground.m_nPicture );
}


//创建一个向导样式属性表 事例-----------------------------------------------------------------
//重载OnSetActive()函数并调用SetWizardButtons()使相应的上一步”和“下一步”按钮可用
class CWizIntroPage : public CPropertyPageImpl<CWizIntroPage>
{
public:
    enum { IDD = IDD_WIZARD_INTRO };
    // Construction
    CWizIntroPage();
    // 构造函数使用一个字符串资源ID来设置页面标题栏文字
 // CWizIntroPage::CWizIntroPage() : CPropertyPageImpl<CWizIntroPage>( IDS_WIZARD_TITLE ) {}
 
    // Maps
    BEGIN_MSG_MAP(COptionsWizard)
        CHAIN_MSG_MAP(CPropertyPageImpl<CWizIntroPage>)
    END_MSG_MAP()
    // Notification handlers
    int OnSetActive();
    /*
 int CWizIntroPage::OnSetActive() //重载OnSetActive
 {
     SetWizardButtons ( PSWIZB_NEXT ); //调用SetWizardButtons()使“下一步”按钮按钮可用
     return 0;
 }
    */
};

//下面流程和创建属性表方法2相似
//派生一个新类
class COptionsWizard : public CPropertySheetImpl<COptionsWizard>
{
public:
    // Construction
    COptionsWizard ( HWND hWndParent = NULL );
    // Maps
    BEGIN_MSG_MAP(COptionsWizard)
        CHAIN_MSG_MAP(CPropertySheetImpl<COptionsWizard>)
    END_MSG_MAP()
    // Property pages
    CWizIntroPage m_pgIntro;
};
//构造函数:设置样式标志,添加页面
COptionsWizard::COptionsWizard ( HWND hWndParent ) :
    CPropertySheetImpl<COptionsWizard> ( 0U, 0, hWndParent )
{
    SetWizardMode(); //将属性表改称向导模式 (之前有介绍)
    AddPage ( m_pgIntro );
}
//在CMainFrame类添加菜单函数来调用向导页面
void CMainFrame::OnOptionsWizard ( UINT uCode, int nID, HWND hwndCtrl )
{
COptionsWizard wizard;
    wizard.DoModal();
}


//创建一个向导样式属性表 + DDV 事例-----------------------------------------------------------------
class CWizBkColorPage :
    public CPropertyPageImpl<CWizBkColorPage>,
    public CWinDataExchange<CWizBkColorPage>
{
public:
    // some stuff removed for brevity...
    BEGIN_DDX_MAP(CWizBkColorPage)
        DDX_RADIO(IDC_BLUE, m_nColor)
        DDX_CHECK(IDC_FAIL_DDV, m_bFailDDV)
    END_DDX_MAP()
    // Notification handlers
    int OnSetActive(); //使“上一步”和“下一步”按钮可用 (同上)
    /*
 int CWizBkColorPage::OnSetActive()
 {
     SetWizardButtons ( PSWIZB_BACK | PSWIZB_NEXT );
     return 0;
 }
    */
    BOOL OnKillActive();
    /*
    两个都是重载函数 (之前有介绍)
    OnKillActive() 和 OnWizardNext() 都可以使向导维持在当前页面 (此段代码两个函数都可以使用)
    OnKillActive()在单击“上一步”和“下一步”时被调用
    OnWizardNext()只是在单击“下一步”时被调用,还可以直接将向导引导到指定的页面,而不是按顺序的下一页
   
 int CWizBkColorPage::OnKillActive()
 {
     if ( !DoDataExchange(true) )
         return TRUE;    // prevent deactivation
     if ( m_bFailDDV ) //如果是TRUE就表示checkbox处于选中状态
         {
         MessageBox (
           _T("Error box checked, wizard will stay on this page."),
           _T("PSheets"), MB_ICONERROR );
         return TRUE;    // prevent deactivation
         }
     return FALSE;       // allow deactivation
 }
    */
    // DDX vars
    int m_nColor;
protected:
    int m_bFailDDV;
};


//属性表窗口居中-----------------------------------------------------------------
#define UWM_CENTER_SHEET WM_APP  //添加一个自定义消息,以响应属性表窗口居中
class CAppPropertySheet : public CPropertySheetImpl<CAppPropertySheet>
{
//...
    BEGIN_MSG_MAP(CAppPropertySheet)
        MESSAGE_HANDLER_EX(UWM_CENTER_SHEET, OnPageInit) //在属性表的消息映射链中处理自定义消息
        CHAIN_MSG_MAP(CPropertySheetImpl<CAppPropertySheet>)
    END_MSG_MAP()
 
    // Message handlers
    LRESULT OnPageInit ( UINT, WPARAM, LPARAM );
    /*
 LRESULT CAppPropertySheet::OnPageInit ( UINT, WPARAM, LPARAM )
 {
     if ( !m_bCentered )
        {
         m_bCentered = true;     //确保属性表窗口只响应一次UWM_CENTER_SHEET消息
         CenterWindow ( m_psh.hwndParent );
        }
 
     return 0;
 }
    */
protected:
    bool m_bCentered;  // set to false in the ctor
};

//在每个属性页OnInitDialog()方法发送 这个自定义的居中消息 到属性表窗口
BOOL CBackgroundOptsPage::OnInitDialog ( HWND hwndFocus, LPARAM lParam )
{
    GetPropertySheet().SendMessage ( UWM_CENTER_SHEET );
    DoDataExchange(false);
    return TRUE;
}


//属性页中添加图标-----------------------------------------------------------------
//要使用 属性表or属性页 未被成员函数封装的特性,就要直接访问相关数据结构:
CPropertySheetImpl类 -> PROPSHEETHEADER 结构成员 -> m_psh
CPropertyPageImpl类 -> PROPSHEETPAGE 结构成员 -> m_psp

//属性表->标签页->添加图标
CBackgroundOptsPage::CBackgroundOptsPage()    //例子在Background页面添加一个图标
{
 //设置 PROPSHEETPAGE 结构中几个成员
    m_psp.dwFlags |= PSP_USEICONID;      //添加一个标志
    m_psp.pszIcon = MAKEINTRESOURCE(IDI_TABICON);  // Load ICON
    m_psp.hInstance = _Module.GetResourceInstance();
}

 

 

 

 

 

 

 

 

 

 

 

 

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值