代碼倉庫: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/