基础常识与原理分析汇总.

来源:互联网 发布:painttool sai mac 编辑:程序博客网 时间:2024/05/22 02:02

目录:

1.duilib在vs2013下环境配置:

2.duilib绝对布局和相对布局:

3.Duilib各个类相关简介及类关联结构图.

4.DuiLib消息处理剖析 + 内附带解析转载 写的很好.

5.基于WindowImplBase窗体开发入门.

6.XML描述文件位置

7.为窗口添加双击风格和双击消息

8.duilib各个控件消息响应分类.

9.trunk技术的理解.



1.duilib在vs2013下环境配置:

1.库文件:自己使用的是Troy的duilib库:http://pan.baidu.com/s/1c2J2beg

2.编译生成duilib库:http://jingyan.baidu.com/article/0320e2c1ee366d1b86507b5d.html

这一块有现成的安装教学例子,便不再赘述:

有几个点注意的是:

1.批生成的时候,一定要重新生成,不然可能到时源文件存在,生成的库异常.

2.初期学习,建立的是win32空项目的,一定按照步骤走.

3.关于命名使用,我这里使用的是Troy的库,就和链接教材那块不一致.老大帮忙解答了.<命名那块: 多字节是A,默认是Unicode >

并且知道怎么配置程序是Unicode还是多字节的,关于区别,转参照了解:http://blog.csdn.NET/qq_24571549/article/details/68940615


4.配置项目属性顺序.配置好包含目录(DuiLib),配置好库目录(Lib),并且将bin目录下的3步骤图中的dll拷入的程序所在目录下.注意着最后一步才能正常编译执行程序.



2..duilib绝对布局和相对布局:

大多数刚使用duilib的朋友时候非常依赖duilib自带的设计器,用他可以拖拉控件,可视化的做出自己想要的界面。可是用一段时间就会发现原带的设计器有很多bug,时不时会崩溃,支持的控件数量有限,属性数量也有限,导出的代码冗余。当时问了几个高手,大家建议不要使用设计器而应该自己手写xml代码。起初手写时感觉特别麻烦,可是用几天后你会发现手写要比使用设计器好得多:你可以更加了解duilib,熟悉每个控件的各个属性,对控件的控制也更加方便。而如果想称心如意的脱离设计器去编写xml文件,有非常有必要弄明白各个布局的用法和布局技巧。完全可以靠手写xml来做出一个程序的界面,相信用了一段duilib的朋友也是这样     

在这里提醒一下新手朋友,在duilib的根目录有一个属性列表.xml的文件,他包含了绝大多数控件的绝大多数属性的介绍,有不懂的属性记得时常翻看他,同时不得不说这个文件包含的属性的确是不全面的,想要知道最全面的属性信息,可以看每个控件的源代码,在SetAttribute函数中可以看到最全面的属性信息!<举例:在Duilib文件夹下的Control,有个个控件的源文件>.

      要想手写xml,当然必须有一个编写工具,原则上只要是可以编写文本的工具都可以,大家根据习惯自己挑选适合的工具,我目前在使用的是sublime这款工具,感觉编写xml非常方便,使用界面也不错。<notepade ++也行>

6大布局的作用:

      duilib的Layout目录专门放置布局相关的容器控件,这6个布局分别为:Container、VerticalLayout、HorizaontalLayout、TileLayout、TabLayout、ChildLayout。容器之间可以任意相互嵌套,我分别说明他们的用法。

      首先我要说明一下,下面介绍的时候,我都默认认为每个控件的float属性为false,也就是不使用绝对定位,这个属性会打破各个布局的作用。

<这句话在使用上有个误区,实际上,只要了解乐html语言之后,float属性和这个是一个道理,就能灵活使用了,灵活使用,不要惧怕着属性>

  Container:

     Container布局是其他所有布局以及含有容器特性(如CList、CListContainerElement)的控件的基类,而实际上开发过程中很少使用这个布局,只用他来做其他更高级的布局的基类。因为Container布局中的所有控件都会自动填充满整个布局,所有的控件都叠到了一起,假如恰好有什么需求要让子控件都覆盖起来,而且可以随着容器的改变而自适应填充的话,Container就是不二之选。而除了这个效果之外,一般我们不使用它。

 VerticalLayout、HorizaontalLayout:

     VerticalLayout与HorizaontalLayout布局无疑是duilib中最常使用的两个布局,巧妙的使用这两个布局可以满足大多数的布局需求。从单词的意思上不难看出VerticalLayout是纵向布局,HorizaontalLayout是横向布局。这门两个直接继承自Container布局。

     VerticalLayout布局会让他包含的元素都纵向排列开,HorizaontalLayout布局会让给他包含的元素都横向排列开:如图

 


                                                           

        我故意没让控件填满整个容器,为了说明这两个布局不会强行让子元素的总和去填满容器,纵向布局会从上到下根据每个控件的高度让他们排到一起,横向布局会从左到右根据每个控件的宽度让他们排到一起。另外可以看到,纵向布局只关心子元素的高度,而不会强行让子元素的宽度等于容器的宽度,这点从图片可以看到,横向布局同理也是只关心子元素的宽度。而这两个布局经常会嵌套使用,如下效果:



       可以看到我最外层使用了一个纵向布局,他包含了横纵横三个布局(分别为红绿蓝颜色),每个横向布局里又分别包含了几个按钮。我们在编写界面时经常用到这个方法!以下是这个布局效果图对应的xml代码:

<?xml version="1.0" encoding="utf-8" standalone="yes" ?><Window size="300,226">    <VerticalLayout width="300" height="318" bkcolor="#FFFFFFFF">        <HorizontalLayout width="300" height="65" bkcolor="#FFFF0000">            <Button text="按钮测试" width="60" height="35" />            <Button text="按钮测试" width="60" height="63" />            <Button text="按钮测试" width="60" height="35" />        </HorizontalLayout>        <VerticalLayout width="300" height="96" bkcolor="#FF00FF00">            <Button text="按钮测试" width="60" height="64" />            <Button text="按钮测试" width="60" height="75" />            <Button text="按钮测试" width="60" height="64" />        </VerticalLayout>        <HorizontalLayout width="300" height="65" bkcolor="#FF0000FF">            <Button text="按钮测试" width="60" height="47" />            <Button text="按钮测试" width="60" height="64" />            <Button text="按钮测试" width="60" height="64" />        </HorizontalLayout>    </VerticalLayout></Window>

TileLayout:

       TileLayout布局是用来做类似360工具箱的效果:


  在前面的文章里,我写的《duilib中ListCtrl控件的实现》和《仿酷狗音乐播放器开发日志十三——左侧功能块的完善》正是使用了这个布局完成的。这个布局有有两个关键属性:

<Attribute name="columns" default="1" type="INT" comment="列数,如(4)"/><Attribute name="itemsize" default="0,0" type="SIZE" comment="子项固定大小,如(128,128)"/>

 columns和itemsize属性,这两个属性不能一起用,应该只是用其中的一个。使用columns属性可以来设置每行中包含的列数,他会自动把包含的元素从左到右从上到下按照columns属性的设置排列起来,他把每行的列数固定死了。而itemsize有两个字段,通过我读源码,发现第二个字段是无效的,我们只要使用第一个字段就行了,他会设置每个元素所占的区域,比如容器的宽度是500,给itemsize设置为 100 x 10,那个每行就会容纳5个元素,当我们拉伸了窗体让容器宽度变为700,那么每行就会自动容纳7个元素,这意味着使用这个属性会让每行容纳的元素个数是自动可变的!这在很多情况下是很有用的属性。注意itemsize并不是直接设置了子控件的大小,而只是限制了子控件的最大区域。比如itemsize为100x100,而子控件设置为50x50,那么 最终的子控件大小为50x50,而子控件的位置会按照100x100来计算。这个希望读者自己实践一下来理解这个效果!

 TabLayout:

     TabLayout布局同样常用,他就像MFC的选项卡CTabCtrl控件,如图:



      但是在duilib中TabLayout只是下面的布局界面,而不包含顶端的选项卡按钮,所以经常用Option控件配合他一起使用,使用他时他会把他包含的下一级元素作为一个页面,所以我们通常在他里面放入横纵向布局来作为一个页面,在横纵向布局里再规划每个页面的外观。

  这个控件的详细使用方法大家可以看duilib自带的360demo,我就不赘述了!

      ChildLayout:

      ChildLayout布局比较少用,因为他的功能可以用其他布局来代替,他的作用就是从一个xml文件中加载布局来嵌入到ChildLayout布局所在的地方,使用这个布局一般只需要指定xmlfile属性来设置xml文件位置就可以了。他的意义在于可以把繁杂的大量xml代码分隔开。比如他和TabLayout布局结合,让TabLayout布局包含5个ChildLayout布局,而每个ChildLayout布局分别从5个xml文件加载自己的布局文件,这样就可以分块化的编写布局代码。

      实际上有个比他更好用的标签,就是Include标签,Include不属于布局,但他的作用在布局方面非常类似ChildLayout,指定他的Source属性到某个xml文件就可以了。相对ChildLayout,Include的优点是可以自动识别自定义控件,而ChildLayout不可以!

      在这里要提一下360Demo的自定义控件,这个demo的自定义控件做法误导了很多人,里面使用了自定义控件的方法,把一个xml布局文件嵌入到界面里,这种做法完全没必要,直接使用Include标签,一句xml代码可以完全代替自定义控件。

