关于duilib的介绍就不多讲了,一来不熟,二来小伙伴们想必已经对比了多个界面库,也无需赘述。下面进入正题:

不看广告看疗效! 已有众多知名公司采用duilib做为界面库,如华为网盘、PPS(PPS和华为之前都是用UIPower)、金山快盘(也没用自家的界面库)、酷我音乐、爱奇艺视频、百度杀毒、百度卫士、百度管家等一系列产品。而duilib自己提供的Demo有QQ、QQ旋风、360等等。下面是一部分截图:

    这么好的东东,都开源三年多了,肿么一直没有个像样的文档和入门教程咧? 那些知名公司的界面小伙伴们能马上用么??? 此处省略N字...

    这就是Alberl写入门教程的原因。

    Alberl虽然关注DirectUI快三年了,但是一直处于观望状态,几年前duilib牵头,使很多想寻求商业合作的界面库也纷纷开源或免费,当时Alberl还想等着他们出2.0时再用呢,结果眼一睁一闭,别说2.0了,能勉强活下来的就剩duilib了。duilib的历史简介就这样了。

Hello world

#pragma once
#include <UIlib.h>
using namespace DuiLib;

#ifdef _DEBUG
#   ifdef _UNICODE
#       pragma comment(lib, "DuiLib_ud.lib")
#   else
#       pragma comment(lib, "DuiLib_d.lib")
#   endif
#else
#   ifdef _UNICODE
#       pragma comment(lib, "DuiLib_u.lib")
#   else
#       pragma comment(lib, "DuiLib.lib")
#   endif
#endif

class CDuiFrameWnd : public CWindowWnd, public INotifyUI
{
public:
    virtual LPCTSTR GetWindowClassName() const { return _T("DUIMainFrame"); }
    virtual void    Notify(TNotifyUI& msg) {}

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

        if( uMsg == WM_CREATE ) 
        {
            CControlUI *pWnd = new CButtonUI;
            pWnd->SetText(_T("Hello World"));   // 设置文字
            pWnd->SetBkColor(0xFF00FF00);       // 设置背景色

            m_PaintManager.Init(m_hWnd);
            m_PaintManager.AttachDialog(pWnd);
            return lRes;
        }

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

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

protected:
    CPaintManagerUI m_PaintManager;
};

int APIENTRY _tWinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPTSTR lpCmdLine, int nCmdShow)
{
    CPaintManagerUI::SetInstance(hInstance);

    CDuiFrameWnd duiFrame;
    duiFrame.Create(NULL, _T("DUIWnd"), UI_WNDSTYLE_FRAME, WS_EX_WINDOWEDGE);
    duiFrame.ShowModal();
    return 0;
}

一个Hello World就完成了,是不是很简单呢^_^

    PS:上述代码除了显示了文字以外,还有以下特点:

    1、鼠标移到客户区(绿色部分)时,鼠标样式会变成手型

    2、调整窗口大小以及最大化时,Hello World会自适应窗口的大小,一直都居中

    3、如果想改变背景颜色的话,直接调用SetBkColor就行啦,是不是比win32和MFC方便呢

响应按钮事件

上一个Hello World的教程里有一句代码是这样的:CControlUI *pWnd = new CButtonUI;

    也就是说,其实那整块绿色背景区域都是按钮的区域。(这里简要介绍下,CControlUI 是duilib中所有控件的基类,而CButtonUI则是按钮类,更多的控件会在后面的教程一一介绍。)

    那么怎样响应按钮的点击消息呢?

    我们需要几个步骤:

    1、调用AddNotifier函数将消息加入duilib的消息循环

    2、给按钮设置一个唯一的控件ID(SetName函数)

    3、在Notify函数里处理按钮点击消息。

    代码如下:

class CDuiFrameWnd : public CWindowWnd, public INotifyUI
{
public:
    virtual LPCTSTR GetWindowClassName() const { return _T("DUIMainFrame"); }
    virtual void    Notify(TNotifyUI& msg) 
    {
        if(msg.sType == _T("click"))
        {
            if(msg.pSender->GetName() == _T("btnHello")) 
            {
                ::MessageBox(NULL, _T("我是按钮"), _T("点击了按钮"), NULL);
            }
        }
    }

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

        if( uMsg == WM_CREATE ) 
        {
            CControlUI *pWnd = new CButtonUI;
            pWnd->SetName(_T("btnHello"));      // 设置控件的名称,这个名称用于标识每一个控件,必须唯一,相当于MFC里面的控件ID
            pWnd->SetText(_T("Hello World"));   // 设置文字
            pWnd->SetBkColor(0xFF00FF00);       // 设置背景色

            m_PaintManager.Init(m_hWnd);
            m_PaintManager.AttachDialog(pWnd);
            m_PaintManager.AddNotifier(this);   // 添加控件等消息响应,这样消息就会传达到duilib的消息循环,我们可以在Notify函数里做消息处理
            return lRes;
        }

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

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

protected:
    CPaintManagerUI m_PaintManager;
};

运行结果如图:

自绘标题栏 

如果大家有做过标题栏的自绘,肯定会感慨各种不容易,并且现有的一些资料虽然完美的实现了功能,但是代码比较乱,需要自行整理。如果用duilib,就是小case啦。

    duilib其实并没有区分标题栏和客户区,它的实现方法是屏蔽了系统自带的标题栏,用客户区来模拟标题栏,所以想怎么画就怎么画,非常方便。

    1、我们首先屏蔽一下系统自带的标题栏,

         在HandleMessage函数里屏蔽以下三个消息即可 WM_NCACTIVATE、WM_NCCALCSIZE、WM_NCPAINT

