DuiLib简介核心类CWindowWndCDialogBuilerCPaintManagerUI控件类控件使用示例背景小图标鼠标拖动弹出式菜单时间日期控件定制列表List平铺布局TileLayout注意事项说明颜色表示设定控件位置入口main多线程下改变界面

DirectUI意为直接在父窗口上绘图(Paint on parent dc directly)。即子窗口不以窗口句柄的形式创建(windowless),只是逻辑上的窗口,绘制在父窗口之上。

DuiLib简介

DuiLib是一款强大的界面开发工具,将用户界面和逻辑处理进行分离。其使用XML描述界面风格和布局,可以很方便的构建高效、绚丽的、非常易于扩展的界面。

核心类

CWindowWnd


窗体对象管理父类,主要:

  • 创建窗体

  • 窗体消息过程处理

  • 提高窗口子类化与超类化接口

主要函数说明:

  • Create:注册窗口,设定回调函数,创建窗口,处理消息

  • CenterWindow:窗口居中

  • ShowWindow:显示窗口

  • ShowModal:以模态方式显示窗口

可重写的虚函数:

  • HandleMessage(UINT uMsg, WPARAM, LPARAM):消息处理(uMsg标识类型,如‘WM_CREATE’等)函数,所有消息都可在此截获,Notify与HandleCustomMessage都在此函数中调用的;若重载,函数结尾需要调用父类消息处理函数__super::HandleMessage

  • HandleCustomMessage:一般推荐继承此函数来处理消息;

  • Notify(TNotifyUI &msg):控件消息处理,根据msg.sType(如‘click’,‘textchanged’)判断消息类型;

  • OnFinalMessage:最后的消息,一般删除自身delete this

  • InitWindow:在OnCreate完成时调用;可在里面获取控件信息,通过m_pm.FindControl("name")来获取控件UI对象,以便操作控件。

以关闭窗体为例:若是主窗体关闭且要退出程序,则发送PostQuitMessage来退出消息循环(程序退出);其他情形,则只是关闭窗体并不退出程序。

LRESULT MyControl::HandleCustomMessage(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL &bHandled){
  switch(uMsg){
  case WM_CLOSE:
    m_bQuit = (ID_CLOSE_WINDOW_ONLY != wParam);
    break;

  case WM_DESTROY:
    ::DestroyWindow(m_hWnd);
    if(m_bQuit && !m_bChildWin)
      ::PostQuitMessage(0L);
  }

  // ...
  return 0L
}

CDialogBuiler

控件布局类,主要:

  • 读取XML脚本,分析脚本,构建控件树;

  • 创建控件对象;

主要函数说明

  • Create("test.xml", 0, NULL, &m_pm, NULL)创建控件类

  • FindSubControl("name"):在创建的控件中,查找子项

CPaintManagerUI

窗口消息及图形绘制管理类,与窗口绑定,主要:

  • 绘制控件

  • 消息管理

  • 事件通知

在绘制控件时,会以如下顺序调用CControlUI的绘制函数:

  • PaintBkColor(hDC):绘制背景颜色  

  • PaintBkImage(hDC):绘制背景图  

  • PaintStatusImage(hDC):绘制状态图

  • PaintForeColor(hDC):绘制前景颜色

  • PaintForeImage(hDC):绘制前景图

  • PaintText(hDC):绘制文本  

  • PaintBorder(hDC):绘制边框

MessageLoop处理消息循环,可通过在HandleCustomMessage的WM_DESTROY消息中发送退出消息::PostQuitMessage(0),以结束循环退出程序。

控件类