这里给出一个Include的用法:

<Include source="Login\ScrollBar.xml" count="1" />

 其中source属性指定xml文件的路径,count指定解析的次数。

绝对布局(float属性为真)的意义与用法:

       在知道了6大布局的用法之后,知道了各种样式的界面外观的大致布局方法,而这还远不够让我们写出漂亮的布局外观,只有配合相对布局与绝对布局才可以更好的控制界面的元素。值得一提的是,我这里说的相对布局和绝对布局并不是一个容器或者控件,这只是一种技巧和使用方法,用在容器布局所包含的控件上,常用到横纵向布局中。

      我先来介绍绝对布局,笼统上说绝对布局和相对布局其实只有一个差别,也就是我在前面提到的float属性,容器中包含的控件float属性为真就是绝对布局,为假就是相对布局。不要小看这一个属性,他带来的效果可以天壤之别!

      给控件的float属性设为真后,就使用了绝对布局,故名意思,绝对布局就是让控件的坐标绝对化,这样这个控件就不受他的容器的束缚而可以自己随意设置自己的位置!比如在横纵向布局中给他们包含的子控件设置float属性,这个控件就不会被自动横纵向排列。而我的建议是,能不用绝对布局就别用绝对布局!原因有三个:<自我阅读阐释:这个绝对布局所说的坐标绝对化是有参照物的:比如包含它的容器或者窗口.>

      1)绝对布局破坏了各个容器的特性,而不受容器的束缚。

      2)绝对布局让控件的坐标固定,不利于控件自动调节位置。

      3)后面提到的相对布局几乎可以完成绝对布局的所有特性。

     那么为什么要用绝对布局,因为他的一个功能是相对布局无法完成的,就是让控件或者布局重叠或者相交!有的时候我们必须这样做来让控件组合起来达到一些效果。我可以明确的说,我在做仿酷狗播放器的过程中,整个xml布局代码只用了2个绝对布局,一个是编写搜索栏《仿酷狗音乐播放器开发日志二——搜索栏的编写》,一个是编写电台控件。如图:

       另外一个非常经典的使用绝对布局的例子就是我前几天写的《用duilib制作仿QQ2013动态背景登录器》,这些例子都是因为要让控件重叠起来组合出新的控件才使用了绝对布局,如果不让控件重叠或者没有特殊需求,最好别用绝对布局。<就是一种能力,灵活使用即可>

        虽然不建议使用,但我也得说一下绝对布局相关的属性和使用技巧。

        1)把float属性设置为真。

        2)设置pos属性,这个属性在float为真时才有效,他包含四个字段,分别以为了控件的左上右下下个坐标的位置,但是建议只指定前两个字段来设置控件的左上角的坐标,控件的宽度用width和height属性来控制,这样做的好处是避免了计算右下角坐标的繁琐!以后修改的时候也很清晰!

        举出一个例子:

<Label name="MusicName" float="true" pos="100,100" width="26" height="22" textcolor="#FF505050" endellipsis="true" />


        这里设置了一个Label控件,左上角放到100,100的坐标上,控件高22,宽26

        不得不提一个很有用的属性,那就是relativepos属性,属性列表没有列出这个属性,这是我自己总结的

<Attribute name="relativepos" default="0,0,0,0" type="RECT" comment="设置相对移动,float为真时,分别表示横纵向位移值,横纵向缩放值,移动单位建议50或100"/>

        用了这个属性,就可以让控件拥有相对布局的一部分特性,那就是根据容器的大小,自己可以调整位置和大小!这个特点我在《仿酷狗音乐播放器开发日志二——搜索栏的编写》用到了,是为了让搜索按钮可以自己移动。这个属性的前两个字段表示横纵向的位移值,后两个字段表示缩放值,具体效果大家应该自己实践一下!另外这个属性默认是有严重bug的,就是窗体最小化再恢复后有这个属性的控件会自动无规律偏移,这个bug我修复了,详见《仿酷狗音乐播放器开发日志二——搜索栏的编写》。

        这样就介绍完了绝对布局,然后就是整片文章的最重要部分,相对布局!

相对布局的意义与用法:

       我把相对布局的介绍放到最后,因为它很重要!

       在容器内部使用控件或者容器时,float属性设置为flase(duilib默认为false)就是相对布局了。这是我非常推荐使用的,前面我也说了我在写仿酷狗的整个布局中,上百个控件中我只给两个控件使用了绝对布局,其余都是相对布局。他的优点如下:

        1)布局和控件是可以根据窗体的大小改变而自动调整位置的,这点很重要

        2)不需要绝对布局那样麻烦的计算各个控件的位置

        3)在容器中调整前一个控件的位置,后面的控件都会自动调整坐标

      其实总得来说使用相对布局意义就是使用布局控件的自动排列特性!

      使用了相对布局后,就不用设置float属性和pos属性,一般只设置甚至不设置width和height属性。这点很重要,如果你的控件或者布局的大小是固定的,那么就设置width和height属性,如果想让控件或者布局根据窗体的大小而自动调整大小和位置,就不设置这两个属性。如果只设置了一个属性,比如width设置为100,而height不设置,那么他的高度是自动调整的而宽度是固定的。父容器会自动安排他包含的元素,让含有width和height属性的控件占据相应的大小,把剩下的空间都分配给没有设置wieth和height属性的容器或者控件里。看下面一个例子:

<?xml version="1.0" encoding="utf-8" standalone="yes" ?><Window size="300,300">    <VerticalLayout name="Father">        <HorizontalLayout name="Sub1" height="50" >        </HorizontalLayout>        <VerticalLayout name="Sub2">        </VerticalLayout>        <HorizontalLayout name="Sub3" height="50" >        </HorizontalLayout>    </VerticalLayout></Window>

      可以看到窗体的大小为300x300,而最外层的是一个名为Fahter的纵向容器,包含三个子容器。而Father没有指定width和height,所以当我改变了窗体的大小时,Father会自动调整自己的大小到和窗体大小相同,而三个子容器我都眉头指定width属性,所以三个子容器的宽度和Father是一样,也就是他们的宽度都是和窗体宽度一样,并且会自动调整。Sub1和Sub3的高度设置为50,所以他们的高度就固定了,而Sbu2的高度也没有指定,那他会自动占据了除了Sub1和Sub2的所有空间!其实这个例子的布局是非常常见的界面布局例子,比如说酷狗,sub1和sub3分别表示标题栏和状态栏,sub2为程序的主界面:

 



       关于酷狗的更加详细的布局分析可以看我前面写的博客《仿酷狗音乐播放器开发日志——整体框架分析》,几乎每开发酷狗的一部分,我都会把布局分析一下写出来。

      把这些知识综合起来,现在就可以写出一个自动调整大小的大致布局了,但是还没法精确控制每个控件的位置。

      利用上面介绍的自动占位的特性,我这里举一个标题栏编写的例子:

<HorizontalLayout name="background" height="30" bkimage="file='UI\BKImage\4.jpg' source='0,0,1000,30'" ><!-- 标题栏 -->        <Label text="皮肤与窗口调整——Redrain播放器" textcolor="#FFFFFFFF" textpadding="5,0,0,0" font="1" autocalcwidth="false" width="300"/><Control /><Button name="closebtn" width="23" height="18" padding="0,6,0,0" normalimage="UI\title\close_normal.png" hotimage="UI\title\close_hover.png" pushedimage="UI\title\close_down.png" />                     </HorizontalLayout>

 

       这个例子是我的仿酷狗播放器的换肤窗体的标题栏。他的外层是一个横向布局,高度为30,宽度随窗体调整。让标题文字居于左侧,关闭按钮在最右侧,如果让窗体调整宽度后文字和关闭按钮自动调整位置,就需要把中间的空位占满。这时我们就需要一个占位控件,不给他设置width和height属性,这样子他就会自动占据剩余的空间!就达到了相对布局的自动调整位置的效果。这个使用方法是相对布局里非常常用的!而这个占位控件在没有什么其他要求时建议像我给出的例子那样,使用Control控件,因为他是所有控件和容器的祖先基类,代码和属性相对是最少的,这样有利于提高程序的效率!

       在充分理解了占位的技巧后,再配合一些微调属性,就可以完美控制各个控件了,这几个微调属性分别是inset、padding、childpadding,这几个属性的介绍如下:

<Attribute name="padding" default="0,0,0,0" type="RECT" comment="外边距,如(2,2,2,2)"/><Attribute name="inset" default="0,0,0,0" type="RECT" comment="容器的内边距,如(2,2,2,2)"/><Attribute name="childpadding" default="0" type="DWORD" comment="子控件之间的额外距离,如(4)"/>

  在float为假,也就是相对布局中,这几个属性才起效。

      inset属性

      这是给容器控件使用的,使用后他所包含的所有使用相对布局的元素,都会被限制在设置的范围内,适合对容器内所用元素进行整体的坐标控制。比如在前面提到的做360工具箱时,我们使用TileLayout容器来存放每一个工具,我们首先设置inset属性,就可以让所有的工具项限制在一定范围内,例子如下:

<TileLayout name="listctrl" width="540" height="400" inset="20,20,0,20" childpadding="20" vscrollbar="true" columns="2" bkimage="CustomList\List_bk.png" itemhotimage="CustomList\ListItem_bk_hover.png" itemselectedimage="CustomList\ListItem_bk_hover.png"></TileLayout>

 

通过设置inset属性,让所有元素限制在一定范围内而不用重复设置每个元素的属性。

childpadding属性:

      childpadding属性设置容器内每个元素之间的间距,这个比较容易理解,上面的例子中也用到了childpadding属性。这个属性在不同容器中代表不同意思,在横向布局中代表子控件之间的横向间隔,在纵向布局中代表子控件之间的纵向间隔,在TileLayout容器中代码行与行之间的间隔!

padding属性:

      padding属性是相对布局中最常用的属性!用来设置相对于前一个控件的位置,这个属性的控件位置微调的关键。一般只用他的前两个字段,设置左边距和上边距,后两个字段是无效的,或者说存在问题(为什么会有问题?请看源码)。不过使用这两个字段就够了。这是我的仿酷狗播放器的状态的布局代码:

<HorizontalLayout height="30" inset="77,0,0,0" bkimage="UI\statusbar\status_bk.png"><!-- 状态栏 -->    <Button width="31" height="30" padding="0,0,0,0" normalimage="UI\statusbar\add_normal.png" hotimage="UI\statusbar\add_hover.png" pushedimage="UI\statusbar\add_down.png" />    <Button width="31" height="30" padding="40,0,0,0" normalimage="UI\statusbar\locate_normal.png" hotimage="UI\statusbar\locate_hover.png" pushedimage="UI\statusbar\locate_down.png" />    <Button width="31" height="30" padding="40,0,0,0" normalimage="UI\statusbar\search_normal.png" hotimage="UI\statusbar\search_hover.png" pushedimage="UI\statusbar\search_down.png" />    <Button width="31" height="30" padding="40,0,0,0" normalimage="UI\statusbar\setting_normal.png" hotimage="UI\statusbar\setting_hover.png" pushedimage="UI\statusbar\setting_down.png" />    <Control />    <Label text="Redrain仿酷狗音乐盒DirectUI ^_^" textpadding="0,6,0,0" width="30" font="0" /></HorizontalLayout>

      使用padding属性,这是底部这四个按钮的相对位置。如果我想整体让这四个控件向右位移10像素,那么我只要设置第一个按钮的padding属性为padding="10,0,0,0",就可以了,其他布局完全不需要修改!

结束语

   说到这里也就把布局的知识总结得差不多了,对于duilib的新手朋友,建议多看看各个demo的布局文件,他们几乎涵盖了所有知识点。然后结合我总结的东西自己手动写几个布局代码,相信很快就可以熟练编写界面布局了。另外我的前面博客里经常会分析布局,大家也可以看看。


以上文章转载自:http://www.cnblogs.com/Macchiato1988-10-17/p/5500899.html


3.Duilib各个类相关简介及类关联结构图.

UI里的宏
窗体样式宏
+---UI_WNDSTYLE_CONTAINER   容器样式,无任何样式
+---UI_WNDSTYLE_FRAME       UI标准框架窗体样式(标题栏、边框、图标、标题、最大、最小按钮、关闭按钮)
+---UI_WNDSTYLE_CHILD       UI窗体子控件样式
+---UI_WNDSTYLE_DIALOG      UI对话框窗体样式
扩展窗体样式宏
+---UI_WNDSTYLE_EX_FRAME    窗体客户区凹陷样式
+---UI_WNDSTYLE_EX_DIALOG   工具栏窗口样式+模式对话框样式
类样式宏
+---UI_CLASSSTYLE_CONTAINER 类容器样式
+---UI_CLASSSTYLE_FRAME     水平、垂直大小改变时绘制
+---UI_CLASSSTYLE_CHILD     水平、垂直大小改变时绘制、支持双击事件、保存窗体位图
+---UI_CLASSSTYLE_DIALOG    水平、垂直大小改变时绘制、支持双击事件、保存窗体位图

ASSERT(expr)                判断表达式是否为NULL或FALSE
TRACE(LPSTSTR pstrFormat,...)
                            在Debug输出窗口打印格式化字符串
TRACEMSG(UINT uMsg)         返回指定消息的16进制表示


一、核心类
1. CWindowWnd,窗口对象管理父类,主要作用:
 1) 创建窗口。
 2) 窗口消息过程处理。
 3) 提供窗口子类化与超类化接口。
2. CDialogBuilder,控件布局类,主要作用:
 1) 读取XML脚本,分析脚本,构建控件树。
 2) 创建控件对象。
3. CPaintManagerUI,窗口消息及图形绘制管理器类,与窗口绑定,主要作用:
 1) 绘制控件。
 2) 消息管理。
 3) 事件通知。
4. INotifyUI,事件通知抽象类,主要作用:
 1) 重载Notify虚函数,处理事件通知。
二、控件类
1. CControlUI,控件管理父类,主要作用:
 1) 控件的通用基类,提供控件通用属性管理。
2. CLabelUI,静态标签类,父类CControlUI。
3. CButtonUI,按钮类,父类CLabelUI。
4. COptionUI,选择按钮类,父类CButtonUI。
5. CTextUI,静态文本类,父类CLabelUI。
6. CProgressUI,进度条类,父类CLabelUI。
7. CSliderUI,父类CProgressUI。
8. CEditUI,编辑框类,父类CLabelUI。
9. CListUI,列表框类,父类CVerticalLayoutUI、IListUI。
 1) CListHeaderUI,父类CHorizontalLayoutUI。
 2) CListHeaderItemUI,列表头类,父类CControlUI。
 3) CListTextElementUI,类表文本类,父类CListLabelElementUI。
 4) CListLabelElementUI,父类CListElementUI。
10. CComboUI,组合框类,父类CContainerUI、IListOwnerUI。
11. CActiveXUI,ActiveX控件类,父类CControlUI、 IMessageFilterUI。
12. CContainerUI,容器类,父类CControlUI、IContainerUI。
13. CTabLayoutUI,选项页布局类,父类CContainerUI。
14. CTileLayoutUI,父类CContainerUI。
15. CDialogLayoutUI,对话框布局类,父类CContainerUI。、
16. CVerticalLayoutUI,垂直布局类,父类CContainerUI。
17. CHorizontalLayoutUI,水平布局类,父类CContainerUI。
18. CListExpandElementUI,父类CListTextElementUI。
19. CListContainerElementUI,父类CContainerUI、IListItemUI。
三、辅助类
1. CStdPtrArray,指针数组。
2. CStdValArray,数据数组。
3. CStdString,字符串数组。
4. CStdStringPtrMap,字符串指针映射数组。



原文来自:http://blog.csdn.NET/gaojinshan/article/details/8265206



4.DuiLib消息处理剖析

直接将总结提前了,可以依照这个直接实现消息处理,若下理清细节,继续参看下面文字详细部分介绍.

1.消息最先到达你的HandleMessage中.  <HandleMessage中调用MessageHandler.为了INotifyUI控件消息响应>

2.CPaintManagerUI消息类:内部有GetMessage,TranslateMessage DispatchMessage


父类为WindowImplBase,重载:HandleCustomMessage<中处理WM_DESTROY等消息,仅是个接口而已>;代码示例1

父类为CWindowWnd,重写HandleMessage.<处理:WM_CREATE并且在此消息中进行m_PaintManager.AttachDialog(pRoot);m_PaintManager.AddNotifier(this);代码示例2>