代码如下:    

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

        if( uMsg == WM_CREATE ) 
        {
            CControlUI *pWnd = new CButtonUI;
            pWnd->SetName(_T("btnHello"));      // 设置控件的名称,这个名称用于标识每一个控件,必须唯一,相当于MFC里面的控件ID
            pWnd->SetText(_T("Hello World"));   // 设置文字
            pWnd->SetBkColor(0xFF00FF00);       // 设置背景色

            m_PaintManager.Init(m_hWnd);
            m_PaintManager.AttachDialog(pWnd);
            m_PaintManager.AddNotifier(this);   // 添加控件等消息响应,这样消息就会传达到duilib的消息循环,我们可以在Notify函数里做消息处理
            return lRes;
        }
        // 以下3个消息WM_NCACTIVATE、WM_NCCALCSIZE、WM_NCPAINT用于屏蔽系统标题栏
        else if( uMsg == WM_NCACTIVATE ) 
        {
            if( !::IsIconic(m_hWnd) ) 
            {
                return (wParam == 0) ? TRUE : FALSE;
            }
        }
        else if( uMsg == WM_NCCALCSIZE ) 
        {
            return 0;
        }
        else if( uMsg == WM_NCPAINT ) 
        {
            return 0;
        }

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

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

即可看到如下效果

    2、对于加入最小化最大化按钮等操作会在后面的教程里讲到,因为duilib已经提供了一个基础的类来实现常用的功能,所以这里就略过了。

 XML配置界面

前面那些教程都是为了让小伙伴们从win32、MFC过渡到duilib,让大家觉得duilib不是那么陌生,如果大家现在还对duilib非常陌生的话,那就说明前面的教程做得不好,请大家在下面留言,我会一一查看,并做出改进。

    从这个教程开始就是见证奇迹的时刻啦~\(^o^)/~ 

    其实duilib主打的界面制作方式是XML + UI引擎 + win32框架,其实和浏览器HTML + CSS + 渲染引擎的方式非常类似,可以将其理解为一个非常mini的浏览器。

    而用duilib写界面时,大部分是在写XML,类似于写HTML,这点可能会让习惯MFC等windows界面的伙伴们有点不习惯,需要克服克服,相信在看完Alberl的教程之后,就会习惯啦~

    前面的教程所使用的界面制作方式大家应该不算陌生,那么如果用XML来实现,会是什么样子呢?  

    其实也是很简单的,

    1、把if( uMsg == WM_CREATE ) 里面的代码改成下面这样:

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

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

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

 2、设置XML的路径:

    在CPaintManagerUI::SetInstance(hInstance); 下面调用SetResourcePath函数:

CPaintManagerUI::SetInstance(hInstance);
CPaintManagerUI::SetResourcePath(CPaintManagerUI::GetInstancePath());   // 设置资源的默认路径(此处设置为和exe在同一目录)

 3、建立XML:

    新建一个XML,文件名为“duilib.xml”,保存为UTF-8格式(不要使用windows自带的记事本编辑,可以使用UltraEdit、EditPlus等编辑器),内容如下:

<?xml version="1.0" encoding="UTF-8"?>
<Window size="800,600"> <!-- 窗口的初始尺寸 -->
    <HorizontalLayout bkcolor="#FF00FF00"> <!-- 整个窗口的背景 -->
    </HorizontalLayout>
</Window>

 4、将"duilib.xml"放到exe目录下。

  现在即可看到一个绿色的窗口,是不是也很简单呢。

  那么我们继续来添加Hello World按钮吧~O(∩_∩)O~

  上面的XML只是描述了窗口的大小和背景色,但是并没有添加按钮,下面我们添加一个Hello 按钮,只需在XML里面添加一行即可:

<?xml version="1.0" encoding="UTF-8"?>
<Window size="800,600"> <!-- 窗口的初始尺寸 -->
    <HorizontalLayout bkcolor="#FF00FF00"> <!-- 整个窗口的背景 -->
        <Button name="btnHello" text="Hello World"/> <!-- 按钮的属性,如名称、文本 -->
    </HorizontalLayout>
</Window>

再次运行一下exe,是不是又看到了熟悉的画面,怎么样,写XML很简单吧~O(∩_∩)O~   :

  不过大家可以发现,改成XML后,窗口启动后不居中了,怎么办呢?

在duiFrame.ShowModal上面那行加上duiFrame.CenterWindow();就可以啦

main.cpp的完整代码如下:

 

#pragma once
#include <UIlib.h>
using namespace DuiLib;

#ifdef _DEBUG
#   ifdef _UNICODE
#       pragma comment(lib, "DuiLib_ud.lib")
#   else
#       pragma comment(lib, "DuiLib_d.lib")
#   endif
#else
#   ifdef _UNICODE
#       pragma comment(lib, "DuiLib_u.lib")
#   else
#       pragma comment(lib, "DuiLib.lib")
#   endif
#endif

