WTL学习总结

No Comments

         在windows平台,相比MFC,我更喜欢WTL,因其简洁漂亮。所以陆续花了一年的时间学习之,这里总结一下(在学习Wtl/Atl之前,最好是对WinApi编程有一定的了解)。

安装

        Wtl主页 http://sourceforge.net/projects/wtl/ ,整个库就是一堆.h文件,官方没有提供Installer,下载后解压到某个目录即可。

        如果需要在VS中使用“工作导向”,可以点击Appwiz目录下的对应js文件来安装之。虽然没有直接对VS2010的支持,不过拿VS2008的改改即可,详情Google下。

        为了在VS中使用Wtl,可以将include目录添加到全局Include Path。不过如果仅仅希望对单个项目有效,则在添加C++ Include Path的同时,可能还需要添加资源的Include Path。如果不希望以来第三方的库,可以直接将源码放在项目内。

 

Atl

        学习Wtl,不可能跳过Atl,Wtl底层就是使用Atl。关于Atl这里就几个点讲一下:

  1. ATL-style 模板
  2. 窗口创建和初始化
  3. Thunk技术
  4. 回调和消息绑定

 

ATL-style 模板

 
template <class T>
class B1
{
public:
    void SayHi()
    {
        T* pT = static_cast<T*>(this);   

        pT->PrintClassName();
    }
protected:
    void PrintClassName() { cout << "This is B1"; }
};

class D1 : public B1<D1>
{
    // No overridden functions at all
};

 