父类为WindowImplBase:可以重写HandleMessage:<示例代码3>


-----------------------------------说明:HandleMessage为高级截获,重写的时候必须确保消息能顺利传递下去--------------------------


代码示例1:

LRESULT CMainWnd::HandleCustomMessage(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled)
{
//直接 响应鼠标右键菜单消息.
if (uMsg == WM_RBUTTONDOWN)
{
CMsgWnd::MessageBox(m_hWnd, NULL, _T("你预定修潜艇服务"));

bHandled = TRUE; 
}

bHandled = FALSE; 处理返回TRUE,否则返回FALSE;

returu 0;

}


代码示例2:

virtual LRESULT HandleMessage(UINT uMsg, WPARAM wParam, LPARAM lParam)
{
LRESULT lRes = 0;


if (uMsg == WM_CREATE)
{
m_PaintManager.Init(m_hWnd);

CDialogBuilder builder;
CControlUI* pRoot = builder.Create(_T("Main.xml"), (UINT)0,this, &m_PaintManager);   // duilib.xml需要放到exe目录下
ASSERT(pRoot && "Failed to parse XML");

m_PaintManager.AttachDialog(pRoot);//附加控件数据到HASH表中
m_PaintManager.AddNotifier(this);   // 添加控件等消息响应,这样消息就会传达到duilib的消息循环,我们可以在Notify函数里做消息处理

return lRes;

if (m_PaintManager.MessageHandler(uMsg, wParam, lParam, lRes))
{
return lRes;
}

return __super::HandleMessage(uMsg, wParam, lParam);
}


示例代码3:

消息机制还没有完,这只是CPaintManagerUI::MessageHandler中的消息机制,如果继承的是 WindowImplBase, WindowImplBase实现了DuiLib窗口的大部分功能。WindowImplBase继承了CWindowWnd,重载了 HandleMessage,也就是说,消息发送的窗口过程后,第一个调用的是WindowImplBase::HandleMessage:
LRESULT WindowImplBase::HandleMessage(UINT uMsg, WPARAM wParam, LPARAM lParam)
{
LRESULT lRes = 0;
BOOL bHandled = TRUE;
switch (uMsg)
{
case WM_CREATE:lRes = OnCreate(uMsg, wParam, lParam, bHandled); break;
case WM_CLOSE:lRes = OnClose(uMsg, wParam, lParam, bHandled); break;
case WM_DESTROY:lRes = OnDestroy(uMsg, wParam, lParam, bHandled); break;
#if defined(WIN32) && !defined(UNDER_CE)
case WM_NCACTIVATE:lRes = OnNcActivate(uMsg, wParam, lParam, bHandled); break;
case WM_NCCALCSIZE:lRes = OnNcCalcSize(uMsg, wParam, lParam, bHandled); break;
case WM_NCPAINT:lRes = OnNcPaint(uMsg, wParam, lParam, bHandled); break;
case WM_NCHITTEST:lRes = OnNcHitTest(uMsg, wParam, lParam, bHandled); break;
case WM_GETMINMAXINFO:       lRes = OnGetMinMaxInfo(uMsg, wParam, lParam, bHandled); break;
case WM_MOUSEWHEEL:lRes = OnMouseWheel(uMsg, wParam, lParam, bHandled); break;
#endif
case WM_SIZE: lRes = OnSize(uMsg, wParam, lParam, bHandled); break;
case WM_CHAR:        lRes = OnChar(uMsg, wParam, lParam, bHandled); break;
case WM_SYSCOMMAND:lRes = OnSysCommand(uMsg, wParam, lParam, bHandled); break;
case WM_KEYDOWN:lRes = OnKeyDown(uMsg, wParam, lParam, bHandled); break;
case WM_KILLFOCUS:lRes = OnKillFocus(uMsg, wParam, lParam, bHandled); break;
case WM_SETFOCUS:lRes = OnSetFocus(uMsg, wParam, lParam, bHandled); break;
case WM_LBUTTONUP:lRes = OnLButtonUp(uMsg, wParam, lParam, bHandled); break;
case WM_LBUTTONDOWN:       lRes = OnLButtonDown(uMsg, wParam, lParam, bHandled); break;
case WM_MOUSEMOVE:lRes = OnMouseMove(uMsg, wParam, lParam, bHandled); break;
case WM_MOUSEHOVER:       lRes = OnMouseHover(uMsg, wParam, lParam, bHandled); break;
default: bHandled = FALSE; break;
}
if (bHandled) return lRes;
lRes = HandleCustomMessage(uMsg, wParam, lParam, bHandled);
if (bHandled) return lRes;
if (m_PaintManager.MessageHandler(uMsg, wParam, lParam, lRes))
return lRes;
return CWindowWnd::HandleMessage(uMsg, wParam, lParam);
}
WindowImplBase处理一些消息,使用成员函数On***来处理消息,所以,可以重载这些函数达到消息过滤的目的。 然后,我们看到,有一个函数:WindowImplBase::HandleCustomMessage,它是虚函数,我们可以重载此函数,进行消息过滤,由于还没有调用m_PaintManager.MessageHandler,
所以在收到Notify消息之前进行的过滤

基于窗体类的消息映射宏机制:


DUI_DECLARE_MESSAGE_MAP()

DUI_BEGIN_MESSAGE_MAP(CKeyBoardDlg, CNotifyPump)

    DUI_ON_MSGTYPE(DUI_MSGTYPE_CLICK, OnClick)

    DUI_ON_MSGTYPE(DUI_MSGTYPE_WINDOWINIT, OnInitWindow)

DUI_END_MESSAGE_MAP()



--4的内附加版本:

文章来源:http://blog.csdn.net/zhuhongshu/article/details/41725657


分为几个大部分:

  1. 控件
  2. 容器(本质也是控件)
  3. UI构建解析器(XML解析)
  4. 窗体管理器(消息循环,消息映射,消息处理,窗口管理等)
  5. 渲染引擎

DuiLib 消息循环剖析

DuiLib的消息循环非常灵活,但不熟悉的可能会觉得非常混乱,不知道该如何下手。所以,我总结了下DuiLib的各种消息响应的方式,帮助大家理解DuiLib和加快开发速度。

其消息处理架构较为灵活,基本上在消息能过滤到的地方,都给出了扩展接口。

看了DuiLib入门教程后,对消息机制的处理有些模糊,为了屏蔽Esc按键,都花了大半天的时间。究其原因,是因为对DuiLib消息过滤不了解。
你至少应该看过上面提及的那篇入门教程,看过一些DuiLib的代码,但可能没看懂,那么这篇文章会给你指点迷津。

Win32消息路由如下:

  1. 消息产生。
  2. 系统将消息排列到其应该排放的线程消息队列中。
  3. 线程中的消息循环调用GetMessage(or PeekMessage)获取消息。
  4. 传送消息TranslateMessage and DispatchMessage to 窗口过程(Windows procedure)。
  5. 在窗口过程里进行消息处理

我们看到消息经过几个步骤,DuiLib架构可以让你在某些步骤间进行消息过滤。首先,第1、2和3步骤,DuiLib并不关心。DuiLib对消息处理集中在CPaintManagerUI类中(也就是上面提到的窗体管理器)。DuiLib在发送到窗口过程的前和后都进行了消息过滤。

DuiLib的消息渠,也就是所谓的消息循环在CPaintManagerUI::MessageLoop()或者CWindowWnd::ShowModal()中实现。俩套代码的核心基本一致,以MessageLoop为例:

void CPaintManagerUI::MessageLoop(){    MSG msg = { 0 };    while( ::GetMessage(&msg, NULL, 0, 0) ) {        // CPaintManagerUI::TranslateMessage进行消息过滤        if( !CPaintManagerUI::TranslateMessage(&msg) ) {            ::TranslateMessage(&msg);            try{            ::DispatchMessage(&msg);            } catch(...) {                DUITRACE(_T("EXCEPTION: %s(%d)\n"), __FILET__, __LINE__);                #ifdef _DEBUG                throw "CPaintManagerUI::MessageLoop";                #endif            }        }    }}

3和4之间,DuiLib调用CPaintManagerUI::TranslateMessage做了过滤,类似MFC的PreTranlateMessage

想象一下,如果不使用这套消息循环代码,我们如何能做到在消息发送到窗口过程前进行常规过滤(Hook等拦截技术除外)?答案肯定是做不到。因为那段循环 代码你是无法控制的。CPaintManagerUI::TranslateMessage将无法被调用,所以,可以看到DuiLib中几乎所有的 demo在创建玩消息后,都调用了这俩个消息循环函数。下面是TranslateMessage代码:

bool CPaintManagerUI::TranslateMessage(const LPMSG pMsg){    // Pretranslate Message takes care of system-wide messages, such as    // tabbing and shortcut key-combos. We'll look for all messages for    // each window and any child control attached.    UINT uStyle = GetWindowStyle(pMsg->hwnd);    UINT uChildRes = uStyle & WS_CHILD;        LRESULT lRes = 0;    if (uChildRes != 0) // 判断子窗口还是父窗口    {        HWND hWndParent = ::GetParent(pMsg->hwnd);        for( int i = 0; i < m_aPreMessages.GetSize(); i++ )         {            CPaintManagerUI* pT = static_cast<CPaintManagerUI*>(m_aPreMessages[i]);                    HWND hTempParent = hWndParent;            while(hTempParent)            {                if(pMsg->hwnd == pT->GetPaintWindow() || hTempParent == pT->GetPaintWindow())                {                    if (pT->TranslateAccelerator(pMsg))                        return true;                    // 这里进行消息过滤                    if( pT->PreMessageHandler(pMsg->message, pMsg->wParam, pMsg->lParam, lRes) )                         return true;                    return false;                }                hTempParent = GetParent(hTempParent);            }        }    }    else    {        for( int i = 0; i < m_aPreMessages.GetSize(); i++ )         {            CPaintManagerUI* pT = static_cast<CPaintManagerUI*>(m_aPreMessages[i]);            if(pMsg->hwnd == pT->GetPaintWindow())            {                if (pT->TranslateAccelerator(pMsg))                    return true;                if( pT->PreMessageHandler(pMsg->message, pMsg->wParam, pMsg->lParam, lRes) )                     return true;                return false;            }        }    }    return false;}bool CPaintManagerUI::PreMessageHandler(UINT uMsg, WPARAM wParam, LPARAM lParam, LRESULT& /*lRes*/){    for( int i = 0; i < m_aPreMessageFilters.GetSize(); i++ )     {        bool bHandled = false;        LRESULT lResult = static_cast<IMessageFilterUI*>(m_aPreMessageFilters[i])->MessageHandler(uMsg, wParam, lParam, bHandled); // 这里调用接口 IMessageFilterUI::MessageHandler 来进行消息过滤        if( bHandled ) {            return true;        }}…… ……return false;}

在发送到窗口过程前,有一个过滤接口:IMessageFilterUI,此接口只有一个成员:MessageHandler,我们的窗口类要提前过滤消息,只要实现这个IMessageFilterUI,调用CPaintManagerUI::AddPreMessageFilter,将我们的窗口类实例指针添加到CPaintManagerUI::m_aPreMessageFilters 数组中。当消息到达窗口过程之前,就会会先调用我们的窗口类的成员函数:MessageHandler。

下面是AddPreMessageFilter代码:

bool CPaintManagerUI::AddPreMessageFilter(IMessageFilterUI* pFilter){    // 将实现好的接口实例,保存到数组 m_aPreMessageFilters 中。    ASSERT(m_aPreMessageFilters.Find(pFilter)<0);    return m_aPreMessageFilters.Add(pFilter);}

我们从函数CPaintManagerUI::TranslateMessage代码中能够看到,这个过滤是在大循环:

for( int i = 0; i < m_aPreMessages.GetSize(); i++ )

中被调用的。如果m_aPreMessages.GetSize()为0,也就不会调用过滤函数。从代码中追溯其定义:

static CStdPtrArray m_aPreMessages;

是个静态变量,MessageLoop,TranslateMessage等也都是静态函数。其值在CPaintManagerUI::Init中被初始化:

void CPaintManagerUI::Init(HWND hWnd){    ASSERT(::IsWindow(hWnd));    // Remember the window context we came from    m_hWndPaint = hWnd;    m_hDcPaint = ::GetDC(hWnd);    // We'll want to filter messages globally too    m_aPreMessages.Add(this);}

看来,m_aPreMessages存储的类型为CPaintManagerUI* ,也就说,这个静态成员数组里,存储了当前进程中所有的CPaintManagerUI实例指针,所以,如果有多个CPaintManagerUI实例, 也不会存在过滤问题,互不干扰,都能各自过滤。当然m_aPreMessages不止用在消息循环中,也有别的用处。我觉得这个名字起得有点诡异。

然后再说,消息抵达窗口过程后,如何处理。首先,要清楚,窗口过程在哪儿?使用DuiLib开发,我们的窗口类无外呼,继承俩个基类:一个是功能简陋一点 的:CWindowWnd,一个是功能健全一点的:WindowImplBase(继承于CWindowWnd)。然后,我们实例化窗口类,调用这俩个基 类的Create函数,创建窗口,其内部注册了窗口过程:

LRESULT CALLBACK CWindowWnd::__WndProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam){    CWindowWnd* pThis = NULL;    if( uMsg == WM_NCCREATE ) {        LPCREATESTRUCT lpcs = reinterpret_cast<LPCREATESTRUCT>(lParam);        pThis = static_cast<CWindowWnd*>(lpcs->lpCreateParams);        pThis->m_hWnd = hWnd;        ::SetWindowLongPtr(hWnd, GWLP_USERDATA, reinterpret_cast<LPARAM>(pThis));    }     else {        pThis = reinterpret_cast<CWindowWnd*>(::GetWindowLongPtr(hWnd, GWLP_USERDATA));        if( uMsg == WM_NCDESTROY && pThis != NULL ) {            LRESULT lRes = ::CallWindowProc(pThis->m_OldWndProc, hWnd, uMsg, wParam, lParam);            ::SetWindowLongPtr(pThis->m_hWnd, GWLP_USERDATA, 0L);            if( pThis->m_bSubclassed ) pThis->Unsubclass();            pThis->m_hWnd = NULL;            pThis->OnFinalMessage(hWnd);            return lRes;        }    }    if( pThis != NULL ) {        return pThis->HandleMessage(uMsg, wParam, lParam);    }     else {        return ::DefWindowProc(hWnd, uMsg, wParam, lParam);    }}

里面,主要做了一些转换,细节自行研究,最终,他会调用pThis→HandleMessage(uMsg, wParam, lParam);。也即是说,HandleMessage相当于一个窗口过程(虽然它不是,但功能类似)。他是CWindowWnd的虚函数:

virtual LRESULT HandleMessage(UINT uMsg, WPARAM wParam, LPARAM lParam);

所以,如果我们的窗口类实现了HandleMessage,就相当于再次过滤了窗口过程,HandleMessage代码框架如下:

LRESULT HandleMessage(UINT uMsg, WPARAM wParam, LPARAM lParam){if( uMsg == WM_XXX ) {    … …         return 0;    }    else if( uMsg == WM_XXX) {    … …         return 1;}    LRESULT lRes = 0;if( m_pm.MessageHandler(uMsg, wParam, lParam, lRes) ) //CPaintManagerUI::MessageHandlerreturn lRes;    return CWindowWnd::HandleMessage(uMsg, wParam, lParam); // 调用父类HandleMessage}

在注意:CPaintManagerUI::MessageHandler,名称为MessageHandler,而不是HandleMessage。
没有特殊需求,一定要调用此函数,此函数处理了绝大部分常用的消息响应。而且如果你要响应Notify事件,不调用此函数将无法响应,后面会介绍。

好现在我们已经知道,俩个地方可以截获消息:

  1. 实现IMessageFilterUI接口,调用CPaintManagerUI:: AddPreMessageFilter,进行消息发送到窗口过程前的过滤。
  2. 重载HandleMessage函数,当消息发送到窗口过程中时,最先进行过滤。

下面继续看看void Notify(TNotifyUI& msg)是如何响应的。我们的窗口继承于INotifyUI接口,就必须实现此函数:

class INotifyUI

{

public:

    virtual void Notify(TNotifyUI& msg) = 0;

};

上面我说了,在我们的HandleMessage要调用CPaintManagerUI::MessageHandler来进行后续处理。下面是一个代码片段:

bool CPaintManagerUI::MessageHandler(UINT uMsg, WPARAM wParam, LPARAM lParam, LRESULT& lRes){    … …    TNotifyUI* pMsg = NULL;    while( pMsg = static_cast<TNotifyUI*>(m_aAsyncNotify.GetAt(0)) ) {        m_aAsyncNotify.Remove(0);        if( pMsg->pSender != NULL ) {            if( pMsg->pSender->OnNotify ) pMsg->pSender->OnNotify(pMsg);        }        // 先看这里,其它代码先忽略;我们看到一个转换操作static_cast<INotifyUI*>        for( int j = 0; j < m_aNotifiers.GetSize(); j++ ) {            static_cast<INotifyUI*>(m_aNotifiers[j])->Notify(*pMsg);        }        delete pMsg;    }     // Cycle through listeners    for( int i = 0; i < m_aMessageFilters.GetSize(); i++ )     {        bool bHandled = false;        LRESULT lResult = static_cast<IMessageFilterUI*>(m_aMessageFilters[i])->MessageHandler(uMsg, wParam, lParam, bHandled);        if( bHandled ) {            lRes = lResult;            return true;        }}… …}

定义为CStdPtrArray m_aNotifiers;数组,目前还看不出其指向的实际类型。看看,什么时候给该数组添加成员:

bool CPaintManagerUI::AddNotifier(INotifyUI* pNotifier){    ASSERT(m_aNotifiers.Find(pNotifier)<0);    return m_aNotifiers.Add(pNotifier);}

不错,正是AddNotifier,类型也有了:INotifyUI。所以,入门教程里会在响应WM_CREATE消息的时候,调用 AddNotifier(this),将自身加入数组中,然后在CPaintManagerUI::MessageHandler就能枚举调用。由于 AddNotifer的参数为INotifyUI*,所以,我们要实现此接口。 
所以,当HandleMessage函数被调用后,紧接着会调用我们的Notify函数。如果你没有对消息过滤的特殊需求,实现INotifyUI即可,在Notify函数中处理消息响应。

上面的Notify调用,是响应系统产生的消息。程序本身也能手动产生,其函数为:

void CPaintManagerUI::SendNotify(TNotifyUI& Msg, bool bAsync /*= false*/)

DuiLib将发送的Notify消息分为了同步和异步消息。同步就是立即调用(类似SendMessage),异步就是先放到队列中,下次再处理。(类似PostMessage)。

void CPaintManagerUI::SendNotify(TNotifyUI& Msg, bool bAsync /*= false*/){    … …    if( !bAsync ) {        // Send to all listeners        // 同步调用OnNotify,注意不是Notify        if( Msg.pSender != NULL ) {            if( Msg.pSender->OnNotify ) Msg.pSender->OnNotify(&Msg);        }        // 还会再次通知所有注册了INotifyUI的窗口。        for( int i = 0; i < m_aNotifiers.GetSize(); i++ ) {            static_cast<INotifyUI*>(m_aNotifiers[i])->Notify(Msg);        }    }else {        // 异步调用,添加到m_aAsyncNotify array中        TNotifyUI *pMsg = new TNotifyUI;        pMsg->pSender = Msg.pSender;        pMsg->sType = Msg.sType;        pMsg->wParam = Msg.wParam;        pMsg->lParam = Msg.lParam;        pMsg->ptMouse = Msg.ptMouse;        pMsg->dwTimestamp = Msg.dwTimestamp;        m_aAsyncNotify.Add(pMsg);    }}

我们CPaintManagerUI::MessageHandler在开始处发现一些代码:

TNotifyUI* pMsg = NULL;

while( pMsg = static_cast<TNotifyUI*>(m_aAsyncNotify.GetAt(0)) ) {

m_aAsyncNotify.Remove(0);

if( pMsg->pSender != NULL ) {

if( pMsg->pSender->OnNotify ) pMsg->pSender->OnNotify(pMsg);

}

可以看到MessageHandler首先从异步队列中一个消息并调用OnNotify。OnNotify和上面的Notify不一样哦。

OnNotify是响应消息的另外一种方式。它的定义为:

CEventSource OnNotify;

属于CControlUI类。重载了一些运算符,如 operator();要让控件响应手动发送(SendNotify)的消息,就要给控件的OnNotify,添加消息代理。在DuiLib的TestApp1中的OnPrepare函数里,有:

CSliderUI* pSilder = static_cast<CSliderUI*>(m_pm.FindControl(_T("alpha_controlor")));

if( pSilder ) pSilder->OnNotify += MakeDelegate(this, &CFrameWindowWnd::OnAlphaChanged);

至于代理的代码实现,我就不展示了,这里简单说明,就是将类成员函数,作为回调函数,加入到OnNotify中,然后调用 pMsg→pSender→OnNotify(pMsg)的时候,循环调用所有的类函数,实现通知的效果。代理代码处理的很巧妙,结合多态和模板,能将任 何类成员函数作为回调函数。 

查阅CSliderUI代码,发现他在自身的DoEvent函数内调用了诸如:

m_pManager->SendNotify(this, DUI_MSGTYPE_VALUECHANGED);

类似的代码,调用它,我们就会得到通知。

现在,又多了两种消息处理的方式:

  1. 实现INotifyUI,调用CPaintManagerUI::AddNotifier,将自身加入Notifier队列。
  2. 添加消息代理(其实就是将成员函数最为回到函数加入),MakeDelegate(this, &CFrameWindowWnd::OnAlphaChanged);,当程序某个地方调用了 CPaintManagerUI::SendNotify,并且Msg.pSender正好是注册的this,我们的类成员回调函数将被调用。

搜寻CPaintManagerUI代码,我们发现还有一些消息过滤再里面:

bool CPaintManagerUI::AddMessageFilter(IMessageFilterUI* pFilter)

{
    ASSERT(m_aMessageFilters.Find(pFilter)<0);
    return m_aMessageFilters.Add(pFilter);

}

m_aMessageFilters也是IMessageFilterUI array,和m_aPreMessageFilters类似。

上面我们介绍的是CPaintManagerUI::AddPreMessageFilter,那这个又是在哪儿做的过滤?

还是CPaintManagerUI::MessageHandler中:

……    // Cycle through listeners    for( int i = 0; i < m_aMessageFilters.GetSize(); i++ )     {        bool bHandled = false;        LRESULT lResult = static_cast<IMessageFilterUI*>(m_aMessageFilters[i])->MessageHandler(uMsg, wParam, lParam, bHandled);        if( bHandled ) {            lRes = lResult;            return true;        }}… …

这个片段是在,异步OnNotify和Nofity消息响应,被调用后。才被调用的,优先级也就是最低。但它始终会被调用,因为异步OnNotify和 Nofity消息响应没有返回值,不会因为消息已经被处理,而直接退出。DuiLib再次给用户一个处理消息的机会。用户可以选择将bHandled设置 为True,从而终止消息继续传递。我觉得,这个通常是为了弥补OnNotify和Nofity没有返回值的问题,在m_aMessageFilters 做集中处理。

处理完所有的消息响应后,如果消息没有被截断,CPaintManagerUI::MessageHandler继续处理大多数默认的消息,它会处理在其管理范围中的所有控件的大多数消息和事件等。

然后,消息机制还没有完,这只是CPaintManagerUI::MessageHandler中的消息机制,如果继承的是 WindowImplBase, WindowImplBase实现了DuiLib窗口的大部分功能。WindowImplBase继承了CWindowWnd,重载了 HandleMessage,也就是说,消息发送的窗口过程后,第一个调用的是WindowImplBase::HandleMessage:

LRESULT WindowImplBase::HandleMessage(UINT uMsg, WPARAM wParam, LPARAM lParam){LRESULT lRes = 0;BOOL bHandled = TRUE;switch (uMsg){case WM_CREATE:lRes = OnCreate(uMsg, wParam, lParam, bHandled); break;case WM_CLOSE:lRes = OnClose(uMsg, wParam, lParam, bHandled); break;case WM_DESTROY:lRes = OnDestroy(uMsg, wParam, lParam, bHandled); break;#if defined(WIN32) && !defined(UNDER_CE)case WM_NCACTIVATE:lRes = OnNcActivate(uMsg, wParam, lParam, bHandled); break;case WM_NCCALCSIZE:lRes = OnNcCalcSize(uMsg, wParam, lParam, bHandled); break;case WM_NCPAINT:lRes = OnNcPaint(uMsg, wParam, lParam, bHandled); break;case WM_NCHITTEST:lRes = OnNcHitTest(uMsg, wParam, lParam, bHandled); break;case WM_GETMINMAXINFO:        lRes = OnGetMinMaxInfo(uMsg, wParam, lParam, bHandled); break;case WM_MOUSEWHEEL:lRes = OnMouseWheel(uMsg, wParam, lParam, bHandled); break;#endifcase WM_SIZE:lRes = OnSize(uMsg, wParam, lParam, bHandled); break;case WM_CHAR:        lRes = OnChar(uMsg, wParam, lParam, bHandled); break;case WM_SYSCOMMAND:lRes = OnSysCommand(uMsg, wParam, lParam, bHandled); break;case WM_KEYDOWN:lRes = OnKeyDown(uMsg, wParam, lParam, bHandled); break;case WM_KILLFOCUS:lRes = OnKillFocus(uMsg, wParam, lParam, bHandled); break;case WM_SETFOCUS:lRes = OnSetFocus(uMsg, wParam, lParam, bHandled); break;case WM_LBUTTONUP:lRes = OnLButtonUp(uMsg, wParam, lParam, bHandled); break;case WM_LBUTTONDOWN:        lRes = OnLButtonDown(uMsg, wParam, lParam, bHandled); break;case WM_MOUSEMOVE:lRes = OnMouseMove(uMsg, wParam, lParam, bHandled); break;case WM_MOUSEHOVER:        lRes = OnMouseHover(uMsg, wParam, lParam, bHandled); break;default:bHandled = FALSE; break;}if (bHandled) return lRes; lRes = HandleCustomMessage(uMsg, wParam, lParam, bHandled);if (bHandled) return lRes; if (m_PaintManager.MessageHandler(uMsg, wParam, lParam, lRes))return lRes;return CWindowWnd::HandleMessage(uMsg, wParam, lParam);}

WindowImplBase处理一些消息,使用成员函数On***来处理消息,所以,可以重载这些函数达到消息过滤的目的。 然后,我们看到,有一个函数:WindowImplBase::HandleCustomMessage,它是虚函数,我们可以重载此函数,进行消息过滤,由于还没有调用m_PaintManager.MessageHandler,所以在收到Notify消息之前进行的过滤。

有多了两种方式:

  • 重载父类:WindowImplBase的虚函数
  • 重载父类:WindowImplBase::HandleCustomMessage函数

最后,继承于WindowImplBase,还有一种过滤消息的方式,和Notify消息平级,实现方式是仿造的MFC消息映射机制: WindowImplBase实现了INotifyUI接口,并且AddNotify了自身,所以,它会收到Notify消息:

void WindowImplBase::Notify(TNotifyUI& msg)

{

    return CNotifyPump::NotifyPump(msg);

}

NotifyPump 调用 LoopDispatch,代码片段如下:

... ... const DUI_MSGMAP_ENTRY* lpEntry = NULL;const DUI_MSGMAP* pMessageMap = NULL; #ifndef UILIB_STATICfor(pMessageMap = GetMessageMap(); pMessageMap!=NULL; pMessageMap = (*pMessageMap->pfnGetBaseMap)())#elsefor(pMessageMap = GetMessageMap(); pMessageMap!=NULL; pMessageMap = pMessageMap->pBaseMap)#endif{#ifndef UILIB_STATICASSERT(pMessageMap != (*pMessageMap->pfnGetBaseMap)());#elseASSERT(pMessageMap != pMessageMap->pBaseMap);#endifif ((lpEntry = DuiFindMessageEntry(pMessageMap->lpEntries,msg)) != NULL){goto LDispatch;}}... ...

代码量过多,这里进行原理说明,和MFC一样,提供了一些消息宏

DUI_DECLARE_MESSAGE_MAP()

   

DUI_BEGIN_MESSAGE_MAP(CKeyBoardDlg, CNotifyPump)

    DUI_ON_MSGTYPE(DUI_MSGTYPE_CLICK, OnClick)

    DUI_ON_MSGTYPE(DUI_MSGTYPE_WINDOWINIT, OnInitWindow)

DUI_END_MESSAGE_MAP()

和MFC原理一样,声明一些静态变量,存储类的信息,插入一些成员函数,最为回调,最后生成一张静态表。当WindowImplBase::Notify有消息时,遍历表格,进行消息通知。

总结,DuiLib消息响应方式:

  • 实现IMessageFilterUI接口,调用CPaintManagerUI::AddPreMessageFilter,进行消息发送到窗口过程前的过滤。
  • 重载HandleMessage函数,当消息发送到窗口过程中时,最先进行过滤。
  • 实现INotifyUI,调用CPaintManagerUI::AddNotifier,将自身加入Notifier队列。
  • 添加消息代理(其实就是将成员函数最为回到函数加入),MakeDelegate(this, &CFrameWindowWnd::OnAlphaChanged);,当程序某个地方调用了 CPaintManagerUI::SendNotify,并且Msg.pSender正好是this,我们的类成员回调函数将被调用。
  • 重载父类:WindowImplBase的虚函数
  • 重载父类:WindowImplBase::HandleCustomMessage函数
  • 使用类似MFC的消息映射


6.XML描述文件位置

Duilib的界面表现力能如此丰富,很大程度上得益于贴图描述的简单强大。通过之前的学习及参看相关例子,我们可以发现,在XML布局文件中,不 管是窗体背景还是控件,都添加了对应的图片资源以此来美化界面。而很多人在参看例子时发现有着多种添加资源方式,其实总结下来可以简单概括为一下两种:

简单模式

使用文件名作为贴图内容,这种方式下,此图片将会以拉伸方式铺满控件。

复杂模式

使用带属性的字符串表示贴图方式,既支持从文件中加载图片,也可以从资源中加载图片:

01. 如果从文件加载,设置file属性(例如:file=’XX.png’),不要写res和restype属性;

02.  如果从资源加载,设置res和restype属性,不要设置file属性;

其中各属性参数作为如下:

dest属性的作用是指定图片绘制在控件的一部分上面(绘制目标位置)

source属性的作用是指定使用图片的一部分

corner属性是指图片安装scale9方式绘制

mask属性是给不支持alpha通道的图片格式(如bmp)指定透明色

fade属性是设置图片绘制的透明度

hole属性是指定scale9绘制时要不要绘制中间部分;

xtiled属性设置成true就是指定图片在x轴不要拉伸而是平铺,ytiled属性设置成true就是指定图片在y轴不要拉伸而是平铺;


原文转载自:http://www.360doc.com/content/14/1118/20/7023119_426231816.shtml


7.为窗口添加上级风格和双击消息

最近看duilib例子,看到这个我一脸懵逼,感觉好熟悉.,但是一下子没回想起来:


窗口类样式:

CS_DBLCLKS

CS_DBLCLKS标志使窗口可以检测到双击事件。窗口响应双击的细节如下:

  • 如果窗口没有CS_DBLCLKS标志,系统向窗口依次发送如下消息:

WM_LBUTTONDOWN

  • WM_LBUTTONUP
  • WM_LBUTTONDOWN
  • WM_LBUTTONUP.

其实相当于两个单击。

  • 如果窗口有CS_DBLCLKS标志,则系统向窗口依次发送如下消息:

WM_LBUTTONDOWN

  • WM_LBUTTONUP
  • WM_LBUTTONDBLCLK
  • WM_LBUTTONUP.

第一种情况中的第二个WM_LBUTTONDOWNWM_LBUTTONDBLCLK代替了。

注意,在上述序列中间可能会插入其他的一条或一些消息,所以这两个消息序列不一定是完全连续的。

其实,在没有指定CS_DBLCLKS标志时,程序本身也可以检测到双击事件的。参见MSDNDr.GUT"Simulating Mouse Button Clicks"文章,不过要有一些技巧.一般的情况下,如果没有指定CS_DBLCLKS,在窗口的消息循环里将不会得到WM_LBUTTONDBLCLK消息。

所有的标准窗口控件,对话框,桌面窗口类都默认拥有CS_DBLCLKS标志。第三方控件最好也加上此风格,以使其在对话框编辑器里可以正常工作。


CS_HREDRAW ,CS_VREDRAW  
http://zhidao.baidu.com/question/32047449.html
 wangruigaoshou
CS_HREDRAW当窗口水平方向的宽度变化时重绘整个窗口.CS_VREDRAW 当窗口垂直方向的宽度变化时重绘整个窗口.

CS_SAVEBITS
http://topic.csdn.net/u/20090728/22/90265d07-f30a-431a-b02a-cec93ea87883.html
将屏幕图像中被该(窗口)类窗口遮挡的部分保存为一个位图。当窗口移走时,系统用保存的位图来还原屏幕图像,包括被遮挡住的其他窗口。因此,如果位图占用的内存没被释放且其他屏幕操作未宣布存储图像无效,系统不发送WM_PAINT消息至那些被遮挡的窗口。
  此窗口风格对那些短暂显示并在其他屏幕活动发生前移除的小窗口(如菜单或对话框)很有用。这一风格增加了显示窗口所需的时间,因为系统必须先分配内存来存储位图。


自己新建一个win32应用程序测试截图:


8.duilib各个控件消息响应分类.

duilib中各控件响应的消息类型

消息说明Senderclick鼠标点击CButtonUIdropdown下拉显示CComboUIheaderclick点击列标题CListHeaderItemUIitemactivate CListElementUI、CListContainerElementUIitemclick单击选项CListLabelElementUI、CListContainerElementUIitemselect选择选项CComboUI、CListUIkillfocus失去焦点CControlUIlink CTextUI、CListTextElementUImenu CButtonUI、CControlUIreturn回车CEditWnd、CRichEditUIscroll滚动CScrollBarUIselectchanged变更选项COptionUIsetfocus获得焦点CControlUIshowactivex CActiveXUItextchanged文本被改变CEditWndtabselect标签页被选中CTabLayoutUItimer CControlUIvaluechanged值发生变化CSliderUIwindowinit窗体初始化 
  消息说明Senderclick鼠标点击CButtonUIdropdown下拉显示CComboUIheaderclick点击列标题CListHeaderItemUIitemactivate CListElementUI、CListContainerElementUIitemclick单击选项CListLabelElementUI、CListContainerElementUIitemselect选择选项CComboUI、CListUIkillfocus失去焦点CControlUIlink CTextUI、CListTextElementUImenu CButtonUI、CControlUIreturn回车CEditWnd、CRichEditUIscroll滚动CScrollBarUIselectchanged变更选项COptionUIsetfocus获得焦点CControlUIshowactivex CActiveXUItextchanged文本被改变CEditWndtabselect标签页被选中CTabLayoutUItimer CControlUIvaluechanged值发生变化CSliderUIwindowinit窗体初始化 

转载自:http://www.cnblogs.com/MuyouSome/p/3424137.html


9.trunk技术的理解<传入this指针>

既然它能够生成单文档的框架窗口,那么代码中所做的几步基本上与用纯粹的win32 API相同,所以我们沿着这个思路来进行框架的简单剖析。 
主函数中首先是代码CPaintManagerUI::SetInstance(hInstance);至于类CPaintManagerUI到底有什么作用,这个我也不太清楚,现在我还没有仔细看关于这个类的相关代码,这句话主要还是获取了进程的实例句柄。现在先不关心这个。下面的几步主要是在类CDuiFrameWnd中完成或者说在它的基类CWindowWnd中完成。

创建窗口类

主函数中的第二段代码主要完成的是类CDuiFrameWnd对象的创建,我们跟到对应的构造函数中发现它并没有做多余的操作,现在先不管它是如何构造的,它下面就是调用了类的Create函数创建了一个窗口,这个函数的代码如下:

HWND CWindowWnd::Create(HWND hwndParent, LPCTSTR pstrName, DWORD dwStyle, DWORD dwExStyle, int x, int y, int cx, int cy, HMENU hMenu){    if( GetSuperClassName() != NULL && !RegisterSuperclass() ) return NULL;    if( GetSuperClassName() == NULL && !RegisterWindowClass() ) return NULL;    m_hWnd = ::CreateWindowEx(dwExStyle, GetWindowClassName(), pstrName, dwStyle, x, y, cx, cy, hwndParent, hMenu, CPaintManagerUI::GetInstance(), this);    ASSERT(m_hWnd!=NULL);    return m_hWnd;}

我们主要来看第二个if中的代码,首先获得了父窗口的字符串为NULL,然后执行RegisterWindowClass,我们进一步跟到RegisterWindowClass中,它的代码如下:

bool CWindowWnd::RegisterWindowClass(){    WNDCLASS wc = { 0 };    wc.style = GetClassStyle();    wc.cbClsExtra = 0;    wc.cbWndExtra = 0;    wc.hIcon = NULL;    wc.lpfnWndProc = CWindowWnd::__WndProc;    wc.hInstance = CPaintManagerUI::GetInstance(); //之前设置的实例句柄在这个地方使用    wc.hCursor = ::LoadCursor(NULL, IDC_ARROW);    wc.hbrBackground = NULL;    wc.lpszMenuName  = NULL;    wc.lpszClassName = GetWindowClassName();    ATOM ret = ::RegisterClass(&wc);    ASSERT(ret!=NULL || ::GetLastError()==ERROR_CLASS_ALREADY_EXISTS);    return ret != NULL || ::GetLastError() == ERROR_CLASS_ALREADY_EXISTS;}

我们发现首先进行的是窗口类的创建,在创建窗口类时主要关心的是窗口类的lpfnWndProc成员和lpszClassName 。lpszClassName 调用了函数GetWindowClassName,这个函数我们在派生类中进行了重写,所以根据多态它会调用派生类的GetWindowClassName函数,将我们给定的字符串作为窗口类的类名

注册窗口类

从上面的代码可以看出注册的代码也是放在RegisterWindowClass中。在最后调用了RegisterClass函数完成了注册。

创建窗口

当RegisterWindowClass执行完成后,会接着执行下面的代码,也就是 m_hWnd = ::CreateWindowEx(dwExStyle, GetWindowClassName(), pstrName, dwStyle, x, y, cx, cy, hwndParent, hMenu, CPaintManagerUI::GetInstance(), this);完成创建窗口的任务。

显示窗口

Create函数执行完成后,会接着执行下面的duiFrame.ShowWindow();我们跟到这个函数中,函数代码如下:

void CWindowWnd::ShowWindow(bool bShow /*= true*/, bool bTakeFocus /*= false*/){    ASSERT(::IsWindow(m_hWnd));    if( !::IsWindow(m_hWnd) ) return;    ::ShowWindow(m_hWnd, bShow ? (bTakeFocus ? SW_SHOWNORMAL : SW_SHOWNOACTIVATE) : SW_HIDE);}

函数ShowWindow默认传入参数为bShow = true bTakeFocus = false;在最后进行ShowWindow函数的调用时,根据bShow和bTakeFocus来进行值得传入,根据代码我们发现,当不传入参数时调用的其实是这样的代码ShowWindow(m_hWnd, SW_SHOWNOACTIVATE);

消息循环

消息循环其实是通过代码CPaintManagerUI::MessageLoop();完成,我们跟到MessageLoop函数中看

    MSG msg = { 0 };    while( ::GetMessage(&msg, NULL, 0, 0) ) {        if( !CPaintManagerUI::TranslateMessage(&msg) ) {            ::TranslateMessage(&msg);            ::DispatchMessage(&msg);        }    }

在这个函数中完成了消息循环。

回调函数

上面我们留了一个lpfnWndProc函数指针没有说,现在来说明这个部分,跟进到对应的构造函数中,发现类本身不做任何操作,但是父类的构造函数进行了相关的初始化操作,下面是对应的代码

CWindowWnd::CWindowWnd() : m_hWnd(NULL), m_OldWndProc(::DefWindowProc), m_bSubclassed(false){}

这样就将lpfnWndProc指向了__WndProc,用于处理默认的消息。 
这是一个静态的处理函数,下面是它的代码:

LRESULT CALLBACK CWindowWnd::__WndProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam){    CWindowWnd* pThis = NULL;    if( uMsg == WM_NCCREATE ) {        LPCREATESTRUCT lpcs = reinterpret_cast<LPCREATESTRUCT>(lParam);        pThis = static_cast<CWindowWnd*>(lpcs->lpCreateParams);        pThis->m_hWnd = hWnd;        //当开始创建窗口将窗口类对象的指针放入到对应的GWLP_USERDATA字段中        ::SetWindowLongPtr(hWnd, GWLP_USERDATA, reinterpret_cast<LPARAM>(pThis));    }     else {    //取出窗口类对象的指针        pThis = reinterpret_cast<CWindowWnd*>(::GetWindowLongPtr(hWnd, GWLP_USERDATA));        if( uMsg == WM_NCDESTROY && pThis != NULL ) {            LRESULT lRes = ::CallWindowProc(pThis->m_OldWndProc, hWnd, uMsg, wParam, lParam);            ::SetWindowLongPtr(pThis->m_hWnd, GWLP_USERDATA, 0L);            if( pThis->m_bSubclassed ) pThis->Unsubclass();            pThis->m_hWnd = NULL;            pThis->OnFinalMessage(hWnd);            return lRes;        }    }    if( pThis != NULL ) {        return pThis->HandleMessage(uMsg, wParam, lParam);    }     else {        return ::DefWindowProc(hWnd, uMsg, wParam, lParam);    }}

上述的代码,在创建窗口时将窗口类对象指针存入到对应的位置便于在其他位置取出并使用。通过return pThis->HandleMessage(uMsg, wParam, lParam);这句话调用的具体对象的HandleMessage,我们在对应的派生类中定义了相应的虚函数,所以根据多态它会调用我们重写的虚函数来处理具体消息,至于我们不关心的消息,它会调用LRESULT lRes = ::CallWindowProc(pThis->m_OldWndProc, hWnd, uMsg, wParam, lParam);或者DefWindowProc,通过对基类的构造函数的查看,我们


原创粉丝点击