CMFCListCtrl 强制选择的项目有红色
CMFCListCtrl force selected item to have red color
我有自己的 CMFCListCtrl 派生 class,我在其中实现了
virtual COLORREF OnGetCellTextColor(int nRow, int nColum)
{
CMyClass* pMyClass = (CMyClass*)GetItemData(nRow);
if (pMyClass && pMyClass->m_bDeleted)
return RGB(255, 0, 0);
return __super::OnGetCellTextColor(nRow, nColum);
}
用于将已删除条目标记为红色的功能。除非选择了一个项目,否则这会起作用。
我转到 void CMFCListCtrl::OnCustomDraw(NMHDR* pNMHDR, LRESULT* pResult)
函数,并在
行放置了条件为 iRow==selected item
的断点
lplvcd->clrText = OnGetCellTextColor(iRow, iColumn);
执行了它,然后我为 &lplvcd->clrText
创建了一个新的数据断点。
函数命中数据断点
comctl32.dll!SHThemeComputeTextColors()
,如调用堆栈图像所示:
,这显然是覆盖了变量值。
当我在 Internet 上搜索 SHThemeComputeTextColors
但没有出现任何内容时,有人可以帮助我将所选项目的文本强制设置为红色吗?
更改突出显示项目的颜色和字体比看起来要棘手,因为突出显示所选项目是一个系统范围的问题。其实这是我拖了很久的事情,今天终于解决了...
至少有两种方法可以更改列表控件的外观:所有者绘制(您必须自己完成所有绘制)和自定义绘制(系统会在即将执行列表的某些步骤时告诉您)绘图并允许您更改颜色、字体等)。这个答案是关于自定义绘制的。 This article covers the basics of using Custom Draw with CListCtrl.
解释了如何更改 CListCtrl(不是 CMFCListCtrl,我们很快就会讲到)中的突出显示颜色in this other article。这些是您必须执行的步骤:
- Intercept the listview draw routine just before it is about to draw a highlighted row (item).
- Turn off the row highlight.
- Set the row colors to whatever you want.
- Let the listview draw the row.
- Intercept the listview draw routine after it has drawn the row (post-draw item).
- Turn this row's highlighting back on.
所以,当listview要绘制高亮行时,你必须告诉系统该行没有高亮,所以它不使用系统颜色来绘制它,告诉使用什么颜色,并设置该项目再次突出显示,因此选择照常进行。
这是那篇文章的代码:
COLORREF g_MyClrFgHi; // My foreground hilite color
COLORREF g_MyClrBgHi; // My background hilite color
HWND g_hListView; // Window handle of listview control
void EnableHighlighting(HWND hWnd, int row, bool bHighlight)
{
ListView_SetItemState(hWnd, row, bHighlight? 0xff: 0, LVIS_SELECTED);
}
bool IsRowSelected(HWND hWnd, int row)
{
return ListView_GetItemState(hWnd, row, LVIS_SELECTED) != 0;
}
bool IsRowHighlighted(HWND hWnd, int row)
{
// We check if row is selected.
// We also check if window has focus. This was because the original listview
// control I created did not have style LVS_SHOWSELALWAYS. So if the listview
// does not have focus, then there is no highlighting.
return IsRowSelected(hWnd, row) && (::GetFocus(hWnd) == hWnd);
}
BOOL OnNotify(WPARAM wParam, LPARAM lParam, LRESULT* pResult)
{
static bool bIsHighlighted = false;
*pResult = 0;
NMHDR *p = (NMHDR *)lParam;
switch (p->code)
{
...
case NM_CUSTOMDRAW:
NMLVCUSTOMDRAW *lvcd = (NMLVCUSTOMDRAW *)p;
NMCUSTOMDRAW &nmcd = lvcd->nmcd;
switch (nmcd.dwDrawStage)
{
case CDDS_PREPAINT:
// We want item prepaint notifications, so...
*pResult = CDRF_NOTIFYITEMDRAW;
break;
case CDDS_ITEMPREPAINT:
{
int iRow = (int)nmcd.dwItemSpec;
bHighlighted = IsRowHighlighted(g_hListView, iRow);
if (bHighlighted)
{
lvcd->clrText = g_MyClrFgHi; // Use my foreground hilite color
lvcd->clrTextBk = g_MyClrBgHi; // Use my background hilite color
// Turn off listview highlight otherwise it uses the system colors!
EnableHighlighting(g_hListView, iRow, false);
}
// We want item post-paint notifications, so...
*pResult = CDRF_DODEFAULT | CDRF_NOTIFYPOSTPAINT;
break;
}
case CDDS_ITEMPOSTPAINT:
{
if (bHighlighted)
{
int iRow = (int)nmcd.dwItemSpec;
// Turn listview control's highlighting back on now that we have
// drawn the row in the colors we want.
EnableHighlighting(g_hListView, iRow, true);
}
*pResult = CDRF_DODEFAULT;
break;
}
default:
*pResult = CDRF_DODEFAULT;
break;
}
break;
...
}
}
这与 CListCtrl 一起工作正常,但您询问的是 CMFCListCtrl。问题是 CMFCListCtrl 已经要求收到有关 NM_CUSTOMDRAW 通知的通知(如您所见,那是它调用 OnGetCellTextColor
函数的时间)。如果您在自己的 CMFCListCtrl 派生的 class 中为此创建一个处理程序,这些通知将不会到达 CMFCListCtrl 并且您将失去该功能(它可能很好,具体取决于您的需要)。
所以这是我所做的:我在我的列表控件中创建了一个 NM_CUSTOMDRAW 处理程序,如果我正在处理突出显示的行,我会更改颜色,否则,我调用CMFCListCtrl::OnNMCustomDraw()。正如您将看到的,我只使用了正常的高亮颜色;那是因为即使控件没有焦点,我也只想查看所选项目:
void CMyListCtrl::OnNMCustomdraw(NMHDR* pNMHDR, LRESULT* pResult)
{
bool callParent = true;
static bool bHighlighted = false;
LPNMLVCUSTOMDRAW lpLVCustomDraw = reinterpret_cast<LPNMLVCUSTOMDRAW>(pNMHDR);
NMCUSTOMDRAW nmcd = lpLVCustomDraw->nmcd;
*pResult = CDRF_DODEFAULT;
switch (lpLVCustomDraw->nmcd.dwDrawStage)
{
case CDDS_PREPAINT:
*pResult = CDRF_NOTIFYITEMDRAW;
break;
case CDDS_ITEMPREPAINT:
{
int row = nmcd.dwItemSpec;
bHighlighted = IsRowHighlighted(row);
if (bHighlighted)
{
lpLVCustomDraw->clrText = GetSysColor(COLOR_HIGHLIGHTTEXT);
lpLVCustomDraw->clrTextBk = GetSysColor(COLOR_HIGHLIGHT);
EnableHighlighting(row, false);
*pResult = CDRF_DODEFAULT | CDRF_NOTIFYPOSTPAINT;
callParent = false;
}
}
break;
case CDDS_ITEMPOSTPAINT:
if (bHighlighted)
{
int row = nmcd.dwItemSpec;
EnableHighlighting(row, true);
callParent = false;
}
*pResult = CDRF_DODEFAULT;
break;
default:
break;
}
if (callParent)
{
__super ::OnCustomDraw(pNMHDR, pResult);
}
}
bool CMyListCtrl::IsRowHighlighted(int row)
{
bool selected = GetItemState(row, LVIS_SELECTED) != 0;
return selected;
}
void CMyListCtrl::EnableHighlighting(int row, bool enable)
{
SetItemState(row, enable ? 0xff : 0, LVIS_SELECTED);
}
我还没有彻底测试它,但它似乎有效。
更新:
有点小问题。当你调用 EnableHigilighting() 时,对话框会得到 LVN_ITEMCHANGED 通知,它可以触发重绘,这将触发 LVN_ITEMCHANGED 通知...所以如果你正在收听 LVN_ITEMCHANGED 通知,你可能需要做这样的事情:
void CMyListCtrl::EnableHighlighting(int row, bool enable)
{
m_internalStateChange = true;
SetItemState(row, enable ? 0xff : 0, LVIS_SELECTED);
m_internalStateChange = false;
}
void CWhateverDialog::OnLvnItemchangedListaEjesPane(NMHDR *pNMHDR, LRESULT *pResult)
{
LPNMLISTVIEW pNMLV = reinterpret_cast<LPNMLISTVIEW>(pNMHDR);
if (!c_List.InternalStateChange() && /* other conditions */)
{
// Respond to state changes
}
}
我有自己的 CMFCListCtrl 派生 class,我在其中实现了
virtual COLORREF OnGetCellTextColor(int nRow, int nColum)
{
CMyClass* pMyClass = (CMyClass*)GetItemData(nRow);
if (pMyClass && pMyClass->m_bDeleted)
return RGB(255, 0, 0);
return __super::OnGetCellTextColor(nRow, nColum);
}
用于将已删除条目标记为红色的功能。除非选择了一个项目,否则这会起作用。
我转到 void CMFCListCtrl::OnCustomDraw(NMHDR* pNMHDR, LRESULT* pResult)
函数,并在
iRow==selected item
的断点
lplvcd->clrText = OnGetCellTextColor(iRow, iColumn);
执行了它,然后我为 &lplvcd->clrText
创建了一个新的数据断点。
函数命中数据断点
comctl32.dll!SHThemeComputeTextColors()
,如调用堆栈图像所示:
,这显然是覆盖了变量值。
当我在 Internet 上搜索 SHThemeComputeTextColors
但没有出现任何内容时,有人可以帮助我将所选项目的文本强制设置为红色吗?
更改突出显示项目的颜色和字体比看起来要棘手,因为突出显示所选项目是一个系统范围的问题。其实这是我拖了很久的事情,今天终于解决了...
至少有两种方法可以更改列表控件的外观:所有者绘制(您必须自己完成所有绘制)和自定义绘制(系统会在即将执行列表的某些步骤时告诉您)绘图并允许您更改颜色、字体等)。这个答案是关于自定义绘制的。 This article covers the basics of using Custom Draw with CListCtrl.
解释了如何更改 CListCtrl(不是 CMFCListCtrl,我们很快就会讲到)中的突出显示颜色in this other article。这些是您必须执行的步骤:
- Intercept the listview draw routine just before it is about to draw a highlighted row (item).
- Turn off the row highlight.
- Set the row colors to whatever you want.
- Let the listview draw the row.
- Intercept the listview draw routine after it has drawn the row (post-draw item).
- Turn this row's highlighting back on.
所以,当listview要绘制高亮行时,你必须告诉系统该行没有高亮,所以它不使用系统颜色来绘制它,告诉使用什么颜色,并设置该项目再次突出显示,因此选择照常进行。
这是那篇文章的代码:
COLORREF g_MyClrFgHi; // My foreground hilite color
COLORREF g_MyClrBgHi; // My background hilite color
HWND g_hListView; // Window handle of listview control
void EnableHighlighting(HWND hWnd, int row, bool bHighlight)
{
ListView_SetItemState(hWnd, row, bHighlight? 0xff: 0, LVIS_SELECTED);
}
bool IsRowSelected(HWND hWnd, int row)
{
return ListView_GetItemState(hWnd, row, LVIS_SELECTED) != 0;
}
bool IsRowHighlighted(HWND hWnd, int row)
{
// We check if row is selected.
// We also check if window has focus. This was because the original listview
// control I created did not have style LVS_SHOWSELALWAYS. So if the listview
// does not have focus, then there is no highlighting.
return IsRowSelected(hWnd, row) && (::GetFocus(hWnd) == hWnd);
}
BOOL OnNotify(WPARAM wParam, LPARAM lParam, LRESULT* pResult)
{
static bool bIsHighlighted = false;
*pResult = 0;
NMHDR *p = (NMHDR *)lParam;
switch (p->code)
{
...
case NM_CUSTOMDRAW:
NMLVCUSTOMDRAW *lvcd = (NMLVCUSTOMDRAW *)p;
NMCUSTOMDRAW &nmcd = lvcd->nmcd;
switch (nmcd.dwDrawStage)
{
case CDDS_PREPAINT:
// We want item prepaint notifications, so...
*pResult = CDRF_NOTIFYITEMDRAW;
break;
case CDDS_ITEMPREPAINT:
{
int iRow = (int)nmcd.dwItemSpec;
bHighlighted = IsRowHighlighted(g_hListView, iRow);
if (bHighlighted)
{
lvcd->clrText = g_MyClrFgHi; // Use my foreground hilite color
lvcd->clrTextBk = g_MyClrBgHi; // Use my background hilite color
// Turn off listview highlight otherwise it uses the system colors!
EnableHighlighting(g_hListView, iRow, false);
}
// We want item post-paint notifications, so...
*pResult = CDRF_DODEFAULT | CDRF_NOTIFYPOSTPAINT;
break;
}
case CDDS_ITEMPOSTPAINT:
{
if (bHighlighted)
{
int iRow = (int)nmcd.dwItemSpec;
// Turn listview control's highlighting back on now that we have
// drawn the row in the colors we want.
EnableHighlighting(g_hListView, iRow, true);
}
*pResult = CDRF_DODEFAULT;
break;
}
default:
*pResult = CDRF_DODEFAULT;
break;
}
break;
...
}
}
这与 CListCtrl 一起工作正常,但您询问的是 CMFCListCtrl。问题是 CMFCListCtrl 已经要求收到有关 NM_CUSTOMDRAW 通知的通知(如您所见,那是它调用 OnGetCellTextColor
函数的时间)。如果您在自己的 CMFCListCtrl 派生的 class 中为此创建一个处理程序,这些通知将不会到达 CMFCListCtrl 并且您将失去该功能(它可能很好,具体取决于您的需要)。
所以这是我所做的:我在我的列表控件中创建了一个 NM_CUSTOMDRAW 处理程序,如果我正在处理突出显示的行,我会更改颜色,否则,我调用CMFCListCtrl::OnNMCustomDraw()。正如您将看到的,我只使用了正常的高亮颜色;那是因为即使控件没有焦点,我也只想查看所选项目:
void CMyListCtrl::OnNMCustomdraw(NMHDR* pNMHDR, LRESULT* pResult)
{
bool callParent = true;
static bool bHighlighted = false;
LPNMLVCUSTOMDRAW lpLVCustomDraw = reinterpret_cast<LPNMLVCUSTOMDRAW>(pNMHDR);
NMCUSTOMDRAW nmcd = lpLVCustomDraw->nmcd;
*pResult = CDRF_DODEFAULT;
switch (lpLVCustomDraw->nmcd.dwDrawStage)
{
case CDDS_PREPAINT:
*pResult = CDRF_NOTIFYITEMDRAW;
break;
case CDDS_ITEMPREPAINT:
{
int row = nmcd.dwItemSpec;
bHighlighted = IsRowHighlighted(row);
if (bHighlighted)
{
lpLVCustomDraw->clrText = GetSysColor(COLOR_HIGHLIGHTTEXT);
lpLVCustomDraw->clrTextBk = GetSysColor(COLOR_HIGHLIGHT);
EnableHighlighting(row, false);
*pResult = CDRF_DODEFAULT | CDRF_NOTIFYPOSTPAINT;
callParent = false;
}
}
break;
case CDDS_ITEMPOSTPAINT:
if (bHighlighted)
{
int row = nmcd.dwItemSpec;
EnableHighlighting(row, true);
callParent = false;
}
*pResult = CDRF_DODEFAULT;
break;
default:
break;
}
if (callParent)
{
__super ::OnCustomDraw(pNMHDR, pResult);
}
}
bool CMyListCtrl::IsRowHighlighted(int row)
{
bool selected = GetItemState(row, LVIS_SELECTED) != 0;
return selected;
}
void CMyListCtrl::EnableHighlighting(int row, bool enable)
{
SetItemState(row, enable ? 0xff : 0, LVIS_SELECTED);
}
我还没有彻底测试它,但它似乎有效。
更新:
有点小问题。当你调用 EnableHigilighting() 时,对话框会得到 LVN_ITEMCHANGED 通知,它可以触发重绘,这将触发 LVN_ITEMCHANGED 通知...所以如果你正在收听 LVN_ITEMCHANGED 通知,你可能需要做这样的事情:
void CMyListCtrl::EnableHighlighting(int row, bool enable)
{
m_internalStateChange = true;
SetItemState(row, enable ? 0xff : 0, LVIS_SELECTED);
m_internalStateChange = false;
}
void CWhateverDialog::OnLvnItemchangedListaEjesPane(NMHDR *pNMHDR, LRESULT *pResult)
{
LPNMLISTVIEW pNMLV = reinterpret_cast<LPNMLISTVIEW>(pNMHDR);
if (!c_List.InternalStateChange() && /* other conditions */)
{
// Respond to state changes
}
}