使用这个模板形式有几个好处:

  1. 不需要使用指向对象的指针。
  2. 节省内存,因为不需要虚函数表。
  3. 因为没有虚函数表所以不会发生在运行时调用空指针指向的虚函数。
  4. 所有的函数调用在编译时确定(译者加:区别于C++的虚函数机制使用的动态编连),有利于编译程序对代码的优化。

    http://www.winmsg.com/wtl/Part1.htm

     

    窗口创建和初始化

            有个哥们就这个流程分析了一下,地址在这里 http://blog.csdn.net/jznsmail/archive/2004/12/01/200947.aspx

            在Windows下,任何窗口创建都是通过CreateWindowEx或者CreateWindow函数来实现之,Wtl/Atl也不例外,(不过WtlAtl的流程略有不同)。详细的流程见上面链接,这里就几个重点说一下(不仅仅是Atl,也包括Wtl的内容)

            对于Windows窗口,需要派生自CFrameWindowImpl类,创建和初始化窗口就在该类的CreateEx函数实现。通过前面讲到的“ATL-style 模板”,CreateEx会调用Create函数,CFrameWindowImpl提供了默认的Create实现,不过也可以在派生类中定制。在CFrameWindowImpl类成员函数Create中会首先注册窗口,然后才创建之。

            在CFrameWindowImpl的Create函数中会通过CFrameWindowImplBase的成员函数Create创建实际的窗口,使用API函数CreateWindowEx。

            在CFrameWindowImpl的Create函数中会调用CFrameWndClassInfo类成员函数Register注册窗口,注册用到的信息通过DECLARE_FRAME_WND_CLASS或DECLARE_FRAME_WND_CLASS_EX宏来指定。在这两个宏中会指定静态成员函数StartWindowProc 作为窗口回调。这个静态成员在CWindowImplBaseT类中定义。在这个静态成员中使用了下面提到的Thunk技术动态地修改回调参数,同时将回调重置为该类的另一个静态成员WindowProc(见API函数SetWindowLongPtr和属性GWLP_WNDPROC

            以上提到的是Wtl流程的一个简单概述,对于普通窗口(控件),我们一般使用Atl的那一套而非Wtl。

            对于控件,一般派生自CWindowImpl窗口,窗口工作在该类的成员函数Create中完成。注册窗口的信息使用DECLARE_WND_CLASS或DECLARE_WND_CLASS_EX宏。具体的注册操作在ATL::CWndClassInfo类的成员函数Register中完成,窗口的创建在CWindowImplBaseT类的成员函数Create中完成。

     

    Thunk技术

            对于Windows API,从系统的消息回调出来的消息唯一的标识符就是HWND句柄,而当前随便一个UI程序都有相当之多的控件,所以必须要有一种行之有效的方法来通过这个句柄定位控件。比较傻B的方法就是建立一个HWND到控件类实例的映射,然后在消息收到后查询之,不过这种方法有很大的局限性。

            Atl用Thunk技术来处理这个问题,这个所谓的“Thunk技术”干的事挺简单,不过要理解代码还真不容易(至少我是花了不少功夫)。有兴趣可以研究下代码:

    (Microsoft Visual Studio 9.0\VC\atlmfc\include\atlstdthunk.h)

    #pragma pack(push,1)
    struct _stdcallthunk
    {
    	DWORD   m_mov;          // mov dword ptr [esp+0x4], pThis (esp+0x4 is hWnd)
    	DWORD   m_this;         //
    	BYTE    m_jmp;          // jmp WndProc
    	DWORD   m_relproc;      // relative jmp
    	BOOL Init(DWORD_PTR proc, void* pThis)
    	{
    		m_mov = 0x042444C7;  //C7 44 24 0C
    		m_this = PtrToUlong(pThis);
    		m_jmp = 0xe9;
    		m_relproc = DWORD((INT_PTR)proc - ((INT_PTR)this+sizeof(_stdcallthunk)));
    		// write block from data cache and
    		//  flush from instruction cache
    		FlushInstructionCache(GetCurrentProcess(), this, sizeof(_stdcallthunk));
    		return TRUE;
    	}
    	//some thunks will dynamically allocate the memory for the code
    	void* GetCodeAddress()
    	{
    		return this;
    	}
    	void* operator new(size_t)
    	{
            return __AllocStdCallThunk();
        }
        void operator delete(void* pThunk)
        {
            __FreeStdCallThunk(pThunk);
        }
    };
    #pragma pack(pop)

     

            这里我就不说什么原理了,我这水平也说不清:-)。

            Thunk技术实际上就是用一段汇编代码将函数的参数动态的替换,具体来说是将回调函数参数HWND动态替换成窗口类实例的指针。关于它推荐阅读 http://www.cngr.cn/article/54/395/2006/2006071928301.shtml

            这个过程在CWindowImplBaseT类的静态成员StartWindowProc中实现。首先在注册窗口时会指定它为回调,当第一次调用时,通过Thunk技术动态地修改回调参数,同时将回调重置为该类的另一个静态成员WindowProc。具体的实现代码如下:

    pThis->m_thunk.Init(pThis->GetWindowProc(), pThis);
    WNDPROC pProc = pThis->m_thunk.GetWNDPROC();
    WNDPROC pOldProc = (WNDPROC)::SetWindowLongPtr(hWnd, GWLP_WNDPROC, (LONG_PTR)pProc);

     

            那这个类实例在哪获取呢?其实Wtl/Atl实现也并不美观,通过CAtlWinModule类存储这些指针,很显然整个程序只应该包含一个CAtlWinModule类实例,这也是为什么我们经常看到这个实例前带一个extern关键字。

            在新的回调WindowProc中,Atl将参数HWND转型为CWindowImplBaseT类指针,然后调用该类的成员函数ProcessWindowMessage来进一步派发消息。这个函数怎么定义看下一节内容。

     

    回调和消息绑定

            前面提到一个ProcessWindowMessage函数,它最初在CMessageMap类中以纯虚函数的形式定义。在Atl中不管什么类,只要派生自CMessageMap就可以处理消息,这就提供了很大的灵活性。

            ATL定义了一堆预处理宏来实现这个函数和分发逻辑,典型的如下:

    	BEGIN_MSG_MAP(CMyWindow)
    		MESSAGE_HANDLER(WM_CLOSE, OnClose)
    		MESSAGE_HANDLER(WM_DESTROY, OnDestroy)
    		COMMAND_ID_HANDLER(IDC_ABOUT, OnAbout)
    	END_MSG_MAP()

            前后两个宏用于定义ProcessWindowMessage函数,中间则通过一个Switch结构来定义分发逻辑和回调绑定。

            ATL这一套结构有几个好处:

    1. 避免过多的纯虚函数导致额外的开销
    2. 容易将消息处理重定向,例如将消息处理重定向到一个非窗口类,实现UI和逻辑分离。也可以让同一个消息处理类同时处理多个窗口的消息。具体见ATL定义的那堆宏。

            Wtl在Atl基础上针对特定消息定义了很多更加精确的宏,不过个人不是很喜欢,因为懒得去记。

     

    Wtl入门

            最基本的Wtl程序可以通过Appwiz来生成,很有必要理解下Wtl程序的流程。简单地讲,控件使用CButton等封装类(atlctrls.h),窗口使用CFrameWindowImpl派生。如果希望自定义控件,可以派生自CWindowImpl(自绘还需要继承其他自绘支持类,见后面说明),例如

    class CBeepButton: public CWindowImpl< CBeepButton,CButton >
    {
    public:
    	DECLARE_WND_CLASS( _T("CBeepButton"))
    	BEGIN_MSG_MAP( CBeepButton )
    	END_MSG_MAP()
    };

            不过上面的类封装除了装逼没有任何用处!

            发现几个比较恶心的地方提一下。

            如果在自定义控件有绘制字体时,绘出来的字很奇怪,和默认的字体相差较大,后来发现可以手动设置字体将其设置为UI默认字体。

    	m_widget_->SetFont(AtlGetStockFont(DEFAULT_GUI_FONT));

            对于动态创建的Edit或者RichEdit,边框都很奇怪,为了设置默认的边框,需要

    	m_widget_->ModifyStyleEx(0, WS_EX_CLIENTEDGE, SWP_DRAWFRAME);

     

    超类化

            超类化(superclass )是一种生成新的窗口类的方法。它的中心思想是依靠现有的窗口类,克隆出另一个窗口类。被克隆的类可以是Windows预定义的窗口类,这些预定义的窗口类有按钮或下拉框控制等等。也可以是一般的类。克隆的窗口类使用被克隆的类(基类)的窗口消息处理函数。

            克隆类可以有自己的窗口消息处理函数,也可以使用基类的窗口处理函数。

    http://www.builder.com.cn/2007/1116/637833.shtml

            超类化是以类型为单位来设置,也就是被超类的类必须是已经存在的窗口(很显然,如果想“超”自定义窗口类,必须保证该类至少被用过一次或者手动注册过,如果没注册则超类会初始化失败)。在消息处理时,Wtl首先会使用超类的处理函数,如果没处理,则会调用“被超类”的消息处理函数来处理。

            那这个“被超类”的消息处理函数在哪里呢?毕竟每次只能注册一个回调函数。实际上在CWindowImplBaseT类中包含一个成员m_pfnSuperWindowProc用于在超类化时存储“被超类”的原始回调。这个成员在CFrameWndClassInfo类或_ATL_WNDCLASSINFOW结构的成员函数Register调用时作为参数传入实现初始化。

            通过宏DECLARE_FRAME_WND_SUPERCLASS或DECLARE_WND_SUPERCLASS来声明一个超类,两者原理都是一致的,即定义一个OrigWndClassName。在注册窗口类是首先获得OrigWndClassName类的注册信息,然后替换回调,并且将原来的回调保存在参数中,这个参数即前面提到的m_pfnSuperWindowProc。

            关于这两回调的配合,可以参考CWindowImplBaseT类静态成员函数WindowProc(这个函数即通过Thunk技术重置后的回调函数,见前面说明)。Wtl首先会调用当前的回调,如果返回FALSE,则继续调用原来的回调。

            为了测试超类化自定义窗口类,我写了如下代码:

    class CBeepButton1: public CWindowImpl< CBeepButton1,CButton >
    {
    public:
    	DECLARE_WND_CLASS( _T("CBeepButton1"))
    	BEGIN_MSG_MAP( CBeepButton1 )
    		MESSAGE_HANDLER( WM_LBUTTONDOWN, OnLButtonDown )
    		MESSAGE_HANDLER( WM_LBUTTONUP, OnLButtonUp )
    	END_MSG_MAP()
    
    	LRESULT OnLButtonDown( UINT, WPARAM, LPARAM, BOOL& bHandled )
    	{
    		this->SetWindowText("Press");
    		bHandled = FALSE; // alternatively: DefWindowProc()
    		return 0;
    	}
    
    	LRESULT OnLButtonUp( UINT, WPARAM, LPARAM, BOOL& bHandled )
    	{
    		this->SetWindowText("Release");
    		bHandled = FALSE; // alternatively: DefWindowProc()
    		return 0;
    	}
    };
    
    class CBeepButton: public CWindowImpl< CBeepButton >
    {
    public:
    	DECLARE_WND_SUPERCLASS( _T("BeepButton"), _T("CBeepButton1") )
    	BEGIN_MSG_MAP( CBeepButton )
    		MESSAGE_HANDLER( WM_LBUTTONDOWN, OnLButtonDown )
    	END_MSG_MAP()
    	LRESULT OnLButtonDown( UINT, WPARAM, LPARAM, BOOL& bHandled )
    	{
    		bHandled = FALSE; // alternatively: DefWindowProc()
    		return 0;
    	}
    };

            经测试发现几个问题,为了使用 CBeepButton,必须先实现一个 CBeepButton1(目的是为了注册窗口类,主要是手动注册不方便)。否则 CBeepButton1会注册失败。即便如此 CbeepButton还是无法运行,究其原因是因为Thunk技术未能重置回调。

            前面说过,Atl在初始化一个窗口类时,会使用Thunk技术动态重置回调。这个两个回调分别是CWindowImplBaseT类静态成员函数StartWindowProc和WindowPro,Thunk技术在前者中被使用,是一个临时的回调,具体的消息分发在后者进行。

            当超类化一个自定义窗口类时,首先会获得原窗口的回调,而这个回调很显然是StartWindowProc,而新的窗口类并非处理了所有消息,所以总是有部分消息会发送到原窗口类的回调,即StartWindowProc。杯具就在这里,StartWindowProc是一个临时的回调。实际中当消息派发到这里时会碰到断点,因为无法从CAtlWinModule中获得类实例的指针(这都不是该窗口类初始化过程,当然找不到了)。这不得不说是Wtl的一个Bug,不过就目前这种框架,要改好还真不容易。

     

    子类化

            子类化(subclass)是普遍采用的一种扩展窗口功能的方法。它的大致原理如下。

            在一个窗口创建完了之后,将该窗口的窗口函数替换成新的窗口消息处理函数。这个新的窗口函数可以对某些需要处理的特定的消息进行处理,然后再将处理传给原来的窗口函数。

            注意它与superclass的区别。

            Superclass是以一个类为原版,进行克隆。既在注册新的窗口类时,使用的是基类窗口的窗口函数。

            而subclass是在某一个窗口注册并创建后,通过修改该窗口的窗口消息函数的地址而实现的。它是针对窗口实例。

    http://www.builder.com.cn/2007/1116/637833.shtml

            子类化的核心就是前面提到的Thunk技术,这里就不废话了。

     

    自绘

            说的自绘,我真想吐血,很不明白微软为什么要为了自绘搞出那么一套鸟毛东西,虽然所有的自绘都可以通过Paint来完成,不过既然微软推出那么一套鸟毛,总是会要犹豫一下,”我用Paint来做会不会有什么不妥“。

            并不是所有的内置控件都支持那一套鸟毛的自绘,Wtl通过COwnerDraw类和CCustomDraw 类来简化这一套东西的开发,其实就是定义一堆宏来处理回调,提供一个相对简单的接口给程序员。为了能够让控件收到这些自定义消息,需要在父窗口加入反射宏DEFAULT_REFLECTION_HANDLER()。

            不过个人很少用这东西,即便要自绘Button,我也是通过WM_PAINT来做的(这种做法有什么不妥忘各位指出,不过我没发现效率有多低,至少结构一致和美观,我想谁都不想去理解那么多规则,简单就是美)。

     

    浏览器控件

            如今越来越多的UI程序选择浏览器控件来显示网页内容,这有很多好处,至少简化了开发并且可以实现很复杂的样式,更重要的是无需升级可保持最新。

            Wtl官方并没有内置浏览器控件,不过可以参考这里http://devel.openocr.org/svn/openocr/trunk/cuneiform/interface/icrashreport/wtl/samples/tabbrowser/browserview.h ,               

    Windows自绘控件的陷阱

    2 Comments

          今天要写个透明的、支持颜色和字体大小的Static控件,新控件派生子CStatic(WTL),在WM_PAINT回调中进行自绘,测试时发现CPU占用爆高,搜了搜原来有一处没注意。

          实际上,如果程序在WM_PAINT消息中对客户区刷新完毕后工作并没有结束,如果不使无效区域变得有效,Windows会在下一轮消息循环中继续放入一个WM_PAINT消息,而不是根据程序是否执行了刷新过程,所以程序也可以不去刷新客户区,而是简单地用一个ValidateRect函数直接让客户区变得有效,以此来“欺骗”Windows已经没有无效区域了。当然也可以用BeginPaint和EndPaint实现同样的效果,而我在上面的代码中是没有这些代码,结果造成Windows不停地发送Paint消息而导致CPU占用爆高。

          参考 http://www.examda.com/ncre2/cpp/jichu/20090613/082508824-2.html

    一个空格导致的杯具

    No Comments

          今天用Windows Api CreateProcess函数来启动另一个程序,带了几个参数,可调试时,参数死活都不正确,浪费了两个小时。

          程序路径放在第一个参数中,而参数则放在第二个参数中,问题就出在这个用法上(CreateProcess函数有好几种参数用法),如果使用上面提到的这种,第二个参数必须第一个字符是空格,而我给的参数是没有空格的 :-(

    http://dev.firnow.com/course/3_program/c++/cppsl/200889/135348.html

         如果通过lpCmdLine参数查看命令行参数,那么父进程创建子进程的时候,需要在子程序和参数中加上空格号。比如:

    CreateProcess(“c:\\test.exe”,“ -p“, NULL, NULL, FALSE, 0, NULL, NULL, &si, &pi);

    【转】不用双缓冲实现无闪烁图象

    No Comments

    http://hi.baidu.com/techicey/blog/item/16a55ffcec749cf6fd037f19.html

    首先说一下产生闪烁的原因,当窗口由于任何原因需要重绘时,总是先用背景色或背景图象将显示区清除,然后才显示图象,这样在短时间内背景色与显示图形交替出现,使得显示窗口看起来在闪。如果将背景刷设置成NULL,这样无论怎样重绘图形都不会闪了,但是会使得窗口的显示乱成一团,因为重绘时没有背景色对原来绘制的图形进行清除,而又叠加上了新的图形。

    一般的解决方法就是采用双缓冲,创建一幅内存图象,把背景和图片先绘制到该图象,然后把绘制好的该图象显示到窗口,这样就不会产生闪烁,其实就是隐藏了图象的显示过程,原来是在前台,给你看到先在画布上刷上背景,再画上图象,现在是在后台画好了再拿出来给你看。

    看起来好象解决方法是需要一次性绘制好图象,其实关键是不能让任何背景及图片在绘制时产生重叠,跟是否一次性绘制没多大关系。电脑的绘制速度还是很快的,不信可以试试把背景刷设置成NULL,这样不会闪烁,然后循环调用BitBlt把一幅小图片铺满整个窗口,虽然是多次绘制,一样不会闪烁,但是如果改变一下循环步长,让图片产生重叠,就会开始闪烁了。

    知道了闪烁的原因,不用双缓冲的解决办法就不难找到了,调用ExcludeClipRect,可以排除掉绘制区域,先绘制图片,ExcludeClipRect掉图片的区域,再绘制背景,相当于把一幅挖了一个洞的画布贴到窗口,这样不产生绘制重叠部分,就不会产生闪烁了。

    API代码,始终在窗口右下角显示一幅300*200的图片,窗口背景为黑色:

    case WM_PAINT:
    {
    PAINTSTRUCT ps;
    RECT rc;
    HDC hMemDC;

    GetClientRect(hWnd,&rc);
    BeginPaint(hWnd,&ps);
    hMemDC = CreateCompatibleDC(ps.hdc);
    SelectObject(hMemDC,hbmp);
    BitBlt(ps.hdc,rc.right-300,rc.bottom-200,300,200,hMemDC,0,0,SRCCOPY);
    ExcludeClipRect(ps.hdc,rc.right-300,rc.bottom-200,rc.right,rc.bottom);
    FillRect(ps.hdc,&rc,(HBRUSH)GetStockObject(BLACK_BRUSH));
    DeleteDC(hMemDC);
    EndPaint(m_hWnd,&ps);
    return 0;
    }

    有时候因为刷新区域的问题,可能调整窗口但是WM_PAINT的代码不起作用,这时需要响应WM_SIZE消息,通知画面更新一下:
    case WM_SIZE:
    InvalidateRect(hWnd,NULL,FALSE);
    break;

    对于显示多幅图象,这个方法也是适用的,只要每绘制完一幅图象,ExcludeClipRect掉该图象区域就可以,但是注意如果图象有重叠,绘制顺序是反过来的,即盖在最上面的图象需要最先绘制,最后给整个窗口刷上背景就OK了。

    [转]WTL学习笔记之NM_CUSTOMDRAW和WM_DRAWITEM

    No Comments

    最近在学习《WTL for MFC Programmer》系列文章的一些小结和感受
    相同点:
    1.都是通知消息,都可以被反射回控件类自行处理。
    2.都和自定义控件的绘画有关。
    区别:
    MSDN对WM_DRAWITEM描述:
    The WM_DRAWITEM message is sent to the parent window of an owner-drawn button, combo box, list box, or menu when a visual aspect of the button, combo box, list box, or menu has changed.
    MSDN对NM_CUSTOMDRAW的描述:
    Sent by some common controls to notify their parent windows about drawing operations. This notification is sent in the form of a WM_NOTIFY message.
    1.可以看出,前者是对Owner-Draw风格的按钮,复选框,列表框和菜单有效的,树形控件并没在此列。所以在系列文章的第五篇中,自定义按钮是继承了COwnerDraw,树形控件是继承了CCustomDraw,MSDN中也列出了一些NM_CUSTOMDRAW有关的控件:
    List view
    NMLVCUSTOMDRAW
    ToolTip
    NMTTCUSTOMDRAW
    Tree view
    NMTVCUSTOMDRAW
    Toolbar
    NMTBCUSTOMDRAW
    All other supported controls
    NMCUSTOMDRAW
    2.前者若想进行一些gdi动作,那基本上就是整个区域需要绘画,gdi的一些操作比较多,后者使用更简单,一些属性(比如字体颜色)只需要设置一些变量即可。
    3.前者是一个独立的消息,后者是被包含在WM_NOTIFY消息中被发送的。
    4.NM_CUSTOMDRAW分好多个阶段,可以通过重载某些方法来改变行为,这些方法包括OnPrePaint, OnItemPrePaint等等(细节只能看wtl源代码了,MSDN中也稍有介绍)。
    猜测:他们应用在不同的控件上,但是NM_CUSTOMDRAW貌似是WM_DRAWITEM的加强版,呵呵
    欢迎大家多多批评指教~
    以下是在MSDN上找到的一些解释,链接是相关的一篇文章地址
    所有者绘制
    控制控件绘制的另一种方法是利用所有者绘制。事实上,您也许听开发人员提到过所有者 绘制控件,因为它是用于开发自定义控件最普通的技术。该技术普遍使用的主要原因在于,Windows 可为您提供很多帮助。在呈现控件的那一刻,Windows 就已经创建并填写了设备上下文,决定了控件的大小和位置,并且向您传递信息以使您了解此刻绘制的需求。对于列表控件(例如,列表框和列表视图), Windows 将为列表中的每一项调用绘制代码,这意味着您只需绘制这些项,而无需考虑控件的其他方面。注意,所有者绘制可用于大多数控件。然而,它不能用于编辑控件; 并且考虑到列表控件,它只能用于报表视图样式。
    自定义绘制
    对于绘制自己的控件而言,这可能是最少 为人所知的技术。事实上,许多技术能力较高的开发人员也混淆了术语所有者绘制 (owner-draw) 和自定义绘制 (custom-draw)。关于自定义控件,首先需要了解,它仅针对于指定的公共控件:标头、列表视图、rebar、工具栏、工具提示、跟踪条和树视 图。此外,尽管所有者绘制只允许绘制报告视图风格的列表视图控件,而自定义绘制则使您能够处理列表视图控件所有视图风格的绘制。使用自定义绘制的另一个明 显优势是,您可以对希望绘制的内容进行严格挑选。实现方式是,在控件绘制的每个阶段由 Windows 向代码发送一个消息。这样,您可以决定在每个阶段是自己进行所有的绘制工作,增加默认的绘制,还是允许 Windows 为该阶段执行所有的绘制。(鉴于自定义绘制是本文的一个主题,因此您很快会看到它的工作方式。)
    文章地址: http://msdn2.microsoft.com/zh-cn/library/ms364048(VS.80).aspx

    最近在学习《WTL for MFC Programmer》系列文章的一些小结和感受相同点:1.都是通知消息,都可以被反射回控件类自行处理。2.都和自定义控件的绘画有关。
    区别:MSDN对WM_DRAWITEM描述:The WM_DRAWITEM message is sent to the parent window of an owner-drawn button, combo box, list box, or menu when a visual aspect of the button, combo box, list box, or menu has changed.
    MSDN对NM_CUSTOMDRAW的描述:Sent by some common controls to notify their parent windows about drawing operations. This notification is sent in the form of a WM_NOTIFY message.
    1.可以看出,前者是对Owner-Draw风格的按钮,复选框,列表框和菜单有效的,树形控件并没在此列。所以在系列文章的第五篇中,自定义按钮是继承了COwnerDraw,树形控件是继承了CCustomDraw,MSDN中也列出了一些NM_CUSTOMDRAW有关的控件:List view    NMLVCUSTOMDRAWToolTip    NMTTCUSTOMDRAWTree view    NMTVCUSTOMDRAWToolbar    NMTBCUSTOMDRAWAll other supported controls    NMCUSTOMDRAW2.前者若想进行一些gdi动作,那基本上就是整个区域需要绘画,gdi的一些操作比较多,后者使用更简单,一些属性(比如字体颜色)只需要设置一些变量即可。3.前者是一个独立的消息,后者是被包含在WM_NOTIFY消息中被发送的。4.NM_CUSTOMDRAW分好多个阶段,可以通过重载某些方法来改变行为,这些方法包括OnPrePaint, OnItemPrePaint等等(细节只能看wtl源代码了,MSDN中也稍有介绍)。
    猜测:他们应用在不同的控件上,但是NM_CUSTOMDRAW貌似是WM_DRAWITEM的加强版,呵呵欢迎大家多多批评指教~
    以下是在MSDN上找到的一些解释,链接是相关的一篇文章地址所有者绘制
    控制控件绘制的另一种方法是利用所有者绘制。事实上,您也许听开发人员提到过所有者 绘制控件,因为它是用于开发自定义控件最普通的技术。该技术普遍使用的主要原因在于,Windows 可为您提供很多帮助。在呈现控件的那一刻,Windows 就已经创建并填写了设备上下文,决定了控件的大小和位置,并且向您传递信息以使您了解此刻绘制的需求。对于列表控件(例如,列表框和列表视图), Windows 将为列表中的每一项调用绘制代码,这意味着您只需绘制这些项,而无需考虑控件的其他方面。注意,所有者绘制可用于大多数控件。然而,它不能用于编辑控件; 并且考虑到列表控件,它只能用于报表视图样式。
    自定义绘制
    对于绘制自己的控件而言,这可能是最少 为人所知的技术。事实上,许多技术能力较高的开发人员也混淆了术语所有者绘制 (owner-draw) 和自定义绘制 (custom-draw)。关于自定义控件,首先需要了解,它仅针对于指定的公共控件:标头、列表视图、rebar、工具栏、工具提示、跟踪条和树视 图。此外,尽管所有者绘制只允许绘制报告视图风格的列表视图控件,而自定义绘制则使您能够处理列表视图控件所有视图风格的绘制。使用自定义绘制的另一个明 显优势是,您可以对希望绘制的内容进行严格挑选。实现方式是,在控件绘制的每个阶段由 Windows 向代码发送一个消息。这样,您可以决定在每个阶段是自己进行所有的绘制工作,增加默认的绘制,还是允许 Windows 为该阶段执行所有的绘制。(鉴于自定义绘制是本文的一个主题,因此您很快会看到它的工作方式。)
    文章地址: http://msdn2.microsoft.com/zh-cn/library/ms364048(VS.80).aspx

    Older Entries