Converting from traditional to simplified Chinese, part 3: Highlighting differences

One of the things that is interesting to me as a student of the Chinese languages is to recognize where the traditional and simplified Chinese scripts differ. Since this is my program, I'm going to hard-code the color for simplified Chinese script: maroon.

To accomplish the highlighting, we take advantage of listview's custom-draw feature. Custom-draw allows you to make minor changes to the way items are displayed on the screen. It's a middle ground between having listview do all the work (via default drawing behavior) and having the program do all the work (via owner-draw).

The custom-draw cycle for shell common controls consists of series of NM_CUSTOMDRAW notifications, starting with the most general and getting more specific. The reason for the break-down is multi-fold. First, it allows the listview control to short-circuit a portion of custom-draw behavior if the parent window does not indicate that it wishes to customize a particular behavior. This reduces message traffic and improves performance when large numbers of items are being drawn. Second, it allows the parent window to target its customizations to the drawing stages it is interested in.

Listviews are peculiar among the shell common controls in that its items sometimes (but not always) have sub-items. This complicates the drawing process since it requires listview to accomodate both styles: large icon view does not use sub-items, but report view does. To address this, the CDDS_ITEMPREPAINT stage is entered when an item is about to paint, and any changes made by the parent window are considered to be effective for the entire item. If you want to make changes on a per-subitem basis, you have to respond to CDDS_ITEMPREPAINT | CDDS_SUBITEM and set your properties (or reset them if you want to return to the default) for that sub-item.

With those preliminary remarks settled, we can dive in.

class RootWindow : public Window
 HWND m_hwndLV;
 COLORREF m_clrTextNormal;
 Dictionary m_dict;

We declare our listview custom-draw handler as well as the member variable in which we remember the normal text color so that we can reset it for columns we do not intend to colorize.

LRESULT RootWindow::OnNotify(NMHDR *pnm)
 switch (pnm->code) {
  if (pnm->hwndFrom == m_hwndLV) {
   return OnLVCustomDraw(CONTAINING_RECORD(
                         CONTAINING_RECORD(pnm, NMCUSTOMDRAW, hdr),
                                                NMLVCUSTOMDRAW, nmcd));
 return 0;

If we receive a NM_CUSTOMDRAW notification from the listview control, we call our new handler. The multiple calls to the CONTAINING_RECORD macro are necessary because the NMHDR structure is nestled two levels deep inside the NMLVCUSTOMDRAW structure.

 switch (pcd->nmcd.dwDrawStage) {
  m_clrTextNormal = pcd->clrText;
  pcd->clrText = m_clrTextNormal;
  if (pcd->iSubItem == COL_SIMP &&
    pcd->nmcd.dwItemSpec < (DWORD)Length()) {
    const DictionaryEntry& de = Item(pcd->nmcd.dwItemSpec);
    if (de.m_pszSimp) {
      pcd->clrText = RGB(0x80, 0x00, 0x00);

During the CDDS_PREPAINT stage, we indicate our desire to receive CDDS_ITEMPREPAINT notifications. During the CDDS_ITEMPREPAINT stage, we save the normal text color and indicate that we want to receive sub-item notifications. It is in the sub-item notification CDDS_ITEMPREPAINT | CDDS_SUBITEM that the real work happens.

First, we reset the color to the default on the assumption that we will not need to colorize this column. But if the column is the simplified Chinese column, if the item number is valid, and if the simplified Chinese is different from the traditional Chinese, then we set the text color to maroon.

That's enough with the Chinese/English dictionary for now. All this time, and we don't even have search capability yet! We'll work on that next month.

Comments (5)
  1. Seth McCarus says:

    Since this will probably rise to the top of Google’s results concerning list-view custom draw, let me add that on a dialog, you can’t just return CDRF_NOTIFYITEMDRAW, due to the way dialog procs work (as Raymond has discussed elsewhere).

    Instead, set the return value in the dialog using SetWindowLongPtr and DWL_MSGRESULT, as in the following snippet:

    switch (lvcd->nmcd.dwDrawStage)




    return TRUE;



  2. binaryc says:

    Why use CONTAINING_RECORD rather than a cast?

  3. Bryan says:

    The NMHDR structure’s base address is the same as the containing NMLVCUSTOMDRAW structure’s base address (NMHDR is the first member of the first member of the NMLVCUSTOMDRAW), so you can cast it directly, in this case.

    But only in this case, and not if Microsoft changes one of the structures in the future (though that would break every existing program that uses them, so it likely won’t happen). Still, IMO it’s a bit cleaner.

  4. Mike Dunn says:

    NMHDR and the related NMxxx structs were designed to be castable, no? Sort of like a simple version of inheritance that’s usable from C?

    Shameless plug for my own list view custom draw article:

  5. Changing the font on a column-by-column basis.

Comments are closed.