DuiLib中主要控件:

  • CControlUI,控件管理父类,控件的通用基类,提供控件通用属性管理。

  • CLabelUI,静态标签类,父类CControlUI。

  • CButtonUI,按钮类,父类CLabelUI。

  • COptionUI,选择按钮类,父类CButtonUI。

  • CTextUI,静态文本类,父类CLabelUI。

  • CProgressUI,进度条类,父类CLabelUI。

  • CSliderUI,父类CProgressUI。

  • CEditUI,编辑框类,父类CLabelUI。

  • CListUI,列表框类,父类CVerticalLayoutUI、IListUI。

  • CComboUI,组合框类,父类CContainerUI、IListOwnerUI。

  • CActiveXUI,ActiveX控件类,父类CControlUI、 IMessageFilterUI。

  • CContainerUI,容器类,父类CControlUI、IContainerUI。

  • CTabLayoutUI,选项页布局类,父类CContainerUI。

  • CTileLayoutUI,平铺布局类,父类CContainerUI。

  • CDialogLayoutUI,对话框布局类,父类CContainerUI。、

  • CVerticalLayoutUI,垂直布局类,父类CContainerUI。

  • CHorizontalLayoutUI,水平布局类,父类CContainerUI。

  • CListExpandElementUI,父类CListTextElementUI。

  • CListContainerElementUI,父类CContainerUI、IListItemUI。

容器类中可以包含其他子控件,从而定制出复杂的控件

CListUI,列表框类中可包含以下子项

  • CListHeaderUI,父类CHorizontalLayoutUI。

  • CListHeaderItemUI,列表头类,父类CControlUI。

  • CListTextElementUI,类表文本类,父类CListLabelElementUI。

  • CListLabelElementUI,父类CListElementUI。

  • CListContainerElementUI,容器类,可组合多个控件作为一个子项。

控件使用示例

一些复杂或易出错控件的使用示例说明。

背景小图标

通过normalimage等可以设定控件的默认背景图片,但是无法指定具体位置与大小(会被拉伸为整个控件的大小);若要定制背景图标(显示在特定区域)在需要使用bkimage属性(dest指定在控件中相对位置与大小,source指定要显示图标的大小)。

以下列框右侧向下箭头图标为例:下拉框中的文本属性只能在Combo中通过item*来设定

<Combo name="cmb_test" width="139" height="40" bkimage="file='arrow' dest='113,15,130,26' source='0,0,17,11'" textcolor="#FF000000" bordercolor="#FFDDDDDD" font="2" textpadding="10,0,6,0" itemfont="2" itemtextpadding="10,10,6,0" >
  <ListLabelElment text"One" height="30" userdata="1" selected="true" />
  <ListLabelElment text"Two" height="30" userdata="2" />
</Combo>

鼠标拖动

窗体若有标题栏Caption则自动支持通过标题栏进行拖动,若没有标题栏、同时支持任意位置拖动,可通过处理鼠标事件来实现。

// m_bDrageMove:标识当前是否在拖动状态
// m_ptDrageStart:标识拖动开始的位置
LRESULT DragControl::HandleCustomMessage(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL &bHandled){
  switch(uMsg){
  case WM_MOUSEMOVE:
    DrageMove(lParam);
    break;
  case WM_LBUTTONUP:
    m_bDrageMove = false;
    break;
  case WM_LBUTTONDOWN:
    DrageDown(lParam);
    break;

  default: break;
  }

  return 0L;
}

void DragControl::DrageMove(LPARAM lParam){
  if( ::GetAsyncKeyState(VK_LBUTTON)!=0 && m_bDrageMove ){
    DrageWnd(lParam);
  }
}

// IsDrageRect判断是否在拖动区域
// 整个拖动过程中GetWindowRect获取到的位置都是拖动开始时的位置
void DragControl::DrageWnd(LPARAM lParam){
  POINT pt{GET_X_LPARAM(lParam), GET_Y_LPARAM(lParam)};
  if(IsDrageRect(pt, m_hWnd)){
    RECT rtCur;
    ::GetWindowRect(m_hWnd, &rtCur);

    int cx = rcCur.left + (pt.x - m_ptDrageStart.x);
    int cy = rcCur.top + (pt.y - m_ptDrageStart.y);
    int width = rcCur.right - rcCur.left;
    int hight = rcCur.bottom - rcCur.top;
    ::SetWindowPos(m_hWnd, NULL, cx, cy, width, hight, SWP_NOZOREDER|SWP_NOSIZE|SWP_NOACTIVATE);
  }
}