class CDuiFrameWnd : public CWindowWnd, public INotifyUI
{
public:
    virtual LPCTSTR GetWindowClassName() const { return _T("DUIMainFrame"); }
    virtual void    Notify(TNotifyUI& msg) 
    {
        if(msg.sType == _T("click"))
        {
            if(msg.pSender->GetName() == _T("btnHello")) 
            {
                ::MessageBox(NULL, _T("我是按钮"), _T("点击了按钮"), NULL);
            }
        }
    }

    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("duilib.xml"), (UINT)0, NULL, &m_PaintManager);   // duilib.xml需要放到exe目录下
            ASSERT(pRoot && "Failed to parse XML");

            m_PaintManager.AttachDialog(pRoot);
            m_PaintManager.AddNotifier(this);   // 添加控件等消息响应,这样消息就会传达到duilib的消息循环,我们可以在Notify函数里做消息处理
            return lRes;
        }
        // 以下3个消息WM_NCACTIVATE、WM_NCCALCSIZE、WM_NCPAINT用于屏蔽系统标题栏
        else if( uMsg == WM_NCACTIVATE ) 
        {
            if( !::IsIconic(m_hWnd) ) 
            {
                return (wParam == 0) ? TRUE : FALSE;
            }
        }
        else if( uMsg == WM_NCCALCSIZE ) 
        {
            return 0;
        }
        else if( uMsg == WM_NCPAINT ) 
        {
            return 0;
        }

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

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

protected:
    CPaintManagerUI m_PaintManager;
};

int APIENTRY _tWinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPTSTR lpCmdLine, int nCmdShow)
{
    CPaintManagerUI::SetInstance(hInstance);
    CPaintManagerUI::SetResourcePath(CPaintManagerUI::GetInstancePath());   // 设置资源的默认路径(此处设置为和exe在同一目录)

    CDuiFrameWnd duiFrame;
    duiFrame.Create(NULL, _T("DUIWnd"), UI_WNDSTYLE_FRAME, WS_EX_WINDOWEDGE);
    duiFrame.CenterWindow();
    duiFrame.ShowModal();
    return 0;
}

XML基础类

现在大家应该对XML描述界面不那么陌生了,那么我们做进一步介绍。

  前面的教程我们写了很多代码,为的是让大家了解下基本流程,其实duilib已经对常用的操作做了很好的包装,正式使用时无需像前面的教程那样写那么多代码,下面我们就来看看XML的包装类WindowImplBase:

  WindowImplBase类是一个duilib的基础框架类,封装了常用操作,以方便大家使用。 它是以XML作为界面描述的,所以用它的时候,我们必须将界面描述写到XML里。

  下面将是我们第三次实现Hello World程序~O(∩_∩)O~

class CDuiFrameWnd : public WindowImplBase
{
public:
    virtual LPCTSTR    GetWindowClassName() const   {   return _T("DUIMainFrame");  }
    virtual CDuiString GetSkinFile()                {   return _T("duilib.xml");  }
    virtual CDuiString GetSkinFolder()              {   return _T("");  }
};

int APIENTRY _tWinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPTSTR lpCmdLine, int nCmdShow)
{
    CPaintManagerUI::SetInstance(hInstance);

    CDuiFrameWnd duiFrame;
    duiFrame.Create(NULL, _T("DUIWnd"), UI_WNDSTYLE_FRAME, WS_EX_WINDOWEDGE);
    duiFrame.CenterWindow();
    duiFrame.ShowModal();
    return 0;
}

关键代码才10行左右,是不是更简单了呢?  后面的教程将会进一步介绍WindowImplBase的使用及功能~

  注意:由于_tWinMain函数也基本不会变动,所以如果没有改动,后面的教程也不再列出这些代码,因为后面的教程基本上只需要改动CDuiFrameWnd 类以及XML文件。

完整的自绘标题栏

 看了前面那么多教程,相信对duilib已有基本映像了,我们就快马加鞭,做出一个完整的自绘标题栏吧~

    看到下面这个效果图,小伙伴们是不是有点惊呆了呢~O(∩_∩)O~

    duilib实现以上效果非常简单,

  1、将按钮图片的文件夹放到exe目录,点此下载

  2、main.cpp的代码无需改动,只需改动XML,XML内容如下(受网页宽度限制,以下XML格式有点乱,请复制到本地的XML编辑器上,以方便查看): 

<?xml version="1.0" encoding="UTF-8"?>
<Window size="800,600" mininfo="600,400" caption="0,0,0,32" sizebox="4,4,4,4"> <!-- 窗口的初始尺寸(宽800,高600)、窗口的最小尺寸(宽600,高400)、标题栏拖拽区域(高32)、可拖拽边框大小(这里添加sizebox后就可以拖拽边框调整大小了) -->
    <VerticalLayout bkcolor="#FFF0F0F0" bkcolor2="#FFAAAAA0"> <!-- 整个窗口的背景色 -->
        <!-- 标题栏区 -->
        <HorizontalLayout height="32" bkcolor="#FFE6E6DC" bkcolor2="#FFAAAAA0"> <!-- 标题栏背景色 bkcolor、bkcolor2、bkcolor3分别是渐变色的三个值-->         
            <VerticalLayout /> <!-- 占空位,占据左边所有的空位-->
            <VerticalLayout width="77"> <!-- 右边三个控件所占的宽度-->
                <Button name="minbtn"   tooltip="最小化" float="true" pos="0,5,22,24"  width="23" normalimage=" file='SysBtn\MinNormal.bmp' " hotimage=" file='SysBtn\MinFocus.bmp' " pushedimage=" file='SysBtn\MinFocus.bmp' "/>
                <Button name="maxbtn"   tooltip="最大化" float="true" pos="22,5,44,24" width="23" normalimage=" file='SysBtn\MaxNormal.bmp' " hotimage=" file='SysBtn\MaxFocus.bmp' " pushedimage=" file='SysBtn\MaxFocus.bmp' " />
                <Button name="restorebtn" visible="false" tooltip="还原" float="true" pos="22,5,44,24" width="23" normalimage=" file='SysBtn\StoreNormal.bmp' " hotimage=" file='SysBtn\StoreFocus.bmp' " pushedimage=" file='SysBtn\StoreFocus.bmp' " />
                <Button name="closebtn" tooltip="关闭"   float="true" pos="44,5,74,24" width="28" normalimage=" file='SysBtn\CloseNormal.bmp' " hotimage=" file='SysBtn\CloseFocus.bmp' " pushedimage=" file='SysBtn\CloseFocus.bmp' "/>
            </VerticalLayout>      
        </HorizontalLayout>
        
        <!-- 客户区 -->
        <HorizontalLayout>
        <Button name="btnHello" text="Hello World"/>
    </HorizontalLayout>
    </VerticalLayout>
