Fiddling with the fonts, part 2: Keeping the English font small


We concluded last time that we wanted the custom large font to apply only to the columns containing Chinese characters and leave the original font in place for the English columns. We do this by carrying two fonts around, choosing the appropriate one for each column.

class RootWindow : public Window
{
 ...
private:
 HWND m_hwndLV;
 HWND m_hwndEdit;
 HWND m_hwndLastFocus;
 HFONT m_hfChinese;
 HFONT m_hfNormal;
 int  m_cyEdit;
 ...
}

RootWindow::RootWindow()
 : m_hfChinese(NULL)
 , m_hfNormal(NULL)
{
}

RootWindow::~RootWindow()
{
 if (m_hfChinese) DeleteObject(m_hfChinese);
 if (m_hfNormal) DeleteObject(m_hfNormal);
}

LRESULT RootWindow::OnCreate()
{
 ...
 ListView_SetExtendedListViewStyleEx(m_hwndLV,
                                     LVS_EX_FULLROWSELECT,
                                     LVS_EX_FULLROWSELECT);

 LOGFONT lf;
 if (!GetObject(GetWindowFont(m_hwndLV), sizeof(lf), &lf)) {
  return -1;
 }
 m_hfNormal = CreateFontIndirect(&lf);
 if (!m_hfNormal) return -1;
 lf.lfHeight += lf.lfHeight / 2; // 50% bigger
 m_hfChinese = CreateFontIndirect(&lf);
 if (!m_hfChinese) return -1;
 SetWindowFont(m_hwndLV, m_hfChinese, FALSE);
 ...
}

Before we change the default font for the list view to the Chinese font, we create a copy of the original font (which we rather presumptuously call "normal") for safekeeping. Next, when the list view asks us to customize a column, we select the appropriate font and return the "I also changed the font" code.

LRESULT RootWindow::OnLVCustomDraw(NMLVCUSTOMDRAW* pcd)
{
 ...
 case CDDS_ITEMPREPAINT | CDDS_SUBITEM:
  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);
    }
  }
  // break;
  if (pcd->iSubItem == COL_TRAD || pcd->iSubItem == COL_SIMP) {
    SelectFont(pcd->nmcd.hdc, m_hfChinese);
  } else {
    SelectFont(pcd->nmcd.hdc, m_hfNormal);
  }
  return CDRF_NEWFONT;
 ...
}

There are several important details here.

First, we set the Chinese font as the "overall" font for the list view. It would have been easier for us not to do this; after all, since we explicitly set the font for each column, why does it matter what the default font is? It also would have removed the need to create a copy of the original font. But if you delete the SetWindowFont(m_hwndLV, m_hfChinese); line, the bottoms of the Chinese characters get cut off. The reason is that the list view uses the default font to decide what the line spacing should be. Therefore, the default font for the list view needs to be the largest font we intend to use for any column.

Why does the list view use the default font to decide on the line spacing? Because it's not clairevoyant. That's the only font it has, after all. It doesn't know what font you're going to select in your CDDS_ITEMPREPAINT | CDDS_SUBITEM notification handler. All it has is the font you set with SetWindowFont.

Another important detail is that once we have decided to use different fonts for different columns, we are committed to selecting a font for all columns. The reason for this was discussed when we discussed how to colorize the columns.

Finally, there is the important detail of returning the CDRF_NEWFONT value when we change the font. For performance reasons, the list view assumes you aren't changing the font on a subitem-by-subitem basis (since very few list views do) and it caches many font properties to avoid having to recalculate them all the time. Returning CDRF_NEWFONT indicates that the list view should look at the font you selected and base its computations on that font instead.

Since boldface, italics and underline are font attributes, you can use this "select a custom font" technique to make selected items display as boldface, italics, or underline, in addition to using it to change the font size as we did here.

That's all for this month. Next month will be a rather boring one, adding a status bar to make the Chinese characters even more readable. After that, we'll enhance the dictionary lookup algorithm, which is itself groundwork for dynamic translation, as I may have alluded to in a previous entry.

[Raymond is currently away; this message was pre-recorded.]