void DragControl::DrageDown(LPARAM lParam){
  POINT ptStart{GET_X_LPARAM(lParam), GET_Y_LPARAM(lParam)};
  if(IsDragedControl(ptStart)){
    m_bDrageMove = true;
    m_ptDrageStart = ptStart;
  }
}

// 只有静态控件才被允许拖动
bool DragControl::IsDragedControl(POINT ptMouse){
  DuiLib::CControlUI *pControl = m_pm.FindControl(ptMouse);
  if(CxIsNull(pControl)) return true;

  return IsInStaticControl(pControl);
}

弹出式菜单

弹出式菜单可以方便增强界面功能,易于操作。通过CMenuWnd可方便实现。

以点击按钮(m_pBtn)弹出菜单(m_pMenu)为例。菜单样式定义文件:

<?xml version="1.0" encoding="utf-8"?>
<Window roudcorner="4.4" showshadow="true" shadowimage="shadow.png" shadowsize="8" shadowcorner="8,8,8,8">
 <Fond id="0" name="黑体" size=16 bold="false"/>

 <Menu name="menuTest" bkcolor="#FFFFFFFF" itemselectedbkcolor="#FFDDDDDD" itemalign="center" itemfond="0">
  <MenuElement name="one" width="120" height="40" text="First" />
  <MenuElement name="tow" width="120" height="40" text="Second" />
 </Menu>
</Window>

窗体CPP中菜单操作代码:

// 弹出:(显示在m_pBtn下面)
RECT rcBtn = m_pBtn->GetClientPos();
RECT rcWin{0};
::GetWindowRect(m_hWnd, &rcWin);
POINT ptStart{rcBtn.left + rcWin.left + 5, rcBtn.Bottom + rcWin.top};
m_pMenu = CMenuWnd::CreateMenu(NULL, _T("menu.xml"), ptStart, &m_pm); // ptStart为菜单显示位置

// 响应,在点击菜单时(WM_MENUCLICK事件)
MenuCmd *pCmd = (MenuCmd*)wParam;
CDuiString strName = pCmd->szName;
if( szName == "ourMenu") {...}
m_pm.DeletePrt(pCmd)
m_pMenu->Close(); // 同时销毁菜单

// 销毁:菜单失去焦点时:Notify(msg.sType == "killfocus")时
// 为避免bug,在DuiLib中菜单的Close中需增加:if(isClosing) return;
m_pMenu->Close();

时间日期控件

DuiLib中没有实现自己的日期时间控件,可使用Windows的DATETIMEPICK_CLASS实现的;只需定义一个Edit控件(占位控件,时间控件在此位置显示且使用此控件的大小),然后在InitWindows时构建一个时间控件即可。

时间日期控件实现,继承CWindowWnd,然后实现几个基本函数即可:

class XuDateTime : public DuiLib::CWindowWnd{
public:
  // lpFormat: 日期显示格式
  // bDateOrFormat:创建日期还是时间选择控件
  // bFutureTime:是否只能选择当前时间之后的时间
  void Init(DuiLib::CEditUI *pOwner, LPCTSTR lpFormat, bool bDateOrFormat, bool bFutureTime);

  LPCTSTR GetWindowClassName() const {
    return L"XDateTime";
  }

  LPCTSTR GetSuperClassName() const {
    return DATETIMEPICK_CLASS;
  }

  void OnFinalMessage(HWND hWnd){
    delete this;
  }

  SYSTEMTIME GetTime() const{
    SYSTEMTIME sysTime;
    ::SendMessage(m_hWnd, DTM_GETSYSTEMTIME, 0, (LPARAM)&sysTime);

    return sysTime;
  }
}