</Window>

仅仅在XML里面加了20行代码,一个完整的标题栏居然就完成了!

    试试标题栏可以拖动不,试试可以最大化不,试试有提示条不,试试按钮外观会变化不,试试可以拖拽边框调整窗口大小不?

    必须都可以!

    虽然我们并没有处理以上消息,但是很多功能都已实现,因为WindowImplBase帮我们都做好了。

    小伙伴们,继承了WindowImplBase之后,妈妈再也不用担心我的界面啦~O(∩_∩)O哈哈~

    虽然XML里面已经有很详细的注释了,鉴于以下问题在群里多次出现,所以还是重复提醒一遍:

    以下问题都在XML第二行的 <Window>节点里添加。

1、为什么标题栏不能拖动?

    需要在<Window>节点添加 caption="0,0,0,32",这里的32是指标题栏的高度

2、怎么让窗口大小可以通过拖拽来调整?

    需要在<Window>节点添加 sizebox="4,4,4,4", 这里的4是指鼠标移动到边框4个像素的范围内,鼠标就会显示拖拽样式,这时就可以调整窗口大小了

    下面Alberl来一一解释下XML代码的意思。大部分地方都有明显注释了,重点要解释的就是HorizontalLayout、VerticalLayout以及Button的样式了。

    这一节先介绍Button的样式,大家可以看到关闭按钮除了鼠标移上去有不同外观,还有一个提示条。这些都是在这一行代码里指定的:

<Button name="closebtn" tooltip="关闭"   float="true" pos="44,5,74,24" width="28" normalimage=" file='SysBtn\CloseNormal.bmp' " hotimage=" file='SysBtn\CloseFocus.bmp' " pushedimage=" file='SysBtn\CloseFocus.bmp' "/>

其中:

name="closebtn"    唯一标识按钮,其他按钮的name不能与其重复

tooltip="关闭"         就是那个提示条的文字

float="true"            代表按钮的位置是绝对定位,其位置由pos属性指定

pos="44,5,74,24"    代表按钮的位置矩阵,分别为矩阵左、上、右、下四个点

width="28"             代表按钮图片显示的宽度(这个可以不填,但是由于按钮图片没有做好,如果不填的话,图片会被拉伸有点失真)

normalimage           代表正常状态下按钮显示的图片路径

hotimage                代表鼠标移上去时,按钮显示的图片路径     

pushedimage          代表鼠标点击按钮时,按钮显示的图片路径     

        

下一节将会介绍HorizontalLayout、VerticalLayout。

界面布局

上一个教程实现的标题栏代码中,并没有看到处理自适应窗口大小的代码,但是窗口大小变化后,按钮的位置会跟着变化,这是因为我们将按钮放到了HorizontalLayout、VerticalLayout,这样duilib就会帮我们自动布局按钮的位置和大小,顾名思义,HorizontalLayout就是水平布局,VerticalLayout就是垂直布局。

    最开始的教程里面,窗口大小变化时,Hello World按钮会沾满整个窗口,并且文字始终居中,这就是HorizontalLayout的效果:

<HorizontalLayout>
    <Button name="btnHello" text="Hello World"/>
</HorizontalLayout>

 那么HorizontalLayout和VerticalLayout有什么区别呢? 其实这个Alberl也不怎么懂,Alberl都是直接试效果的,HorizontalLayout不行就换VerticalLayout ~O(∩_∩)O  这个还得请各位大神多多赐教,等写完这个入门教程后,Alberl会继续学习duilib,到时候再继续写教程。

    由于官方木有文档,所有的东西都靠自己去看,去Demo里调试,所以这个布局的准确文字定义我也不好说,就直接用实验的方式跟大家讲解啦~

    现在开始讲解上一个教程中的界面布局:

    1、首先得到一个渐变的背景窗口,将XML的内容改成下面这样

<?xml version="1.0" encoding="UTF-8"?>
<Window size="800,600" mininfo="600,400" caption="0,0,0,32" sizebox="4,4,4,4"> 
    <VerticalLayout bkcolor="#FFF0F0F0" bkcolor2="#FFAAAAA0">
    </VerticalLayout>
</Window>

效果如图:

    从duilib的源码里可以看到:

    (1)XML的 <VerticalLayout> 节点对应于duilib里的CVerticalLayoutUI控件

    (2)CVerticalLayoutUI控件继承于CContainerUI,而CContainerUI继承于CControlUI。

    所以其实CVerticalLayoutUI 也是一个控件啦,把它和CButtonUI同等对待,就比较好理解了。