Comments (14)
  1. Matt says:

    Who’s Claire Voyant?

  2. AC says:

    Matt: A good question indeed. According to Google, Claire Voyant is:

    Victoria Lloyd ~ vocals & lyrics

    Chris Ross ~ programming

    Ben Fargen ~ guitars

    The word in Webster’s is

    http://www.m-w.com/cgi-bin/dictionary?va=clairvoyant

    From just one Raymond’s post I can improve my Windows API programming skills, a learn new word and discover a new band!

  3. Mihai says:

    In general is quite bad idea to hard-code font sizes/attributes.

    Imagine someone will localize this application into Japanese. The Japanese column with have a smaller font than the Chinese one. BAD!

    Same problem with other attributes (bold, italic, underscore).

    Imagine italic-underscored Japanese, or Arabic.

  4. Kalyan says:

    Raymond,

    Could you comment on ease of porting apps from UNIX to Windows?

    regards,

    Kalyan

  5. Daev says:

    Kalyan:

    Read the "Unix to Windows Migration Guide" on MSDN:

    http://msdn.microsoft.com/library/default.asp?url=/library/en-us/dnucmg/html/UCMGch04.asp

    (I have no idea why someone in the MSDN documentation group thinks that article should be "archived" because it’s not "relevant content." Read it now before it gets even harder to find.)

    My impression is that it’s not hard to port applications so long as they don’t make the mistake of thinking fork, signals, pthreads, shells, or X should ever be used. I’ve been porting math libraries from Unix to Windows for the last few years.

  6. With mingw32, cygwin and similar tools you can easily port applications using fork. There is also a pthread<->native threads wrapper around somewhere (so pthreads are actually very portable). If the app uses GTK or Qt you are lucky on the GUI side (there is an implementation for windows) don’t know for other toolkits. For the rest you’ll have to port most function calls by hand :)

  7. jfoscoding says:

    Same thing applies for TreeView – gotta set the TreeView font to be the largest font, otherwise the nodes will get clipped.

  8. Norman Diamond says:

    This article and several others call a function SetWindowFont(). I tried looking for where this function or method was defined in the scratch program or new scratch program or dictionary program or dialog box example, but couldn’t find it.

    Now in the MSDN page at

    http://msdn.microsoft.com/library/default.asp?url=/library/en-us/shellcc/platform/commctls/propsheet/wizards.asp

    one of the examples (you’ll have to unhide it to see it) also calls SetWindowFont().

    Is SetWindowFont a Win32 API? Is it documented somewhere?

  9. Seth McCarus says:

    Norman, SetWindowFont is a macro in windowsx.h.

  10. Norman Diamond says:

    Tuesday, September 27, 2005 2:21 PM by Seth McCarus

    > Norman, SetWindowFont is a macro in

    > windowsx.h.

    Thank you. I have a feeling of deja vu about that answer, and wonder if I might have asked the exact same question before. Which segues into the answer for my other question:

    >> Is it documented somewhere?

    Sounds like the answer’s no. Well, we know how the owner of this blog will scream and pounce on anyone who uses undocumented functions — especially when he sees someone post programs in blogs calling undocumented functions. Now waiting to see how that bloodbath will turn out ^u^

  11. Since it’s just a bunch of macros, the file is self-documenting. If you want to see what a macro does, you can read it directly. There’s also a copy of the original documentation here http://community.borland.com/article/0,1410,17542,00.html

  12. Norman Diamond says:

    Wednesday, September 28, 2005 4:35 AM by oldnewthing

    > Since it’s just a bunch of macros, the file

    > is self-documenting.

    The file self-documents what this version of the file does until the next Visual Studio service pack or (theoretically possible) Windows Update. It doesn’t document whether users are supposed to allow themselves to depend on it.

    A few months ago I read that a header file in the DDK declared some functions but driver programmers weren’t supposed to call those functions. The header file self-documented itself but didn’t document that it had a mistake in itself.

    More recently I’ve had a couple of disputes with your colleagues because ntddk.h and wdm.h export definitions of TCHAR and a few related identifiers (indirectly from ntdef.h but nonetheless doing it). Your colleagues say that driver programmers should know not to use these identifiers in kernel mode, but these self-documenting header files do not say so, and the writer of some code that I saw obviously had an inadequate level of clairvoyance. The MSDN pages say even less (e.g., which primitive C functions are allowed to be called in kernel mode and which aren’t, near-zero documentation). A self-documenting header file is no substitute for an MSDN page.

    > There’s also a copy of the original

    > documentation here

    > http://community.borland.com/article/0,1410,17542,00.html

    Thank you. I plan to read it a bit more fully on the weekend. Meanwhile, are other Borland documents for 3.1 also suitable for determining what identifiers we should and shouldn’t use on Windows XP? Why don’t Visual Studio and DDKs come with pointers to such relevant documents? And when we do rely on them, will you be happy to do the appcompat work to keep the results running?

  13. In my opinion, removing the windowsx.h documentation from MSDN was a mistake.

  14. Norman Diamond says:

    Thursday, September 29, 2005 10:44 AM by oldnewthing

    > In my opinion, removing the windowsx.h

    > documentation from MSDN was a mistake.

    Oh, I didn’t know it used to be there, and I wonder why you didn’t say so earlier. Now knowing that, it’s nice to reach an agreement … at least partially.

    Hmm, what kind of mistake? If it was a slip of the fingers of some typist, then surely it will only take a few years for MSDN to correct the error. But if it was a deliberate exercise of poor judgement, then we should not expect a correction. If it’s the latter case, then I’m still left thinking that authors of new code aren’t supposed to use it.

Comments are closed.