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。这些是您必须执行的步骤:

  1. Intercept the listview draw routine just before it is about to draw a highlighted row (item).
  2. Turn off the row highlight.
  3. Set the row colors to whatever you want.
  4. Let the listview draw the row.
  5. Intercept the listview draw routine after it has drawn the row (post-draw item).
  6. 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
    }
}