为了进一步表现他们的类似,可以将XML里面的VerticalLayout 换成 Button、Control、Container 试试,你会发现效果是一样的哦~

    再次强调下win32/MFC的界面 和duilib 界面的区别:

    (1)MFC中将按钮、菜单、标题栏等等都当作不同的东西(例如标题栏只能放在最上面,按钮不能直接拖到标题栏等等);

     duilib中将所有的东西都同等对待,所以处理起来非常方便(别说把按钮放到标题栏上,就算把标题栏放在按钮上都没问题)。

    (2)MFC中所有的窗口和控件都是继承于CWnd(win32继承于HWND)。

     duilib中所有的控件都继承于CControlUI,所有的窗口都继承于CWindowWnd(内部包装了HWND)。

     所以如果用MFC做一个界面,那么上面就是很多个CWnd,用Spy++可以看到不同的窗口句柄。

     如果用duilib做一个界面,那么上面就是很多个CControlUI,但是用Spy++只能看到一个窗口句柄。

     这是因为duilib整个窗口只有一个HWND,其他的CControlUI虽然是控件,但是其实都是自己绘制出来的,并不是真正的HWND,所以你可以把这些控件理解为自绘,整个duilib的界面绘制,就是在自绘一个HWND。

    2、加上标题栏,将XML的内容改成下面这样: 

<?xml version="1.0" encoding="UTF-8"?>
<Window size="800,600" mininfo="600,400" caption="0,0,0,32" sizebox="4,4,4,4"> 
    <VerticalLayout  bkcolor="#FFF0F0F0" bkcolor2="#FFAAAAA0">
        <!-- 标题栏区 -->
        <HorizontalLayout height="32" bkcolor="#FFE6E6DC" bkcolor2="#FFAAAAA0">  
        </HorizontalLayout>
    </VerticalLayout >
</Window>

效果如图:

其中height="32" 是指这个HorizontalLayout 只占用32个像素高度。

    3、将标题栏移到下面,将XML的内容改成下面这样: 

<?xml version="1.0" encoding="UTF-8"?>
<Window size="800,600" mininfo="600,400" caption="0,0,0,32" sizebox="4,4,4,4"> 
    <VerticalLayout  bkcolor="#FFF0F0F0" bkcolor2="#FFAAAAA0">
        <HorizontalLayout /> <!-- 占空位,占据上面所有的空位-->
 
        <!-- 标题栏区 -->
        <HorizontalLayout height="32" bkcolor="#FFE6E6DC" bkcolor2="#FFAAAAA0">  
        </HorizontalLayout>
    </VerticalLayout >
</Window>

效果如图:

这里只加了一行代码<HorizontalLayout />,就让标题栏移到了下面,这行代码的意思是:占据空白的部分。

由于这行代码放在标题栏HorizontalLayout 的上面,所以标题栏被挤了下去,如果放到下面,是没有效果的。

4、将标题栏移到中间,将XML的内容改成下面这样:

<?xml version="1.0" encoding="UTF-8"?>
<Window size="800,600" mininfo="600,400" caption="0,0,0,32" sizebox="4,4,4,4"> 
    <VerticalLayout  bkcolor="#FFF0F0F0" bkcolor2="#FFAAAAA0">
        <HorizontalLayout /> <!-- 占空位,占据上面所有的空位-->
 
        <!-- 标题栏区 -->
        <HorizontalLayout height="32" bkcolor="#FFE6E6DC" bkcolor2="#FFAAAAA0">  
        </HorizontalLayout>
 
        <HorizontalLayout /> <!-- 占空位,占据下面所有的空位-->
    </VerticalLayout >
</Window>

 

效果如图:

    为什么标题栏就跑到中间了呢? 

    在标题栏的上面和下面都有一个占空位的<HorizontalLayout />,如果没有指定高度,那么他们会默认各占一半高度,相当于有一个默认的属性 height="***"

    那么我们给它指定一个高度试试,比如把上面那个<HorizontalLayout /> 改为<HorizontalLayout height="32" />,效果如图:

    现在应该明白<HorizontalLayout />占位的作用了吧~\(^o^)/~

    这里有一点要注意的就是:

占位的时候,

<HorizontalLayout /> 一般是指定height属性,也就是说占的位置是从上往下算的。因为水平方向的位置都会占据。

<VerticalLayout/>       一般是指定width属性,也就是说占的位置是从左往右算的。因为垂直方向的位置都会占据。

并且HorizontalLayout 和VerticalLayout一般都是交叉包含,而不是重复包含(比如<HorizontalLayout> 子节点里再包含一个<HorizontalLayout> 节点)。

    当然,上面指的是一般情况,如果对布局很熟悉了,就可以随意包含了。

    这里讲的都是把标题栏水平放置,把标题栏垂直放置相信也难不倒大家了,就请自行试验,以便熟悉这些布局。

    5、回到前面第2个步骤,我们来添加客户区布局。

    因为背景色已经是渐变的了,所以就不给客户区加背景了,那么客户区暂且不管。

    6、添加最大化、最小化、关闭按钮:

    上一个教程的最后部分已经解释了按钮的外观等属性,这里为了简明,就不加那么多属性了,先加上一个最小化按钮试试效果:

<?xml version="1.0" encoding="UTF-8"?>
<Window size="800,600" mininfo="600,400" caption="0,0,0,32" sizebox="4,4,4,4"> 
    <VerticalLayout  bkcolor="#FFF0F0F0" bkcolor2="#FFAAAAA0">
        <!-- 标题栏区 -->
        <HorizontalLayout height="32" bkcolor="#FFE6E6DC" bkcolor2="#FFAAAAA0">  
            <Button name="minbtn" normalimage=" file='SysBtn\MinNormal.bmp' " hotimage=" file='SysBtn\MinFocus.bmp' "/>
        </HorizontalLayout>
    </VerticalLayout >
