ListCtrl Checkbox多选问题
需求与目标:
标准的Listctrl是支持多选的,但这个多选只是把那些选中行变成高亮而已,我想要的效果是像svn提交代码的那个listctrl那样:
1.可以用鼠标框选,可以按ctrl或shift进行组合选择.这是listctrl本身支持的,只要设置对了风格就没问题,这个不是重点,跳过 :)
2.在选择了多行之后,点击鼠标或按下空格,能够把所有选中行都选上(呵呵,这就是我想要的)
??没用过这个功能??马上装一个svn,BitComet也可以....
实现:
其实就是把所有的选中行的check box状态都设为true-->SetCheck就可以啦,当然发消息也可以:发LVM_SETITEMSTATE消息对LVIS_STATEIMAGEMASK进行处理就行。其实都一样。
问题:是你会发现,鼠标点击和空格本身我们不用处理listctrl都会自己处理,并改变当前选中行的check box状态,所以我们自己处理的时候就要对当前选中行直接“特殊处理”。否则的话:你刚刚设为TRUE的状态,马上就被listctrl自己处理的消息给改回去了。。。
解决方法:
1.屏蔽listctrl自己的消息,全部我们自己处理(空格消息这么处理会很方便,鼠标消息因为要得到点击的是哪一行,这么干会辛苦点,能偷懒就偷懒嘛~~)
2.还用listctrl自己的消息,但是我们处理完之后要多做一次状态设置,把被系统改的状态再次改回来(BOOL改了3次和改了1次是一样的嘛+_+)
部分代码:
/**//**
* @Function : CDebeheListCtrl::OnNMClick
* @brief : 鼠标点击消息
* @param : pNMHDR
* @param : pResult
* @return :
* @remark :
*/
void CDebeheListCtrl::OnNMClick(NMHDR* pNMHDR, LRESULT* pResult)
...{
// 按下shift和ctrl时不马上选中
NMITEMACTIVATE* pNMItemActivate = (NMITEMACTIVATE*)pNMHDR;
if (pNMItemActivate->iItem == -1
||pNMItemActivate->uKeyFlags == LVKF_SHIFT
|| pNMItemActivate->uKeyFlags == LVKF_CONTROL)
...{
TRACE0("OnNMClick no use ");
}
else if(pNMItemActivate->ptAction.x > 0
&& pNMItemActivate->ptAction.x < LISTCTRL_FIRST_COL_WIDTH)
...{
if (IsInSelected(pNMItemActivate->iItem))
...{
CheckAllSelectedItems(pNMItemActivate->iItem);
this->SetCheck(pNMItemActivate->iItem, !GetCheck(pNMItemActivate->iItem));
}
}
}
/**//**
* @Function : CheckAllSelectedItems
* @brief : 改变所有选中行的check box状态
* @param : nCurItem 当前选中的行
* @return :
* @remark : 当前选中行nCurItem是-1的时候,自己计算check box的状态
* nCurItem不为-1是,就直接用nCurItem行的状态取反来设置
* 大家(所有选中行)的状态
*/
BOOL CDebeheListCtrl::CheckAllSelectedItems(int nCurItem)
...{
BOOL bCheck = FALSE;
if (nCurItem == -1)
bCheck = CalcCheckState();
else
bCheck = !GetCheck(nCurItem);
// 遍历选中行,设置状态
INT nItemIndex = -1;
nItemIndex = this->GetNextItem(-1, LVNI_SELECTED);
if(nItemIndex != -1)
...{
SetCheck(nItemIndex, bCheck);
}
while(TRUE)
...{
nItemIndex = this->GetNextItem(nItemIndex, LVNI_SELECTED);
if(nItemIndex != -1)
...{
SetCheck(nItemIndex, bCheck);
}
else
break;
}
return TRUE;
}
/**//**
* @Function : IsInSelected
* @brief : 判断nItem行,是否在选中行中
* @return : BOOL
* @param : int nItem 要判断的行
* @remark :
*/
BOOL CDebeheListCtrl::IsInSelected(int nItem)
...{
// 得到当前选中行的,另一种方式,Copy from msdn
BOOL bRet = FALSE;
POSITION pos = this->GetFirstSelectedItemPosition();
if (pos == NULL)
TRACE0("No items were selected! ");
else
...{
while (pos)
...{
int nNextItem = this->GetNextSelectedItem(pos);
if (nItem == nNextItem)
...{
bRet = TRUE;
goto Exit0;
}
}
}
Exit0:
return bRet;
}
/**//**
* @Function : CalcCheckState
* @brief : 如果当前选中行是-1,计算一下对选中行要设置的状态
* @return : BOOL
* @remark : 除非所有的选中行状态都是TRUE,否则返回FALSE
*/
BOOL CDebeheListCtrl::CalcCheckState()
...{
BOOL bRet = FALSE;
BOOL bHavenChecked = FALSE;
INT nItemIndex = -1;
nItemIndex = this->GetNextItem(-1, LVNI_SELECTED);
if(nItemIndex != -1)
...{
if(GetCheck(nItemIndex))
...{
bHavenChecked = TRUE;
goto Exit0;
}
}
while(TRUE)
...{
nItemIndex = this->GetNextItem(nItemIndex, LVNI_SELECTED);
if(nItemIndex != -1)
...{
if (GetCheck(nItemIndex))
...{
bHavenChecked = TRUE;
goto Exit0;
}
}
else
break;
}
Exit0:
bRet = !bHavenChecked;
return bRet;
}
BOOL CDebeheListCtrl::PreTranslateMessage(MSG* pMsg)
...{
// 之所以处理这三个消息是我试出来的 +_+
// 不把他们截下来的话,listctrl默认的空格处理,
// 会让我们的逻辑变得复杂
if (pMsg->message == WM_CHAR
|| pMsg->message == WM_KEYDOWN
|| pMsg->message == WM_KEYUP)
...{
if (pMsg->message == WM_KEYDOWN && pMsg->wParam == VK_SPACE)
...{
int nSelected = this->GetSelectedCount();
if (nSelected > 0)
...{
int item = this->GetSelectionMark();
if (IsInSelected(item))
...{
CheckAllSelectedItems(item);
}
else
...{
CheckAllSelectedItems(-1);
}
}
}
// 把双击消息屏蔽,不然点的太快会感觉像是bug
if (pMsg->message == WM_LBUTTONDBLCLK)
return TRUE;
return TRUE; // 切记return TRUE,不然默认的空格处理就会起作用
}
return CListCtrl::PreTranslateMessage(pMsg);
}