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);
}
* @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);
}