</Window>

效果如图:

可以看到整个最小化按钮都被拉伸了,其中两边的矩形色块是因为图片的边框也被拉伸了。

我们再加上最大化按钮和关闭按钮,XML如下:

<?xml version="1.0" encoding="UTF-8"?>
<Window size="800,600" mininfo="600,400" caption="0,0,0,32" sizebox="4,4,4,4"> 
    <VerticalLayout  bkcolor="#FFF0F0F0" bkcolor2="#FFAAAAA0">
        <!-- 标题栏区 -->
        <HorizontalLayout height="32" bkcolor="#FFE6E6DC" bkcolor2="#FFAAAAA0">  
            <Button name="minbtn" normalimage=" file='SysBtn\MinNormal.bmp' " hotimage=" file='SysBtn\MinFocus.bmp' "/>
            <Button name="maxbtn" normalimage=" file='SysBtn\MaxNormal.bmp' " hotimage=" file='SysBtn\MaxFocus.bmp' "/>
            <Button name="closebtn" normalimage=" file='SysBtn\closeNormal.bmp' " hotimage=" file='SysBtn\closeFocus.bmp' "/>
        </HorizontalLayout>
    </VerticalLayout >
</Window>

 

效果如图:

    可以发现三个按钮被均匀拉伸了。

    但是我们显然不想让按钮被拉伸,怎么办呢?

    还记得前面说的占位布局吗?因为我们想让按钮显示到右边,所以我们要占住左边的部分。 那我们加一个占位试试。

不过,

    (1)亲们知道这个占位要加到哪一行么?

        当然是加到按钮的上面! 那我们加到按钮上面试试。

    (2)亲们知道这个占位布局用<HorizontalLayout/>还是<VerticalLayout/>么?

        其实我也不知道,那咱们就试试吧。

        先加个<HorizontalLayout/>玩玩,XML如下:

 

<?xml version="1.0" encoding="UTF-8"?>
<Window size="800,600" mininfo="600,400" caption="0,0,0,32" sizebox="4,4,4,4"> 
    <VerticalLayout  bkcolor="#FFF0F0F0" bkcolor2="#FFAAAAA0">       
        <!-- 标题栏区 -->
        <HorizontalLayout height="32" bkcolor="#FFE6E6DC" bkcolor2="#FFAAAAA0">  
            <HorizontalLayout />
            <Button name="minbtn" normalimage=" file='SysBtn\MinNormal.bmp' " hotimage=" file='SysBtn\MinFocus.bmp' "/>
            <Button name="maxbtn" normalimage=" file='SysBtn\MaxNormal.bmp' " hotimage=" file='SysBtn\MaxFocus.bmp' "/>
            <Button name="closebtn" normalimage=" file='SysBtn\closeNormal.bmp' " hotimage=" file='SysBtn\closeFocus.bmp' "/>
        </HorizontalLayout>
    </VerticalLayout >
</Window>

效果如图:

    (其实换成VerticalLayout等任何CControlUI,效果都是一样的)

    果然占到了左边的部分,但是为什么还是均分呢,怎么让它只占用左边的一大部分呢?

    这就是width的用处了, 我们给HorizontalLayout 加上一个属性 width = "600",可以看到如下效果:

    好家伙,果然有用!

    下一步我们再把width加大一点,应该就可以让按钮正常了吧?

    先别急着管大小,我们先拖动一下窗口大小,或者点击最大化按钮~

    是不是有新问题啦? 为什么按钮还是被放大了呢?

    因为我们给HorizontalLayout加上了width属性,那么在水平方向,它就失去自适应效果啦,因为宽度永远是前面指定的600.

    那怎么样才能让按钮不被放大呢?

    很显然,不应该给左边的占位布局HorizontalLayout 指定width属性,而应该给右边的按钮指定width属性。

    但是按钮有 width属性吗?

    按钮是有,但是我们应该添加一个占位布局 HorizontalLayout  来指定width属性。 XML如下:

<?xml version="1.0" encoding="UTF-8"?>
<Window size="800,600" mininfo="600,400" caption="0,0,0,32" sizebox="4,4,4,4"> 
    <VerticalLayout  bkcolor="#FFF0F0F0" bkcolor2="#FFAAAAA0">       
        <!-- 标题栏区 -->
        <HorizontalLayout height="32" bkcolor="#FFE6E6DC" bkcolor2="#FFAAAAA0">  
            <HorizontalLayout />
            <HorizontalLayout width = "77">
                <Button name="minbtn" normalimage=" file='SysBtn\MinNormal.bmp' " hotimage=" file='SysBtn\MinFocus.bmp' "/>
                <Button name="maxbtn" normalimage=" file='SysBtn\MaxNormal.bmp' " hotimage=" file='SysBtn\MaxFocus.bmp' "/>
                <Button name="closebtn" normalimage=" file='SysBtn\closeNormal.bmp' " hotimage=" file='SysBtn\closeFocus.bmp' "/>
            </HorizontalLayout>
        </HorizontalLayout>
    </VerticalLayout >
</Window>

效果如图:

    现在窗口大小怎么变,按钮都不会被拉伸啦,恭喜小伙伴们~

    可以看到那3个Button节点都被放到了HorizontalLayout 节点下,下面是时候讲一下布局了:

    其实duilib整个界面都是由各种布局组成,我们把上面的XML简化一下,就是下面这样子:

