代码仓库:https://github.com/yangpan4485/duilib/tree/develop/MyDemo
一、基本控件介绍
duilib 里面没有提供默认的控件样式,所以我们就要使用自己的控件样式了
1、Button,Label,Edit,这三个控件应该是我们平时使用最多的控件了,现在我们使用这 3 个控件做一个简单的登录界面,这里我们使用系统自带的标题栏按钮,所以在这里就不使用 WindowImplBase 了,还是使用最原始的方法
login.xml 内容如下
<?xml version="1.0" encoding="UTF-8"?>
<Window size="960,540" mininfo="600,400" caption="0,0,0,32" sizebox="4,4,4,4">
<Font id="0" name="宋体" size="18" bold="false" underline="false" italic="false" />
<VerticalLayout bkcolor="#FFDFFDF0" width="300" height="318"> <!-- 整个窗口使用 VerticalLayout 布局 -->
<VerticalLayout>
<Label name= "userIdLabel" text="用户名:" float="true" pos="300,80,370,120" normaltextcolor="white" font="0"/>
<Label name= "passwordLabel" text="密 码:" float="true" pos="300,140,370,180" normaltextcolor="white" font="0"/>
<Edit name="userIdEdit" text="" float="true" pos="370,85,0,0" width="250" height="30" bkcolor="#FFFFFFFF" textpadding="4,3,4,3" textcolor="#FF000000" disabledtextcolor="#FFA7A6AA" font="0"/>
<Edit name="passwordEdit" text="" float="true" pos="370,146,0,0" width="250" height="30" bkcolor="#FFFFFFFF" textpadding="4,3,4,3" textcolor="#FF000000" disabledtextcolor="#FFA7A6AA" font="0"/>
<Button name="btnLogin" text="登录" float="true" pos="300,220,400,250" normalimage=" file='common/button_normal.bmp' " hotimage=" file='common/button_over.bmp' " pushedimage=" file='common/button_down.bmp' " font="0"/>
<Button name="btnQuit" text="退出" float="true" pos="500,220,600,250" normalimage=" file='common/button_normal.bmp' " hotimage=" file='common/button_over.bmp' " pushedimage=" file='common/button_down.bmp' " font="0"/>
</VerticalLayout>
</VerticalLayout>
</Window>
login.h
#pragma once
#include "UIlib.h"
class Login : public DuiLib::CWindowWnd, public DuiLib::INotifyUI
{
public:
Login();
~Login();
void Init();
bool CreateDUIWindow();
void ShowWindow();
LPCTSTR GetWindowClassName() const override;
void Notify(DuiLib::TNotifyUI& msg) override;
LRESULT HandleMessage(UINT uMsg, WPARAM wParam, LPARAM lParam) override;
LRESULT OnCreate(UINT uMsg, WPARAM wParam, LPARAM lParam);
LRESULT OnClose(UINT uMsg, WPARAM wParam, LPARAM lParam);
void OnClick(DuiLib::TNotifyUI& msg);
private:
HINSTANCE _hInstance;
DuiLib::CPaintManagerUI _paintManager{};
HWND _ownerWnd{};
};
login.cpp
#include "login.h"
#include <iostream>
#include <string>
Login::Login()
{
}
Login::~Login()
{
}
void Login::Init()
{
SetProcessDPIAware();
_hInstance = GetModuleHandle(0);
DuiLib::CPaintManagerUI::SetInstance(_hInstance);
DuiLib::CPaintManagerUI::SetResourcePath(DuiLib::CPaintManagerUI::GetInstancePath() + +_T("resources"));
}
bool Login::CreateDUIWindow()
{
_ownerWnd = Create(NULL, _T("Login"), UI_WNDSTYLE_FRAME, WS_EX_WINDOWEDGE);
if (!_ownerWnd)
{
std::cout << "create dui window failed" << std::endl;
return false;
}
return true;
}
void Login::ShowWindow()
{
ShowModal();
}
LPCTSTR Login::GetWindowClassName() const
{
return _T("DUILOGINFrame");
}
void Login::Notify(DuiLib::TNotifyUI& msg)
{
if (msg.sType == _T("click"))
{
OnClick(msg);
}
}
void Login::OnClick(DuiLib::TNotifyUI& msg)
{
if (msg.pSender->GetName() == _T("btnLogin"))
{
auto editUserId = (DuiLib::CEditUI*)_paintManager.FindControl(_T("userIdEdit"));
auto editPassword = (DuiLib::CEditUI*)_paintManager.FindControl(_T("passwordEdit"));
std::string userId = editUserId->GetText();
std::string password = editPassword->GetText();
std::cout << "userId:" << userId << std::endl;
std::cout << "password:" << password << std::endl;
}
else if (msg.pSender->GetName() == _T("btnQuit"))
{
Close();
}
}
LRESULT Login::HandleMessage(UINT uMsg, WPARAM wParam, LPARAM lParam)
{
LRESULT lRes = 0;
switch (uMsg) {
case WM_CREATE:
lRes = OnCreate(uMsg, wParam, lParam);
break;
case WM_CLOSE:
lRes = OnClose(uMsg, wParam, lParam);
break;
default:
break;
}
if (_paintManager.MessageHandler(uMsg, wParam, lParam, lRes))
{
return lRes;
}
return __super::HandleMessage(uMsg, wParam, lParam);
}
LRESULT Login::OnCreate(UINT uMsg, WPARAM wParam, LPARAM lParam)
{
_paintManager.Init(m_hWnd);
DuiLib::CDialogBuilder builder;
DuiLib::CControlUI* pRoot = builder.Create(_T("login.xml"), (UINT)0, NULL, &_paintManager);
_paintManager.AttachDialog(pRoot);
_paintManager.AddNotifier(this);
return 0;
}
LRESULT Login::OnClose(UINT uMsg, WPARAM wParam, LPARAM lParam)
{
return 0;
}
运行结果如下所示
这个时候会发现密码框的数字可以看到,这里要对 CEdit 加上 password="true" 这个属性,这样再运行就可以了
改完之后我们还会发现一个问题就是可以输入中文,我们一般是不允许密码是中文的,我们需要对输入的字符做一些处理,当用户输入时,编辑框的内容被用户改变了,会触发EN_CHANGE事件,查看CEdit源码,我们发现有这样一个函数
LRESULT CEditWnd::OnEditChanged(UINT /*uMsg*/, WPARAM /*wParam*/, LPARAM /*lParam*/, BOOL& /*bHandled*/)
{
if( !m_bInit ) return 0;
if( m_pOwner == NULL ) return 0;
// Copy text back
int cchLen = ::GetWindowTextLength(m_hWnd) + 1;
LPTSTR pstr = static_cast<LPTSTR>(_alloca(cchLen * sizeof(TCHAR)));
ASSERT(pstr);
if( pstr == NULL ) return 0;
::GetWindowText(m_hWnd, pstr, cchLen);
m_pOwner->m_sText = pstr;
m_pOwner->GetManager()->SendNotify(m_pOwner, DUI_MSGTYPE_TEXTCHANGED);
if( m_pOwner->GetManager()->IsLayered() ) m_pOwner->Invalidate();
return 0;
}
会发送一个 DUI_MSGTYPE_TEXTCHANGED 的消息,在UIDefine.h里面有这样一个定义
#define DUI_MSGTYPE_TEXTCHANGED (_T("textchanged"))
接下来我们就可以在 Notify 里面处理一下 textchanged 这个消息了
加入我们的 Button 的样式都是一样的,所以我们可以为 Button 指定一个默认样式,这样之后改动就只用改一次属性就可以了。给<Window>节点添加一个Default节点即可,其中name属性填写控件的名字,value属性添加控件的属性的值,不过需要将双引号【"】换成【"】,单引号【'】换成【'】,单引号也可以不转换。接下来我们的 xml 会变成下面这样
<?xml version="1.0" encoding="UTF-8"?>
<Window size="960,540" mininfo="600,400" caption="0,0,0,32" sizebox="4,4,4,4">
<Font id="0" name="宋体" size="18" bold="false" underline="false" italic="false" />
<Default name="Button" value=" height="30" width="100" normalimage="file='common/button_normal.bmp'" hotimage="file='common/button_over.bmp'" pushedimage="file='common/button_down.bmp'" font="0"" />
<VerticalLayout bkcolor="#FFDFFDF0" width="300" height="318"> <!-- 整个窗口使用 VerticalLayout 布局 -->
<VerticalLayout>
<Label name= "userIdLabel" text="用户名:" float="true" pos="300,80,370,120" normaltextcolor="white" font="0"/>
<Label name= "passwordLabel" text="密 码:" float="true" pos="300,140,370,180" normaltextcolor="white" font="0"/>
<Edit name="userIdEdit" text="" float="true" pos="370,85,0,0" width="250" height="30" bkcolor="#FFFFFFFF" textpadding="4,3,4,3" textcolor="#FF000000" disabledtextcolor="#FFA7A6AA" font="0"/>
<Edit name="passwordEdit" password="true" text="" float="true" pos="370,146,0,0" width="250" height="30" bkcolor="#FFFFFFFF" textpadding="4,3,4,3" textcolor="#FF000000" disabledtextcolor="#FFA7A6AA" font="0"/>
<Button name="btnLogin" text="登录" float="true" pos="300,220,400,250"/>
<Button name="btnQuit" text="退出" float="true" pos="500,220,600,250"/>
</VerticalLayout>
</VerticalLayout>
</Window>
2、单选框、多选框
duilib 中单选框和多选框都可以用 Option 表示,多选框也可以用 CheckBox 表示
xml 文件内容如下
<?xml version="1.0" encoding="UTF-8"?>
<Window size="960,540" mininfo="600,400" caption="0,0,0,32" sizebox="4,4,4,4">
<Font id="0" shared="true" name="宋体" size="18" bold="false" underline="false" italic="false" />
<Font id="1" shared="true" name="宋体" size="20" bold="false" underline="false" italic="false" />
<Default shared="true" name="Button" value=" height="30" width="100" normalimage="file='common/button_normal.bmp'" hotimage="file='common/button_over.bmp'" pushedimage="file='common/button_down.bmp'" font="0"" />
<Default shared="true" name="Option" value="textcolor="#FFbac0c5" hottextcolor="#FF386382" selectedtextcolor="#FF386382" disabledtextcolor="#FFbac0c5" textpadding="18,2,0,0" align="left" selectedimage="file='common/RadioBtnSel.png' source='0,0,13,13' dest='0,9,14,23'" normalimage="file='common/RadioBtnNon.png' source='0,0,13,13' dest='0,9,14,23'"" />
<Default shared="true" name="CheckBox" value="textcolor="#FFbac0c5" hottextcolor="#FF386382" selectedtextcolor="#FF386382" disabledtextcolor="#FFbac0c5" textpadding="20,2,0,0" align="left" selectedimage="file='common/checked.png' dest='0,8,16,24'" normalimage="file='common/unchecked.png' dest='0,8,16,24'"" />
<VerticalLayout bkcolor="#FFDFFDF0" width="300" height="318"> <!-- 整个窗口使用 VerticalLayout 布局 -->
<VerticalLayout>
<Label name= "userIdLabel" text="用户名:" float="true" pos="300,80,370,120" normaltextcolor="white" font="0"/>
<Label name= "passwordLabel" text="密 码:" float="true" pos="300,140,370,180" normaltextcolor="white" font="0"/>
<Edit name="userIdEdit" text="" float="true" pos="375,85,0,0" width="250" height="30" bkcolor="#FFFFFFFF" textpadding="4,3,4,3" textcolor="#FF000000" disabledtextcolor="#FFA7A6AA" font="0"/>
<Edit name="passwordEdit" password="true" text="" float="true" pos="375,146,0,0" width="250" height="30" bkcolor="#FFFFFFFF" textpadding="4,3,4,3" textcolor="#FF000000" disabledtextcolor="#FFA7A6AA" font="0"/>
<Button name="btnLogin" text="登录" float="true" pos="300,300,400,330"/>
<Button name="btnQuit" text="退出" float="true" pos="500,300,600,330"/>
<Option name="radioTeacher" group="radio" text="老师" pos="370,200,450,230" float="true" font="0" />
<Option name="radioStudent" group="radio" text="学生" pos="500,200,580,230" float="true" font="0" />
<CheckBox name="CheckBox1" pos="370,250,450,280" float="true" text="选择1" font="0" />
<CheckBox name="CheckBox2" pos="500,250,580,280" float="true" text="选择2" font="0" />
</VerticalLayout>
</VerticalLayout>
</Window>
在上面代码中 Option,CheckBox 都是做了默认样式的处理,当然自己也可以做修改,其中有几个新的属性在这里介绍一下
textcolor:默认文本颜色
hottextcolor:鼠标移动上去的文本颜色
selectedtextcolor:选中的时候文本颜色
disabledtextcolor:失效的时候文本颜色,也就是选中了其它的 Option
textpadding:该控件的文本内容的显示和该控件的内边距属性的内容
align:对齐方式
selectedimage:被选中的时候的图片,也就是带点的图片
source:图片的位置。想在控件上画出来图片的哪一块。这一个也可随便坐标
dest:你要放在这个控件的哪里。如果不设就是整个控件
在代码中判断是否选中,可以做下面的处理
void Login::OnClick(DuiLib::TNotifyUI& msg)
{
if (msg.pSender->GetName() == _T("btnLogin"))
{
auto editUserId = (DuiLib::CEditUI*)_paintManager.FindControl(_T("userIdEdit"));
auto editPassword = (DuiLib::CEditUI*)_paintManager.FindControl(_T("passwordEdit"));
std::string userId = editUserId->GetText();
std::string password = editPassword->GetText();
std::cout << "userId:" << userId << std::endl;
std::cout << "password:" << password << std::endl;
auto option = dynamic_cast<DuiLib::COptionUI*>(_paintManager.FindControl(_T("radioTeacher")));
std::cout << "radioTeacher:" << option->IsSelected() << std::endl;
option = dynamic_cast<DuiLib::COptionUI*>(_paintManager.FindControl(_T("radioStudent")));
std::cout << "radioStudent:" << option->IsSelected() << std::endl;
auto checkBox = dynamic_cast<DuiLib::CCheckBoxUI*>(_paintManager.FindControl(_T("CheckBox1")));
std::cout << "CheckBox1:" << checkBox->GetCheck() << std::endl;
checkBox = dynamic_cast<DuiLib::CCheckBoxUI*>(_paintManager.FindControl(_T("CheckBox2")));
std::cout << "CheckBox2:" << checkBox->GetCheck() << std::endl;
}
else if (msg.pSender->GetName() == _T("btnQuit"))
{
Close();
}
else if (msg.pSender->GetName() == _T("radioTeacher"))
{
std::cout << "radioTeacher selected" << std::endl;
}
else if (msg.pSender->GetName() == _T("radioStudent"))
{
std::cout << "radioStudent selected" << std::endl;
}
else if (msg.pSender->GetName() == _T("CheckBox1"))
{
std::cout << "CheckBox1 click" << std::endl;
}
else if (msg.pSender->GetName() == _T("CheckBox2"))
{
std::cout << "CheckBox2 click" << std::endl;
}
}
void Login::OnEditTextChange(DuiLib::TNotifyUI& msg)
{
if (msg.pSender->GetName() == _T("passwordEdit")) {
}
}
其中多选框也可以使用 IsSelected() 判断是不是被选中了,因为 GetCheck() 函数内部调用的就是 IsSelected() 函数
运行代码,结果如下所示
3、组合框也叫做下拉框
xml 代码如下
<?xml version="1.0" encoding="UTF-8"?>
<Window size="960,540" mininfo="600,400" caption="0,0,0,32" sizebox="4,4,4,4">
<Font id="0" shared="true" name="宋体" size="18" bold="false" underline="false" italic="false" />
<Font id="1" shared="true" name="宋体" size="20" bold="false" underline="false" italic="false" />
<Font id="2" shared="true" name="宋体" size="16" bold="false" underline="false" italic="false" />
<Default shared="true" name="Button" value=" height="30" width="100" normalimage="file='common/button_normal.bmp'" hotimage="file='common/button_over.bmp'" pushedimage="file='common/button_down.bmp'" font="0"" />
<Default shared="true" name="Option" value="textcolor="#FFbac0c5" hottextcolor="#FF386382" selectedtextcolor="#FF386382" disabledtextcolor="#FFbac0c5" textpadding="18,2,0,0" align="left" selectedimage="file='common/RadioBtnSel.png' source='0,0,13,13' dest='0,9,14,23'" normalimage="file='common/RadioBtnNon.png' source='0,0,13,13' dest='0,9,14,23'"" />
<Default shared="true" name="CheckBox" value="textcolor="#FFbac0c5" hottextcolor="#FF386382" selectedtextcolor="#FF386382" disabledtextcolor="#FFbac0c5" textpadding="20,2,0,0" align="left" selectedimage="file='common/checked.png' dest='0,8,16,24'" normalimage="file='common/unchecked.png' dest='0,8,16,24'"" />
<VerticalLayout bkcolor="#FFDFFDF0" width="300" height="318"> <!-- 整个窗口使用 VerticalLayout 布局 -->
<VerticalLayout>
<Label name= "userIdLabel" text="用户名:" float="true" pos="300,80,370,120" normaltextcolor="white" font="0"/>
<Label name= "passwordLabel" text="密 码:" float="true" pos="300,140,370,180" normaltextcolor="white" font="0"/>
<Edit name="userIdEdit" text="" float="true" pos="375,85,0,0" width="250" height="30" bkcolor="#FFFFFFFF" textpadding="4,3,4,3" textcolor="#FF000000" disabledtextcolor="#FFA7A6AA" font="0"/>
<Edit name="passwordEdit" password="true" text="" float="true" pos="375,146,0,0" width="250" height="30" bkcolor="#FFFFFFFF" textpadding="4,3,4,3" textcolor="#FF000000" disabledtextcolor="#FFA7A6AA" font="0"/>
<Label name= "serverLabel" text="服务器:" float="true" pos="300,200,370,230" normaltextcolor="white" font="0"/>
<Combo name="env_type" pos="375,200,520,230" width="250" float="true" itemtextcolor="#FF000000" font="3" normalimage=" file='common/normal.png' " hotimage=" file='common/normal.png' " pushedimage=" file='common/normal.png' " itemfont="2" >
<ListLabelElement text=" 线上环境" selected="true" height="30"/>
<ListLabelElement text=" 测试环境" height="30"/>
</Combo>
<Option name="radioTeacher" group="radio" text="老师" pos="370,260,450,290" float="true" font="0" />
<Option name="radioStudent" group="radio" text="学生" pos="500,260,580,290" float="true" font="0" />
<CheckBox name="CheckBox1" pos="370,320,450,350" float="true" text="选择1" font="0" />
<CheckBox name="CheckBox2" pos="500,320,580,350" float="true" text="选择2" font="0" />
<Button name="btnLogin" text="登录" float="true" pos="300,390,400,420"/>
<Button name="btnQuit" text="退出" float="true" pos="500,390,600,420"/>
</VerticalLayout>
</VerticalLayout>
</Window>
其中 itemfont 表示子列表的字体大小
在代码中识别选中了哪个
void Login::OnClick(DuiLib::TNotifyUI& msg)
{
if (msg.pSender->GetName() == _T("btnLogin"))
{
auto editUserId = (DuiLib::CEditUI*)_paintManager.FindControl(_T("userIdEdit"));
auto editPassword = (DuiLib::CEditUI*)_paintManager.FindControl(_T("passwordEdit"));
std::string userId = editUserId->GetText();
std::string password = editPassword->GetText();
std::cout << "userId:" << userId << std::endl;
std::cout << "password:" << password << std::endl;
auto option = dynamic_cast<DuiLib::COptionUI*>(_paintManager.FindControl(_T("radioTeacher")));
std::cout << "radioTeacher:" << option->IsSelected() << std::endl;
option = dynamic_cast<DuiLib::COptionUI*>(_paintManager.FindControl(_T("radioStudent")));
std::cout << "radioStudent:" << option->IsSelected() << std::endl;
auto checkBox = dynamic_cast<DuiLib::CCheckBoxUI*>(_paintManager.FindControl(_T("CheckBox1")));
std::cout << "CheckBox1:" << checkBox->GetCheck() << std::endl;
checkBox = dynamic_cast<DuiLib::CCheckBoxUI*>(_paintManager.FindControl(_T("CheckBox2")));
std::cout << "CheckBox2:" << checkBox->GetCheck() << std::endl;
auto combo = dynamic_cast<DuiLib::CComboUI*>(_paintManager.FindControl(_T("env_type")));
std::cout << "env_type:" << combo->GetText() << std::endl;
std::cout << "env_type:" << combo->GetCurSel() << std::endl; // 第一个输出0
}
else if (msg.pSender->GetName() == _T("btnQuit"))
{
Close();
}
else if (msg.pSender->GetName() == _T("radioTeacher"))
{
std::cout << "radioTeacher selected" << std::endl;
}
else if (msg.pSender->GetName() == _T("radioStudent"))
{
std::cout << "radioStudent selected" << std::endl;
}
else if (msg.pSender->GetName() == _T("CheckBox1"))
{
std::cout << "CheckBox2 click" << std::endl;
}
else if (msg.pSender->GetName() == _T("CheckBox2"))
{
std::cout << "CheckBox2 click" << std::endl;
}
}
到这里一些基本的控件也都介绍的差不多了
二、参考文章
1、DuiLib xml 配置项:https://alenstar.github.io/post/duilib_xml/
2、Duilib CEdit 禁止输入中文的办法:https://www.bbsmax.com/A/amd012XDzg/