看到很多PC客户端的列表都支持多选,比如PP助手,华为网盘,duilib本身UIList却没有此功能的支持,于是想修改一把,完善这方面的劣势,而且相信用到的这个功能的也不少,尤其在开发文件管理相关的功能模块,多选的支持应该是必须的。
源码下载地址:
https://download.csdn.net/download/xdrt81y/7035741
设计思路:
开始的时候,不知道从哪下手,通过度娘一番,渐渐思路清晰来,现在总结一下
1 Ctrl+左键,shift+左键多选列表项
分析这两个功能的业务逻辑。
Ctrl+左键多选,按下Ctrl键点击,主要有三种情况:
1 选中一个
2 再点击,又选中一个
3 再点击前一个,前一个选中状态消失
总结分析:ctrl按下的情况,点击,不会使前一个(一部分)选中项失去选中状态,而只是让当前点击的项改变选中状态,并将焦点移到当前项。
shift+左键,主要有4中情况:
1 点击选中一个(ID为2),向下选中一个(ID为5),则2-5被选中
2 再向下选中一个(ID为10),则2-10被选中
3 向上选中一个(ID为8),则2-8被选中
4 向上选中一个(ID为1),则1-2被选中。
总结分析:shift按下的情况,点击,是选中一个范围,起始项为焦点所在的项,shift点击的项为结束项。
ctrl和shift多选殊途同归,主要是在CListContainerElementUI的点击事件中做些改动,具体的就是UIEvent_BUTTONDOWN.
select() 控制是否选中
click() 控制其他控件的效果
在这两个函数中需检测是否按下了ctrl或shift键。
BOOL bCtrl = (GetKeyState(VK_CONTROL) & 0x8000);
BOOL bShift = (GetKeyState(VK_SHIFT) & 0x8000);
主要代码片段
UIList.cpp
void CListContainerElementUI::DoEvent(TEventUI& event)
{...
if( event.Type == UIEVENT_BUTTONDOWN )
{
if( IsEnabled() ){;
m_pManager->SendNotify(this, DUI_MSGTYPE_ITEMCLICK);
Select(!m_bSelected);
Click();
if( m_pOwner != NULL ) m_pOwner->DoEvent(event);
Invalidate();
}
return;
}
if( event.Type == UIEVENT_RBUTTONDOWN )
{
if( IsEnabled() ){
m_pManager->SendNotify(this, DUI_MSGTYPE_ITEMCLICK);
if(m_bSelected){
//printf("右键菜单");
}else{
Select();
Click();
}
if( m_pOwner != NULL ) m_pOwner->DoEvent(event);
Invalidate();
}
return;
}
if( event.Type == UIEVENT_BUTTONUP )
{
if( m_pOwner != NULL ) m_pOwner->DoEvent(event);
return;
}
if( event.Type == UIEVENT_RBUTTONUP )
{
if( m_pOwner != NULL ) m_pOwner->DoEvent(event);
return;
}
if( event.Type == UIEVENT_CONTEXTMENU )
{
if( m_pOwner != NULL ) m_pOwner->DoEvent(event);
return;
}
if( event.Type == UIEVENT_MOUSEMOVE )
{
if( m_pOwner != NULL ) m_pOwner->DoEvent(event);
return;
}
if( event.Type == UIEVENT_MOUSEENTER )
{
if( IsEnabled() ){
m_pManager->SendNotify(this, DUI_MSGTYPE_MOUSEENTER);
}
Hot();
Invalidate();
return;
}
bool CListContainerElementUI::Select(bool bSelect,bool bCallback)
{
BOOL bShift = (GetKeyState(VK_SHIFT) & 0x8000);
if( !IsEnabled() ) return false;
//if( bSelect == m_bSelected ) return true;
m_bSelected = bSelect;
if(bCallback && m_pOwner != NULL ) {
if(bShift){
m_pOwner->SelectRange(m_iIndex);
}else{
m_pOwner->SelectItem(m_iIndex);
}
}
Invalidate();
return true;
}
bool CListUI::SelectItem(int iIndex, bool bTakeFocus)
{
BOOL bCtrl = (GetKeyState(VK_CONTROL) & 0x8000);
//if( iIndex == m_iCurSel ) return true;
int iOldSel = m_iCurSel;
// We should first unselect the currently selected item
if(!bCtrl){
//if( m_iCurSel >= 0 ) {
for(int i=0;i<GetCount();i++){
if(i!=iIndex){
CControlUI* pControl = GetItemAt(i);
if( pControl != NULL) {
IListItemUI* pListItem = static_cast<IListItemUI*>(pControl->GetInterface(_T("ListItem")));
if( pListItem != NULL ) pListItem->Select(false,false);
}
}
}
m_iCurSel = -1;
//}
}
if( iIndex < 0 ) return false;
CControlUI* pControl = GetItemAt(iIndex);
if( pControl == NULL ) return false;
if( !pControl->IsVisible() ) return false;
if( !pControl->IsEnabled() ) return false;
IListItemUI* pListItem = static_cast<IListItemUI*>(pControl->GetInterface(_T("ListItem")));
if( pListItem == NULL ) return false;
m_iCurSel = iIndex;
if(!bCtrl){
if( !pListItem->Select(true,false) ) {
m_iCurSel = -1;
return false;
}
}
EnsureVisible(m_iCurSel);
if( bTakeFocus ) pControl->SetFocus();
if( m_pManager != NULL ) {
m_pManager->SendNotify(this, DUI_MSGTYPE_ITEMSELECT, m_iCurSel, iOldSel);
}
return true;
}
bool CListUI::SelectRange(int iIndex, bool bTakeFocus)
{
int i = 0;
int iFirst = m_iCurSel;
int iLast = iIndex;
int iCount = GetCount();
if(iFirst == iLast) return true;
CControlUI* pControl = GetItemAt(iIndex);
if( pControl == NULL ) return false;
if( !pControl->IsVisible() ) return false;
if( !pControl->IsEnabled() ) return false;
IListItemUI* pListItem = static_cast<IListItemUI*>(pControl->GetInterface(_T("ListItem")));
if( pListItem == NULL ) return false;
if( !pListItem->Select(true,false) ) {
m_iCurSel = -1;
return false;
}
EnsureVisible(iIndex);
if( bTakeFocus ) pControl->SetFocus();
if( m_pManager != NULL ) {
m_pManager->SendNotify(this, DUI_MSGTYPE_ITEMSELECT, iIndex, m_iCurSel);
}
//locate (and select) either first or last
// (so order is arbitrary)
while(i<iCount){
if(i==iFirst || i == iLast){
i++;
break;
}
CControlUI* pControl = GetItemAt(i);
if( pControl != NULL) {
IListItemUI* pListItem = static_cast<IListItemUI*>(pControl->GetInterface(_T("ListItem")));
if( pListItem != NULL ) pListItem->Select(false,false);
}
i++;
}
// select rest of range
while(i<iCount){
CControlUI* pControl = GetItemAt(i);
if( pControl != NULL) {
IListItemUI* pListItem = static_cast<IListItemUI*>(pControl->GetInterface(_T("ListItem")));
if( pListItem != NULL ) pListItem->Select(true,false);
}
if(i==iFirst || i == iLast){
i++;
break;
}
i++;
}
// unselect rest of range
while(i<iCount){
CControlUI* pControl = GetItemAt(i);
if( pControl != NULL) {
IListItemUI* pListItem = static_cast<IListItemUI*>(pControl->GetInterface(_T("ListItem")));
if( pListItem != NULL ) pListItem->Select(false,false);
}
i++;
}
return true;
}
2 鼠标滑动框选
业务逻辑:鼠标左键或右键按下,开始滑动,出现方框,列表项跟着选中或不选中。
设计思路:检测鼠标按下(UIEVENT_BUTTONDOWN,UIEVENT_RBUTTONDOWN),移动(UIEVENT_MOUSEMOVE),抬起(UIEVENT_BUTTONUP,UIEVENT_RBUTTONUP),还有上下文菜单(UIEVENT_CONTEXTMENU)。
按下时,设定标志位,记录初始点;移动时画框,并根据框与列表项是否选中,改变列表项的状态;抬起时,恢复标识位,右键抬起的话,
还要显示菜单。
主要代码片段:
void CCustomListUI::DoEvent(TEventUI& event){
BOOL bShift = (GetKeyState(VK_SHIFT) & 0x8000);
BOOL bCtrl = (GetKeyState(VK_CONTROL) & 0x8000);
if(bShift || bCtrl){
CListUI::DoEvent(event);
return;
}
switch( event.Type ) {
case UIEVENT_BUTTONDOWN://左键
case UIEVENT_RBUTTONDOWN://右键
{
m_bStartRect = true;
m_startPoint = event.ptMouse; //记录开始点
SetCapture(m_PM.GetPaintWindow());
}
break;
case UIEVENT_MOUSEMOVE:
//if(GetCapture() == m_PM.GetPaintWindow()){
if(m_bStartRect){
HWND hwnd = m_PM.GetPaintWindow();
RECT rcClient;
POINT point = event.ptMouse;
RECT rect = {0};
if(m_startPoint.x == point.x && m_startPoint.y == point.y) break;
::GetClientRect(hwnd, &rcClient);
::InvalidateRect(hwnd, &rcClient, FALSE);
if(point.x<m_startPoint.x && point.y < m_startPoint.y){
RECT rectTemp = {point.x,point.y,m_startPoint.x,m_startPoint.y};
rect = rectTemp;
}else if(point.x>m_startPoint.x && point.y < m_startPoint.y){
RECT rectTemp = {m_startPoint.x,point.y,point.x,m_startPoint.y};
rect = rectTemp;
}else if(point.x<m_startPoint.x && point.y > m_startPoint.y){
RECT rectTemp = {point.x,m_startPoint.y,m_startPoint.x,point.y};
rect = rectTemp;
}else {
RECT rectTemp = {m_startPoint.x,m_startPoint.y,point.x,point.y};
rect = rectTemp;
}
DWORD bkColor2 = this->GetItemBkColor();
UpdateSelectionForRect(rect);
CRenderEngine::DrawRect(m_PM.GetPaintDC(),rect,1,bkColor2);//draw
}
break;
case UIEVENT_BUTTONUP:
{
m_bStartRect = false;
if(GetCapture() == m_PM.GetPaintWindow()){
ReleaseCapture();
HWND hwnd = m_PM.GetPaintWindow();
InvalidateRect(hwnd,NULL,TRUE);
}
}
break;
case UIEVENT_RBUTTONUP:
{
m_bStartRect = false;
if(GetCapture() == m_PM.GetPaintWindow()){
ReleaseCapture();
HWND hwnd = m_PM.GetPaintWindow();
InvalidateRect(hwnd,NULL,TRUE);
}
}
case UIEVENT_CONTEXTMENU:
if( IsContextMenuUsed() ) {
m_pManager->SendNotify(this, DUI_MSGTYPE_MENU, event.wParam, event.lParam);
return;
}
break;
}
CListUI::DoEvent(event);
}
OK到此结束,实现的过程中遇到了诸多困难,但都一点点可否了,思路清晰了,实现就是个时间问题。
功能实现了,但感觉还有提升空间,希望看到的朋友多提宝贵意见。
另,需要Demo的可留言。
优化进行中
功能实现后,发现一个问题,其实之前就发现了,只是一直无对策。
问题描述:鼠标点击操作,不断点击不同的列表项,会出现不选中的情况(按理来说不应该出现),而且频率比较高。
原因探究:鼠标滑动的处理,影响了点击操作。
解决方案:20像素以内的滑动,不做处理。
if(m_startPoint.x == point.x && m_startPoint.y == point.y) break;
这行代码,修改为:
if((point.x >= m_startPoint.x-20 && point.x <= m_startPoint.x+20 )
&& (point.y >= m_startPoint.y-20 && point.y <= m_startPoint.y+20)) break;
实际使用中发现,虚表模式编辑表格时,挺啰嗦的。比如选中多行时,这个选中行信息是需要调用方自己维护的。所以我认为虚表只适合列表模式下的数据展示,比如中数据库或者文件中读取行去显示。
上一个版本中,实表模式有点鸡肋,不仅速度慢还非常占用内存。这次改动很大,主要是奔着实表模式下节约内存和提高速度去的,我在原有的基础上做改动,结果推倒重来无数次,忍无可忍只好从白板开始重新写了。
CGrid
List
UI、CGrid
List
HeaderUI、GGrid
List
BodyUI、CGrid
List
RowUI、CGridL
name="thebtn"(用处:代表当前
控件
。)
text="文字"(用处:
控件
上显示的文字。textcolor可以设置它的颜色。disabledtextcolor禁用时的颜色。textpadding文字边距。)
tooltip="提示文字"(用处:
鼠标
放上去提示的文字。)
bgcolor="#FF00FF00"(用处:背景色。bkcolor2与bkc
CGrid
List
UI主要是结合C
List
UI和CGrid
Ctrl
,我的目标是替代CGrid
Ctrl
,所以函数调用等格式尽量兼容CGrid
Ctrl
。
每个单元格都是一个容器
源码下载地址:https://share.weiyun.com/5TSf5mJ
Duilib
list
表头增加
控件
,通过观察发现凡是想把某
控件
Add到某一
控件
(父
控件
)上面,则父
控件
必须由CContainerUI派生,然后我们观察一下C
List
HeaderItem的实现,发现对于继承的接口我们即使修改为CContainerUI下面继承的接口完全没有问题,所以我们试着把C
List
HeaderIt...