void XuDateTime::Init(DuiLib::CEditUI *pOwner, LPCTSTR lpFormat, bool bDateOrFormat, bool bFutureTime)){
  if(m_hWnd == NULL){
    // 使用占位控件的位置与大小创建时间控件
    RECT rcPos;
    SIZE szPos = pOwner->GetFixedXY();
    rcPos.left = szPos.cx;
    rcPos.top = szPos.cy;
    rcPos.right = szPos.cx + pOwner->GetFixedWidth();
    rcPos.bottom = szPos.cy + pOwner->GetFixedHeight();

    UINT uStyle = WS_CHILD;
    if(bDateOrFormat)
      uStyle |= DTS_SHORTDATEFORMAT;
    else
      uStyle |= DTS_TIMEFORMAT;
    Create(pOwner->GetManager()->GetPaintWindow(), NULL, uStyle, 0, rcPos);

    if(lpFormat!=NULL)
      DateTime_SetFormat(m_hWnd, lpFormat);
    if(bFutureTime){
      SYSTEMTIME sysTime;
      ::GetLocalTime(&sysTime);
      DateTime_SetRange(m_hWnd, GDTR_MIN, &sysTime);
    }
  }

  ::ShowWindow(m_hWnd, SW_SHOWNOACTIVATE);
}

使用时,查找到对应的控件(显示时间控件的占位编辑框),设定即可

// 使用时,在窗口类中的InitWindow中
void TestMainController::InitWindow(){
  // 设m_pTxtDate 对应编辑框为txt_date, m_pWndDate为时间控件
  m_pTxtDate = static_cast<DuiLib::CEditUI*>(m_pm.FindControl(L"txt_date"));
  m_pWndDate = new XuDateTime();
  m_pWndDate->Init(m_pTxtDate, L"yyyy-MM-dd", true, true);
  m_pTxtDate->SetVisible(false);  
}

定制列表List

使用ListContainerElement可定制复杂的列表项,只需在Container中添加所需的控件即可。
在需要列表的地方增加List配置(窗体XML):

 <List name="lst_test" pos="10,10,0,0" float="true" width="300" height="400" vscrollbar="true" itemselectedbkcolor="#FF00B2DD" itemhotbkcolor="#FFDDDDDD" bordersize="0">
 </List>

在独立的XML文件中配置列表项:

<?xml version="1.0" encoding="utf-8"?>
<Window>
 <Fond id="0" name="黑体" size=16 bold="false"/>

 <ListContainerElement>
  <VerticalLayout name="layItem" width="360" height="40" float="true">
    <Label name="lblTitle" text="" pos="20,5,0,0" width="360" height="20" float="true" textcolor="#FFFFFFFF" fond="0"/>
    <Label name="lblTime" text="" pos="20,20,0,0" width="360" height="20" float="true" textcolor="#FFDDDDDD" fond="0"/>
  </VerticalLayout>
 </ListContainerElement>
</Window>

在窗体CPP文件中添加列表项:

void MyControl::BuildListItem(LPCTSTR lpTitle, LPCTSTR lpTime){
  CDialogBuilder builer;
  auto *pItem = static_cast<DuiLib::CListContainerElementUI*>(builer.Create(L"myListItem.xml"), 0, NULL, &m_pm, NULL);

  auto *pLblTitle=static_cast<DuiLib::CLabelUI*>(pItem->FindSubControl(L"lblTitle"));
  pLblTitle->SetText(lpTitle);

  auto *pLblTime=static_cast<DuiLib::CLabelUI*>(pItem->FindSubControl(L"lblTime"));
  pLblTime->SetText(lpTime);

  pItem->SetFixedHeight(60);
  m_pMyList->Add(pItem);  // m_pMyList为List控件
}

