Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How can I change the default CDialog font for a non-modal dialog?

Tags:

c++

winapi

mfc

It is necessary to switch off the "ClearType" property of the default font for all dialog controls. It is possible to do that for one control by setting

logfont.lfQuality = ANTIALIASED_QUALITY

There are a lot of suggestion how to do the same for modal dialogs (http://neelaakash.wordpress.com/2007/12/31/change-default-dialog-font-of-cdialog/ and others), but that should be done for non-modal dialogs (are instantiated with new and Create(...) methods). I've tried to do that myself:

Override 'Create' method, and modify dialog template:

BOOL CActivationChildDialogLicenseInfo::Create(UINT nIDTemplate, 
                                               CWnd* pParentWnd)
{
  CDialogTemplate dlt;
  int nResult;
  // load dialog template
  if (!dlt.Load(MAKEINTRESOURCE(nIDTemplate))) return -1;
  // set your own font, for example “Arial”, 10 pts.
  dlt.SetFont(L"Arial", 12);
  // get pointer to the modified dialog template
  LPSTR pdata = (LPSTR)GlobalLock(dlt.m_hTemplate);
  // let MFC know that you are using your own template
  m_lpszTemplateName = NULL;
  InitModalIndirect(pdata);
  // display dialog box
  nResult = CActivationChildDialog::Create(nIDTemplate, pParentWnd);
  // unlock memory object
  GlobalUnlock(dlt.m_hTemplate);

  return nResult ;
}

Seems like this method do nothing (it is called, I've checked that with putting break-point inside). I've tried to call

nResult = CActivationChildDialog::Create(NULL, pParentWnd);

...but got a lot of ASSERTs.

I've also tried to override the 'OnSetFont' method:

void CActivationChildDialogLicenseInfo::OnSetFont(CFont *pFont)
{
    CActivationChildDialog::OnSetFont(pFont);

    LOGFONT logfont;
    pFont->GetLogFont(&logfont);
    LOGFONT logfont2=logfont;
    pFont->DeleteObject();

    logfont2.lfItalic = true;
    logfont2.lfQuality = ANTIALIASED_QUALITY;
    pFont->CreateFontIndirect(&logfont2);
}

That causes ASSERT during run-time and resulted in a VERY big font being used (lost default font settings, doesn't accept new specified settings)... I don't know why.

Please advise, how can I change a default dialog font that will be "inherited" by all dialog controls?

Thank you very much.

like image 399
Budda Avatar asked Feb 26 '23 15:02

Budda


1 Answers

First off: the simple, reliable way to do this is to create the dialog and then send WM_SETFONT (or call SetFont()) to the dialog and each control in it. I'll show you how to do that below, but first, here's why the two strategies you've already tried didn't (and can't) work:

Modifying the dialog template

First off, you should be calling CDialog::CreateIndirect() if you wish to use a dialog template that you've already loaded.

But don't bother. The dialog template contains only the face name and point size - it does not allow you to specify other LOGFONT values such as lfQuality. If it did, you could simply specify that in your resource definition and avoid writing any runtime code at all!

Intercepting WM_SETFONT

In theory, you could make this work. But it's not practical. Your code has several problems: first off, you'd have to intercept this message for every child control in order for it to do anything useful: the dialog itself probably doesn't render any text. But worse, you're passing the original font to the base class (which hands it to the default window procedure, which stores it internally for later use) and then immediately destroying it - this means the dialog (and everything else using that font, including all of the child controls) will be trying to draw text using a bogus font, and reverting to the default font as a result. Finally, you're creating a new font attached to a temporary object (pFont) created and destroyed by MFC - internally, the CFont object you're working with will be detached from the font handle and destroyed, leaking a handle to a font object that nothing uses.

Leaky abstractions: a note on HFONT and CFont

HFONT is a handle type that Windows uses to represent a font object. Like most of GDI, there are specific functions for creating fonts, and the general-purpose DeleteObject() function for destroying them.

CFont is a light-weight wrapper class for HFONTs. A CFont instance can be attached and detached from an existing HFONT, or used to create a new one. If a CFont instance is still attached to a HFONT when its deconstructor executes, it will call DeleteObject() to destroy the underlying object. Internally, MFC utilizes temporary CFont instances that are attached and detached from HFONTs when calling various message handlers (such as OnSetFont). It's worth remembering that internally, Windows knows nothing about CFont, and a single HFONT may belong to 0 or more CFont instances at any given point in time.

A note on fonts and WM_SETFONT

When you create a new font - whether or not it is wrapped in a CFont object - you are the owner of that font, and it is your responsibility to destroy it once you are finished using it. Passing it to WM_SETFONT (CWnd::SetFont()) doesn't change ownership! This is actually quite useful, as it allows you to pass the same font to multiple windows without worrying about which one will destroy it - you're still the owner, and so you can (and must) destroy it yourself (once there are no windows still using it).

Finally - how to quickly create and set a font on a dialog and all its children

So you should now have enough background to understand the necessary steps:

  1. Create the dialog
  2. Create the desired font
  3. Pass the font to the dialog and its children (by either sending WM_SETFONT messages, or by calling CWnd::SetFont... which itself send a WM_SETFONT message).
  4. When the dialog is destroyed, also destroy your font.

Example

// define this as a class member - class destructor then handles step four!
CFont m_nonCleartypeFont; 

BOOL CActivationChildDialogLicenseInfo::Create(UINT nIDTemplate, 
                                               CWnd* pParentWnd)
{
  // step one: create dialog normally
  BOOL nResult = CActivationChildDialog::Create(nIDTemplate, pParentWnd);

  // step two: create custom font
  // relying on destructor to destroy font once we're done with it
  // so be careful to only create it once!
  if ( NULL == m_nonCleartypeFont.m_hObject )
  {
    CFont* pOriginalFont = GetFont(); // use template font as... template!
    
    // pull information from original font  
    LOGFONT logfont;
    pOriginalFont->GetLogFont(&logfont);

    // make font adjustments: 
    // specify italics
    logfont.lfItalic = true;
    // and non-cleartype antialiasing
    logfont.lfQuality = ANTIALIASED_QUALITY;
    
    // create our font based on adjusted information
    m_nonCleartypeFont.CreateFontIndirect(&logfont);
  } 
  
  // step three: set our custom font on the dialog and all children
  SetFont(&m_nonCleartypeFont, FALSE);
  // Send message to quickly set this font for all children. 
  // See documentation for SendMessageToDescendants()
  // - this is actually the example given!
  SendMessageToDescendants(WM_SETFONT,
      (WPARAM)m_nonCleartypeFont.m_hObject,
      MAKELONG(FALSE, 0), 
      FALSE);
  
  return nResult;
}
like image 118
Shog9 Avatar answered Apr 27 '23 21:04

Shog9