<Window size="800,600" mininfo="600,400" caption="0,0,0,32" sizebox="4,4,4,4"> 
    <VerticalLayout >       
        <HorizontalLayout height="32" >  
            <HorizontalLayout />
            <HorizontalLayout width = "77" />
        </HorizontalLayout>
    </VerticalLayout >
</Window>

 

很明显,最外层是整个窗口的布局<VerticalLayout>,之后又包含了一个 <HorizontalLayout height="32" >布局(即标题栏),

再里面又包含了两个HorizontalLayout布局,所以一切框架和位置都是由布局决定。

    这里先简要介绍一下duilib的UI设计器:

    1、在duilib源码的bin目录下,【DuiDesigner.exe】就是UI设计器啦。

    2、把XML拖拽上去即可直接看到界面效果,

    我们把前面那个完整的XML拖进去,即可看到如下效果:

    红色边框围起来的就是一个个HorizontalLayout 等布局啦~

    7、调整按钮的位置和大小。

    虽然已经将按钮显示到最后边,但是按钮沾满了右边部分,我们现在把他们的高度调小一点:

    给Button节点都加上属性 height ="20",效果如图:

    嗯,效果看起来还行。

    再加上width="23"属性,效果如图:

    那怎么样让按钮不挨着顶部呢?

    前面介绍过float属性,是用于绝对定位,由于现在按钮的位置都是由布局自动调整的,所以没办法调节位置,如果不想让布局自动调整位置,就要加上float="true",这样就可以自己指定位置啦。我们给最小化按钮加上 float="true" pos="0,5,22,24" 试试,效果如图:

    额,最小化按钮哪里去了~~~

    虽然最小化按钮使用了绝对定位,但是其他两个按钮还是自动布局,所以需要给他们也加上,XML如下:

<HorizontalLayout width = "77">
    <Button name="minbtn"   height ="20" width="23" float="true" pos="0,5,22,24"  normalimage=" file='SysBtn\MinNormal.bmp' " hotimage=" file='SysBtn\MinFocus.bmp' "/>
    <Button name="maxbtn"   height ="20" width="23" float="true" pos="22,5,44,24" normalimage=" file='SysBtn\MaxNormal.bmp' " hotimage=" file='SysBtn\MaxFocus.bmp' "/>
    <Button name="closebtn" height ="20" width="23" float="true" pos="44,5,74,24" normalimage=" file='SysBtn\closeNormal.bmp' " hotimage=" file='SysBtn\closeFocus.bmp' "/>
</HorizontalLayout>

效果如图:

    对比一下上个教程完整的图片:

    发现我们这个这个图有点不对,三个按钮的中间的线有点粗~

    这是因为图片画的不对(这些图片是Alberl早几年画的,用于MFC,所以并没有考虑duilib),可以把最大化按钮两边的边框都去掉,这样就OK啦~

    不过呢,没必要那么做,我们恰好可以再学习一点新知识~

    我们可以看到,虽然给关闭按钮指定的宽度也是23,但是它却比其他按钮大。  这说明width属性已经失效了,这是因为我们在pos属性里面已经指定了位置大小,它会优先以pos属性为准,那么显然height属性也失效了。(感谢网友【糖加三勺】的提醒,Alberl的这个说法有误,重新试了一下,如果pos属性放在后面,就会以pos为准,height属性放在后面就会以height为准,并不是属性失效。)

    回到刚刚那个按钮图片的问题,由于每个按钮图片都画了边框,所以会有加在一起就有两个边框了,所以中间的线有点粗,那么我们现在可以将最大化、关闭按钮往左边移动一个像素。

效果如图:

    嘿嘿,好了吧~

    但是这个关闭按钮貌似有点宽~

    我们当然可以再次调整pos属性,但是这不科学~~

    还记得失效的那两个属性么?

    那是因为pos属性的后面两个值都填了非0值,所以导致那两个属性失效,下面我们把pos后面的两个值改为0试试。

可以发现那两个属性又生效啦~(感谢网友【糖加三勺】的提醒,Alberl的这个说法有误,重新试了一下,如果pos属性放在后面,就会以pos为准,height属性放在后面就会以height为准,并不是属性失效。)

    现在只要去掉width、height属性中的一个,按钮都会不见了,Alberl以为按钮的大小会调整成图片的大小呢~~~

看来只能指定宽度和高度了(为了方便后期的位置调整,建议pos属性后面两个值填0,使用width/height属性来指定按钮大小)

这几个图片的宽高分别是19 * 23、19*28,所以就手动指定了,XML如下:

<?xml version="1.0" encoding="UTF-8"?>
<Window size="800,600" mininfo="600,400" caption="0,0,0,32" sizebox="4,4,4,4"> 
    <VerticalLayout  bkcolor="#FFF0F0F0" bkcolor2="#FFAAAAA0">       
        <!-- 标题栏区 -->
        <HorizontalLayout height="32" bkcolor="#FFE6E6DC" bkcolor2="#FFAAAAA0">  
            <HorizontalLayout />
            <HorizontalLayout width = "77">
                <Button name="minbtn"   float="true" pos="0,5,0,0"  height="19" width="23" normalimage=" file='SysBtn\MinNormal.bmp' " hotimage=" file='SysBtn\MinFocus.bmp' "/>
                <Button name="maxbtn"   float="true" pos="22,5,0,0" height="19" width="23" normalimage=" file='SysBtn\MaxNormal.bmp' " hotimage=" file='SysBtn\MaxFocus.bmp' "/>
                <Button name="closebtn" float="true" pos="44,5,0,0" height="19" width="28" normalimage=" file='SysBtn\closeNormal.bmp' " hotimage=" file='SysBtn\closeFocus.bmp' "/>
            </HorizontalLayout>
        </HorizontalLayout>
    </VerticalLayout >