void MyControl::BuildTileItem(LPCTSTR lpImg, LPCTSTR lpName){
  CDialogBuilder builer;
  auto *pItem = static_cast<DuiLib::CHorizontalLayoutUI*>(builer.Create(L"myTileItem.xml"), 0, NULL, &m_pm, NULL);

  auto *pLblImg=static_cast<DuiLib::CLabelUI*>(pItem->FindSubControl(L"lblImg"));
  pLblTitle->SetBkImage(lpImg);

  auto *pLblTime=static_cast<DuiLib::CLabelUI*>(pItem->FindSubControl(L"lblName"));
  pLblTime->SetText(lpName);

  m_pMyTile->Add(pItem);  
}

平铺布局TileLayout

使用平铺布局,可以使条目以平铺的方式显示(从做到右,从上到下)每一个条目,并根据数量自动显示滚动条。
在需要平铺布局的地方增加Layout配置(窗体XML):

<TileLayout name="lay_text" vscrollbar="true" columns="4" inset="2,2,2,2">
</TileLayout>

在独立的XML文件中配置显示项:

<?xml version="1.0" encoding="utf-8"?>
<Window>

 <HorizontalLayout width="100" height="35" inset="0,0,0,5">
    <Label name="lblImg" text="" width="36" height="30" bkimage="myimg.png" />
    <Label name="lblName" text="Kunpeng" height="30" textpadding="5,0,0,0" />
 </ListContainerElement>
</Window>

在窗体CPP总添加显示项处理代码:

void MyControl::BuildTileItem(LPCTSTR lpImg, LPCTSTR lpName){
  CDialogBuilder builer;
  auto *pItem = static_cast<DuiLib::CHorizontalLayoutUI*>(builer.Create(L"myTileItem.xml"), 0, NULL, &m_pm, NULL);

  auto *pLblImg=static_cast<DuiLib::CLabelUI*>(pItem->FindSubControl(L"lblImg"));
  pLblTitle->SetBkImage(lpImg);

  auto *pLblTime=static_cast<DuiLib::CLabelUI*>(pItem->FindSubControl(L"lblName"));
  pLblTime->SetText(lpName);

  m_pMyTile->Add(pItem);  
}

注意事项说明

颜色表示

DuiLib中使用八位十六进制数表示颜色,前两位表示透明度(一般为FF即可),后面几位代表RGB。如白色textcolor="#FF000000"

设定控件位置

重新设定控件位置时,不能使用SetPos(否则其他控件SetVisible会直接影响实际的位置),需要使用:

  • SetFixedXY:设定起始坐标(左与上);

  • SetFixedHeight:设定高度;

  • SetFixedWidth :设定宽度。

入口main

在main中定义控件,创建并显示,然后调用消息循环,等待消息。

int APIENTRY WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, PWSTR lpCmdLine, int nCmdShow)
{
    CPaintManagerUI::SetInstance(hInstance);
    CPaintManagerUI::SetResourcePath(CPaintManagerUI::GetInstancePath());

    CMyWindowController myCtrl;
    myCtrl.Create(NULL, _T("测试"), UI_WNDSTYLE_FRAME, WS_EX_WINDOWEDGE);
    myCtrl.ShowWindow();

    CPaintManagerUI::MessageLoop();
    return 0;
}

多线程下改变界面

在线程里不要操作界面,应使用SendMessage或者PostMessage给界面的m_hWnd发送自定义消息。然后在界面的消息循环里面在做操作界面的动作。

#define ON_PERCENT_MSG              WM_USER + 500
// 在线程函数中发送消息给界面
int DownloadView::on_percent( double percent, int index, INT_PTR user_data )
{
    ::SendMessage(m_hWnd, ON_PERCENT_MSG, (WPARAM)&percent, (LPARAM)user_data);
    return 0;
}


// 在界面消息循环中进行处理消息
LRESULT DownloadView::HandleCustomMessage( UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled )
{
    switch (uMsg)
    {
    case ON_PERCENT_MSG:
         // 处理界面相关的操作
        break;
    default:
        break;
    }
    return WindowImplBase::HandleCustomMessage(uMsg, wParam, lParam, bHandled);
}
Logo

有“AI”的1024 = 2048,欢迎大家加入2048 AI社区

更多推荐