今天在做一个程序的界面时,需要在一个列表中显示除文字以外的其他控件,如:Edit、Button、Combo等;我做界面使用的是duilib,其自带的CListUI并不能满足此向功能,需要进行扩展,在此记录,以便后续使用需要。
先看一下实现的效果:
今天我们的扩展主要包含如下部分:
1. 表头支持控件
在ListUI中,表头是CListHeaderItemUI,而这个类是从CControlUI继承而来,为了能支持在其中显示其他控件,它需要是一个窗口,那么需要将其父类改成CContainerUI,调整基类后,需要同时将CListHeaderItemUI类中引用 CControlUI的地方全部改成CContainerUI,如:
LPVOID CListHeaderItemUI::GetInterface(LPCTSTR pstrName) { if( _tcscmp(pstrName, DUI_CTR_LISTHEADERITEM) == 0 ) return this; return CContainerUI::GetInterface(pstrName); }
如果此处不改,将无法从XML文件中加载内嵌控件。
还需要修改其他如DoEvent事件中的CControlUI,否则会导致表头无法拖拉,切记。
改造后,我们可以从XML文件加载了,XML文件可以这样写:
<List name="listex" bkcolor="#FFFFFFFF" inset="0,0,0,0" itemshowhtml="true" vscrollbar="true" hscrollbar="true" headerbkimage="file=‘list_header_bg.png‘" itemhotimage="file=‘tree_hot.bmp‘ corner=‘2,1,2,1‘" itemselectedimage="file=‘tree_select.bmp‘ corner=‘2,1,2,1‘" itemalign="center" itembkcolor="#FFE2DDDF" itemaltbk="true" hscrollbar="false" menu="true"> <ListHeader height="24" menu="true"> <ListHeaderItem text="" inset="1,0,1,0" minwidth="60" endellipsis="true" font="1" width="95" normalimage="headerctrl_normal.bmp" hotimage="headerctrl_hot.bmp" pushedimage="headerctrl_down.bmp" sepimage="Headerctrl_Sperator.bmp" sepwidth="2"> <VerticalLayout inset="1,0,5,0"> <CheckBox name="selall" endellipsis="true" text="全选" textcolor="#FF386382" hottextcolor="#FF386382" selectedtextcolor="#FF386382" disabledtextcolor="#FFbac0c5" textpadding="20,3,0,0" align="left" selectedimage="file=‘checkbox_p.png‘ dest=‘0,2,15,17‘" normalimage="file=‘checkbox_h.png‘ dest=‘0,2,15,17‘" /> </VerticalLayout> </ListHeaderItem> <ListHeaderItem text="域名" minwidth="100" endellipsis="true" font="1" width="200" normalimage="headerctrl_normal.bmp" hotimage="headerctrl_hot.bmp" pushedimage="headerctrl_down.bmp" sepimage="Headerctrl_Sperator.bmp" sepwidth="2"/> <ListHeaderItem text="描述" minwidth="120" endellipsis="true" font="1" width="150" normalimage="headerctrl_normal.bmp" hotimage="headerctrl_hot.bmp" pushedimage="headerctrl_down.bmp" sepimage="Headerctrl_Sperator.bmp" sepwidth="2"/> </ListHeader>
这样显示出来的效果如下:
注意:
此处需要将内嵌控件的ListHeaderItem 添加一个inset属性,控制内嵌的控件不要铺满整个ListHeaderItem ,否则表头拖动不了,如:
ListHeaderItem text="" inset="1,0,1,0"
2. 列表项支持控件
CListUI的某一行CListContainerElementUI继承至容器CContainerUI,这样一来我们只需要将需要的控件添加到此容器中,即可正确的显示相关的控件了,此处并不需要做调整,只是这样一来会带来一个问题,那就是所添加的列的宽度无法与表头的宽度保持一致。
要解决这个问题,需要给CListContainerElementUI添加SetPos函数,在此函数中,重新校正数据列宽与表头宽度一致,具体如下:
void SetPos(RECT rc) { CContainerUI::SetPos(rc); if( m_pOwner == NULL ) return; if (m_pHeader == NULL) { return; } TListInfoUI* pInfo = m_pOwner->GetListInfo(); int nCount = m_items.GetSize(); for (int i = 0; i < nCount; i++) { CControlUI *pHorizontalLayout = static_cast<CControlUI*>(m_items[i]); // if (pHorizontalLayout != NULL) // { // RECT rtHeader = pHeaderItem->GetPos(); // RECT rt = pHorizontalLayout->GetPos(); // rt.left = pInfo->rcColumn[i].left; // rt.right = pInfo->rcColumn[i].right; // pHorizontalLayout->SetPos(rt); // } CListHeaderItemUI *pHeaderItem = static_cast<CListHeaderItemUI*>(m_pHeader->GetItemAt(i)); if (pHorizontalLayout != NULL && pHeaderItem != NULL) { RECT rtHeader = pHeaderItem->GetPos(); RECT rt = pHorizontalLayout->GetPos(); rt.left = rtHeader.left; rt.right = rtHeader.right; pHorizontalLayout->SetPos(rt); } } } CListHeaderUI *m_pHeader;
此处往列表项中添加了表头的指针,需要在插入一行的时候,将表头的指针传递进来,用于在SetPos的时候获取表头宽度。
注意以上代码中的注释部分,原本打算从TListInfoUI的rcColumn中获取表头项宽度的,但发现新添加行时,rcCulumn中的值全是0,需要在插入行前主动调用一次CListUI::SetPos(GetPos());才能正常,使用起来较麻烦,且容易忘记;即使是添加上了,测试发现获取到的位置有一定的偏移,所以采用将Header传入,实时获取了。
以下是一行数据的XML文件描述:
<?xml version="1.0" encoding="UTF-8"?> <Window> <ListContainerElement > <CheckBox name="selectme" endellipsis="true" text="亲,选我吧!" textcolor="#FF386382" hottextcolor="#FF386382" selectedtextcolor="#FF386382" disabledtextcolor="#FFbac0c5" textpadding="20,3,0,0" align="left" selectedimage="file=‘checkbox_p.png‘ dest=‘0,2,15,17‘" normalimage="file=‘checkbox_h.png‘ dest=‘0,2,15,17‘" /> <HorizontalLayout inset="4,4,4,4"> <Edit text="测试文本" bordersize="1" height="20" bordercolor="#FF4775CC" name="domain" ></Edit> </HorizontalLayout> <HorizontalLayout > <VerticalLayout> <Button text="按钮1" width="50" pushedimage="button_down.bmp" hotimage="button_over.bmp" normalimage="button_nor.bmp" name="ttt" ></Button> <Button text="按钮2" width="50" pushedimage="button_down.bmp" hotimage="button_over.bmp" normalimage="button_nor.bmp" name="ttt" ></Button> <Label text="这是从XML文件中加载的列表项"></Label> </VerticalLayout> </HorizontalLayout> </ListContainerElement> </Window>注意,此处需要确保ListContainerElement 的子控件个数不少于列表的列数
在代码中加载此XML文件,将行数据添加到列表中:
CListUIEx *pList = static_cast<CListUIEx*>(m_PaintManager.FindControl(_T("listex"))); CDialogBuilder builder; CListContainerElementUI* pLine = (CListContainerElementUI*)(builder.Create(_T("sigle_list_item_column.xml"),(UINT)0, this)); if( pLine != NULL ) { pList->InsertItem(0, 60, pLine); //此函数是经过二次封装的 }加载的效果如下:
这样我们可以在列表的不同项中显示任意内容,甚至是一个完整的窗口了。
3. 通过代码动态添加列及列表项
以上的处理均是调整后从XML加载相应的加载已经配置好的列表进行显示,在完成上述工作后,我这边进一步封装了几个函数,以便于动态的添加列[指定内嵌控件]、动态的插入行以及动态的在某一行列中添加控件。
1). 以下代码用于动态的添加列:
BOOL CListUIEx::InsertColumn( int nCol, CListHeaderItemUI *pHeaderItem ) { CListHeaderUI *pHeader = CListUI::GetHeader(); if (pHeader == NULL) { return FALSE; } if (pHeader->AddAt(pHeaderItem, nCol)) { return TRUE; } delete pHeaderItem; pHeaderItem = NULL; return FALSE; } BOOL CListUIEx::SetHeaderItemData(int nColumn, CControlUI* pControl) { CListHeaderUI *pHeader = CListUI::GetHeader(); if (pHeader == NULL) { return FALSE; } CListHeaderItemUI *pHeaderItem = (CListHeaderItemUI *)pHeader->GetItemAt(nColumn); pHeaderItem->Add(pControl); }调用代码如下[添加一列,并且向此列中嵌入一个CheckBox]:
CListHeaderItemUI *pHeaderItem = new CListHeaderItemUI; pHeaderItem->SetTextStyle(DT_RIGHT|DT_VCENTER|DT_SINGLELINE); pHeaderItem->SetText("新增列 "); pHeaderItem->SetAttribute(_T("sepimage"), _T("Headerctrl_Sperator.bmp")); pHeaderItem->SetAttribute(_T("sepwidth"), _T("1")); pHeaderItem->SetAttribute(_T("pushedimage"), _T("headerctrl_down.bmp")); pHeaderItem->SetAttribute(_T("hotimage"), _T("headerctrl_hot.bmp")); pHeaderItem->SetAttribute(_T("normalimage"), _T("headerctrl_normal.bmp")); pHeaderItem->SetFixedWidth(150); pList->InsertColumn(3, pHeaderItem); CCheckBoxUI *pBtnUI = new CCheckBoxUI; pBtnUI->SetText("选择"); pBtnUI->SetAttribute(_T("selectedimage"), _T("file=‘checkbox_p.png‘ dest=‘0,2,15,17‘")); pBtnUI->SetAttribute(_T("normalimage"), _T("file=‘checkbox_h.png‘ dest=‘0,2,15,17‘")); pBtnUI->SetAttribute(_T("textpadding"), _T("20,3,0,0")); pBtnUI->SetAttribute(_T("align"), _T("right")); pBtnUI->SetFloat(true); pBtnUI->SetAttribute("pos", "20,3, 65, 20"); pList->SetHeaderItemData(3, pBtnUI);2). 以下代码用于动态的添加行,动态的指定一列的内容:
int CListUIEx::InsertItem(int nItem, int nHeight) { CListContainerElementUIEx *pListItem = new CListContainerElementUIEx; pListItem->SetFixedHeight(nHeight);/*固定一个行高*/ pListItem->SetList(this); CListHeaderUI *pHeader = CListUI::GetHeader(); if (NULL != pHeader) { int nHeaderCount = pHeader->GetCount(); for (int i = 0; i < nHeaderCount; i++) { pListItem->InsertColumn(i); } } if ( !CListUI::AddAt(pListItem, nItem) ) { delete pListItem; pListItem = NULL; return -1; } return nItem; } void CListUIEx::SetItemData(int nItem, int nColumn, LPCTSTR Text, LPCTSTR Name) { //存放文本 CHorizontalLayoutUI *pSubHor = GetListSubItem(nItem, nColumn); CLabelUI *pLabel = new CLabelUI; pLabel->SetText(Text);//控件属性就根据需求设置吧,我简单设置一下 pLabel->SetTextStyle(DT_CENTER); pLabel->SetAttribute("endellipsis", "true"); pSubHor->SetAttribute("inset", "3,1,3,1"); pLabel->SetName(Name); pSubHor->Add(pLabel);//添加到父控件 } void CListUIEx::SetItemData(int nItem, int nColumn,CControlUI* pControl) { CHorizontalLayoutUI *pSubHor = GetListSubItem(nItem, nColumn); pSubHor->SetAttribute("inset", "3,0,3,1"); pSubHor->Add(pControl);//添加到父控件 }
通过如下代码来添加一行数据:
CListUIEx *pList = static_cast<CListUIEx*>(m_PaintManager.FindControl(_T("listex"))); int nIndex = pList->GetCount(); pList->InsertItem(nIndex); CEditUI *pControl = new CEditUI; pControl->SetText(""); pControl->SetName("edit"); pControl->SetBorderColor(RGB(255, 0, 0)); pControl->SetAttribute("bordersize", "1"); pControl->SetAttribute("bordercolor", "#FF4775CC"); pList->SetItemData(nIndex, 0, pControl); CButtonUI *pBtnUI = new CButtonUI; pBtnUI->SetText("添加"); pBtnUI->SetFixedWidth(60); pBtnUI->SetAttribute(_T("pushedimage"), _T("button_down.bmp")); pBtnUI->SetAttribute(_T("hotimage"), _T("button_over.bmp")); pBtnUI->SetAttribute(_T("normalimage"), _T("button_nor.bmp")); pList->SetItemData(nIndex, 1, pBtnUI); pList->SetItemData(nIndex, 2, "这是一行动态添加的数据", "testid"); if (pList->GetHeader()->GetCount() > 3) { pList->SetItemData(nIndex, 3, "新增列数据", "testid1"); }
测试程序代码下载地址:http://download.csdn.net/detail/tragicguy/7087559
后记:
扩展此控件,参考了文章:http://blog.csdn.net/xdrt81y/article/details/17588961
此份测试代码改自 群友 【朗】 的 ListExtension.
同时特别感谢群友 tojen 的帮助!
duilib功能确实很强大,给我这种UI小白带来了希望,希望有越来越多的高级控件能纳入的基础源码库中,方便大众;也希望其他扩展过duilib功能的大侠,放出代码来,造福众人。
关于duilib中的list的扩展探索,布布扣,bubuko.com
原文:http://blog.csdn.net/tragicguy/article/details/21893065