</Window>

效果如图:

    (上面的XML里,Button的父节点HorizontalLayout 换成 VerticalLayout也是一样的效果,所以有时候他们是没有什么分别的,不过个人的理解应该是要交叉使用的)

    好啦,最关键的布局部分讲完啦,另外,在duilib的官方群里,有共享一个布局案例,Alberl已整理出来【duilib入门和xml培训 布局案例.rar】,那里有9个布局案例,相信看完本教程的讲解之后,再看看那9个XML布局,应该就很熟悉布局啦,如果还有不懂的话,请留言~

界面设计器DuiDesigner

 

上一个教程讲解了怎么布局最大化、最小化、关闭按钮,但是如果手动去计算这三个按钮的位置和大小的话,非常的不直观,也很不方便。

    所以这一章准备介绍duilib的UI设计器,由于这个设计器很不完善,也有很多bug,有时候会导致XML数据丢失,所以很多大神都不建议用,不过我每次写代码都会用SVN,而且会原子提交,所以即使丢失也可以恢复。不过这不代表我赞同一直使用这个UI设计器,我建议大家布局的时候用设计器,布局完毕之后手写XML。这样按钮的大小和位置都很直观,很方便的可以确定,就用不着手动去计算了,当然,如果是美工给的界面,就没必要用设计器了,因为他们会把位置和大小都给过来。

    1、打开设计器:

    在duilib源码的bin目录里,【DuiDesigner.exe】就是UI设计器(这个设计器需要重新编译一次,不然打开XML会崩溃)。

    

    2、新建项目:

    菜单里选择【文件】--【新建】--【文件】,再点击保存将文件保存到指定路径。

    3、新建布局:

    由于前面的教程已经详细讲过布局和标题栏,所以这里直接弄出一个标题栏布局。

    (1)加上一个VerticalLayout 布局,做为整个窗口的布局(由于设计器不能拖拽控件,所以需要先点击工具箱里的VerticalLayout 控件,再点击界面,即可加上控件),如图:

    

    (2)加上一个HorizontalLayout 布局,做为标题栏的布局:

    发现HorizontalLayout 布局并没有沾满整个窗口的宽度,所以需要调整,

在属性里面,将【Size】展开,将【Width】填为0,即可自动拉伸宽度。(注意,需要先点击一个控件,才能设置属性,不要点错了控件哦~)

  

    (3)加上两个VerticalLayout 布局,做为标题栏的左边的占位布局和右边的按钮布局(前面已经介绍过,VerticalLayout 和 HorizontalLayout 有时候可以互换,但是用交叉的方式一般都不会错,交叉方式即VerticalLayout 子节点和父节点都是HorizontalLayout ,而兄弟节点是VerticalLayout ):

右边的布局width改为77

    注意,别把布局点错了位置,此时左边的树形应该如下图:

    (4)布局好了之后,我们往上面加按钮:

先点击Button控件,再点击右上角的按钮布局,可以看到下图:

这时可以拖拽边框调节控件大小,也可以拖动控件的位置。

此时我们可以按Ctrl+C、Ctrl+V复制按钮

然后把按钮拖到相应位置

同理,加上第三个按钮,如下图:

这个时候,我们可以将三个按钮顶端对齐:

先选中三个按钮

,然后点击【顶端对齐】按钮。

接着点击【横向】按钮

,使三个按钮水平方向均匀间隔开来。

方法已经举例说明了,具体的位置和间隔还需要小伙伴们慢慢调整。

可以看到设计器调整控件的位置和大小还是很方便的~

    注意,要记住时不时的按下Ctrl + S,不然设计器崩溃了,就不好了~

下面我们来看看XML的内容,在Tab上面右键,选择【打开所在的文件夹】,

然后打开XML(发现只是打开文件夹,木有定位到文件o(╯□╰)o),此时的XML文件的内容如下:

    (5)其实到这里,就可以抛弃UI设计器啦~~~

    但是好歹是大神花了心血搞出来的,其实多用用SVN,记得随时Ctrl+S,还是可以放心的用的~

下面就接着介绍一些属性吧,

在duilib源码的目录下,有一个文件【属性列表.xml】,这里介绍了所有控件的属性,虽然有一点点遗漏,但是已经够啦。

所以详细属性就请看【属性列表.xml】,这里只介绍一些常用的属性。

我们先给窗口背景换成绿色,

选择整个窗口的布局后,设置【BkColor】属性即可

 

同理,设置标题栏布局的背景色,由于标题栏布局已经被两个子布局遮住,所以这时需要点击左侧的树形控件

然后给按钮也加上背景色,

    现在我们把标题栏换成渐变色,

    除了设置【BkColor】外,再设置【BkColor2】即可,

    把窗口背景换成三色渐变,再设置【BkColor3】即可,不好意思,设计器里面木有这个属性,需要手动在XML里添加~~~

    一切就绪以后,我们可以点击【测试窗体】按钮看看窗口效果

    按下【Esc】键即可关闭【测试窗体】

转自:2013 duilib入门简明教程 -- 前言(1) - Alberl - 博客园

Logo

旨在为数千万中国开发者提供一个无缝且高效的云端环境,以支持学习、使用和贡献开源项目。

更多推荐