
Content
First of all, I beg your pardon for any errors in my English and in the content of this article (it's my first submission).
For a personal project (a PropertyList control), I needed a ListBox which could display and allow selection of fonts. After reading several articles on this subject, I decided to write my own control with the following main behaviors:
- performance during the detection of font type (when I started this work, I had a Pentium 333 Mhz!),
- can display the font names as standard text or using the font itself,
- can display the tooltip (tracking the mouse pointer or the selected items) using the sample text or facename,
- having MRU sublist, shared or not by multiple
CFontListBox instances in the application,
- no dependencies on a bitmap or an
ImageList resource file in the project,
- can be used as a dialog subclassing control or created by hand.
To manage a MRU sublist, shared or not, CFontListBox uses the services offered by an instance of the CFontListManager. Only one instance of CFontListManager is created automatically during the application initialization. This class is responsible for loading the font list, managing the MRU list (private to a CFontListBox instance, shared between two or more controls, or global to the application) through the CFontDescriptor and CFontMruList objects and its various collections.
This control gets the bitmap used to display the list from a bitmap resource of COMDLG32.DLL, so there is no specific resource to be included in your project.
To allow the loading process of the font list, this control does not use the way used by other controls. I tried to distinguish between the font types (TrueType, OpenType and so on). It uses the flags given by the ::EnumFontFamiliesEx API to the callback function (see EnumFontProc and CFontListManager::FillFontList() functions in FontListManager.cpp).
The re-creation of the base ListBox window is needed to allow modifications of the control after its creation to follow the modifications of the CFontListBox style flags.
The persistence of the MRU list needs two functions called in the CWinApp::InitInstance and CWinApp::ExitInstance methods. This is due to the fact that when an instance of CFontListManager is created, the CWinApp::SetCurrentHandles() is not called (this function of the MFC library is responsible for the initialization of the data members of CWinApp object, like m_pszRegistryKey and m_pszProfileName). Persistence acts only on the global MRU list and the custom named ones, if these lists are marked to do so.
CFontListManager has a feature that allows it to react to the system-wide broadcasted message WM_FONTCHANGE. This feature needs a top-level window to receive the message which is created the first time a CFontListBox is created (which can only occur after the CWinApp object and other frame or dialog windows are initialized).
All the classes going with the CFontListBox are located in a static library (FontLBLibxx.lib). The library project directory tree has a special structure because I needed (for other projects) a unique place for the include header files and the lib files.
Note: If you have a ListBox without the style flag LBS_NOINTEGRALHEIGHT set, and if you change the height of the items multiple times (for example, adding, and then removing the MRU list), you will see that the ListBox shrinks: it's due to the design!
FNTLB_GRAPHIC: The list items are displayed using the corresponding font.
FNTLB_ITEMHEIGHTEXPANDED: The spacing between is the same as a ComboBox ListBox.
FNTLB_NOTYPEIMAGE: No font image type is displayed before the font facename.
FNTLB_MANUAL: Not initialized font ListBox (discussed below).
They are set or modified via the SetFlags or the ModifyFlags functions or with the constructor CFontListBox(DWORD dwStyleFlags, LPCTSTR lpszMruListName=NULL).
When the FNTLB_GRAPHIC flag is set and if the font is SYMBOL, then the item text also contains the facename displayed with the GUI system font (like in the Office 2003 font ComboBox).
Note that not all the symbol fonts are marked SYMBOL, as you can see it in Office 2003 font ComboBox or in the demo program.
The FNTLB_ITEMHEIGHTEXPANDED flag gives the same item spacing as in a ComboBox. If it is not set and there is no MRU list attached, the spacing will be the same as in a Win32 FontDialog.
If the list style flag FNTLB_MANUAL is set, then the ListBox is not initialized with the list of system font resources. This type of CFontListBox is intended to receive and display a list of fonts selected through other means (see in the demo). All the MRU flags are automatically disabled for this type of windows.
The CFontListBox style flags affecting the tooltip window are the following:
FNTLB_HAS_TOOLTIPS: The control has tooltip.
FNTLB_TOOLTIPTEXT_FONT: The tooltip text is the font facename.
FNTLB_TOOLTIPTEXT_SAMPLE: The tooltip text is a sample text (default).
FNTLB_TRACKING_TOOLTIP: The tooltip follows the mouse pointer and has a display text corresponding to the font item under it.
FNTLB_TOOLTIPSELECTED: The tooltip also displays the list of selected fonts.
They are set or modified via the SetFlags or the ModifyFlags functions, or with the constructor CFontListBox(DWORD dwStyleFlags, LPCTSTR lpszMruListName=NULL).
Note that if the FNTLB_GRAPHIC is set, the tooltip displays the facename (or the sample text) with the GUI system font and, if not, the tooltip displays the text using the corresponding font.
If the FNTLB_TRACKING_TOOLTIP is not set, then the tooltip is only shown when there is one or more selected items.
If the FNTLB_TRACKING_TOOLTIP is set, the tooltip shows the font facename under the mouse pointer and, if the FNTLB_TOOLTIPSELECTED flag is also set, it also shows the list of selected fonts.
You can assign a MRU list to your CFontListBox instance using the style flags:
FNTLB_MRULIST: A private MRU list.
FNTLB_MRUGLOBALLIST: The application level MRU list.
FNTLB_MRUCUSTOMLIST: A named MRU list shared by two or more controls.
Private MRU list
A private MRU list is local to the control: the modifications made on its MRU list are not reflected on other controls.
Custom MRU list
A custom MRU list is intended to be shared by more than one control in the application. It has a name (a string) which identifies it and can be used to refer to it. It is created the first time a control references it, and not destroyed when no more controls are attached to it (feature to be used in CFontComboBox, coming soon ...). All the modifications made via a control are reflected on the other controls attached to the MRU list.
The global MRU list
The global MRU list is a special custom MRU list which is valid when the application runs.
Attaching a control to a MRU list
You can attach a MRU list to your control using the AttachToMruFontList or SetMruFontListName (which are synonyms) functions as follows:
m_listFont1.AttachToMruFontList(NULL);
m_listFont2.AttachToMruFontList(_T("MruTest"));
m_listFont3.AttachToMruFontList(GLOBAL_MRULIST);
The DetachFromMruFontList() function allows you to detach a control from any type of MRU list previously attached. You do not need to use this function to attach a previously attached control to another MRU list.
You can also do this:
m_listFont1.ModifyFlags(FNTLB_MRUGLOBALLIST |
FNTLB_MRUCUSTOMLIST, FNTLB_MRULIST);
m_listFont1.SetMruFontListName(NULL);
m_listFont2.SetMruFontListName(_T("MruTest2));
m_listFont2.ModifyFlags(FNTLB_MRULIST |
FNTLB_MRUGLOBALLIST, FNTLB_MRUCUSTOMLIST);
// or, more simple, this :
m_listFont2.SetMruFontListName(_T("MruTest2));
m_listFont3.ModifyFlags(FNTLB_MRULIST |
FNTLB_MRUCUSTOMLIST, FNTLB_MRUGLOBALLIST);
m_listFont3.SetMruFontListName(GLOBAL_MRULIST);
- Step 1: Add a
ListBox control in your dialog resource using the Resource Editor. Specify the styles you want (?? the Selection mode).
- Step 2: If not created, create the
CDialog derived class corresponding to your dialog resource.
- Step 3: Add FontListBox.h to your dialog class header.
- Step 4: Via the ClassWizard, add a member control variable of type
CFontListBox.
- Step 5: Modify (in your dialog implementation file):
IMPLEMENT_DYNCREATE(CPpManual, CPropertyPage)
CPpManual::CPpManual() : CPropertyPage(CPpManual::IDD)
{
...
as follows, adding or modifying the CFontListBox style flags (where m_listFont1 is the member variable added before, and "MruTest" the name of a shared MRU font list):
IMPLEMENT_DYNCREATE(CPpManual, CPropertyPage)
CPpManual::CPpManual() : CPropertyPage(CPpManual::IDD),
m_listFont1(FNTLB_HAS_TOOLTIPS | FNTLB_TOOLTIPTEXT_FONT |
FNTLB_TRACKING_TOOLTIP |
FNTLB_MRUCUSTOMLIST, _T("MruTest"))
{
...
- Step 6: Add:
m_listFont1.Initialize();
in the OnInitDialog function of your dialog class.
Follow the above steps 1 to 3. Then, add a CFontListBox variable (or a CFontListBox* variable, as needed) to your CDialog derived class.
Create the object if needed and use the SubClassDlgItem. Don't forget to call the Initialize() function of CFontListBox.
See how to dynamically add and create a CFontListBox control in the CPpManual::OnInitDialog() function of the sample.
Constructors
CFontListBox();
CFontListBox(DWORD dwStyleFlags,
LPCTSTR lpszMruListName=NULL);
void Initialize();
Style flags and style
DWORD SetFlags(DWORD dwFlags);
DWORD GetFlags();
BOOL ModifyFlags(DWORD dwRemove, DWORD dwAdd);
BOOL ModifyStyle( DWORD dwRemove,
DWORD dwAdd, UINT nFlags = 0 );
Images
BOOL SetImages(CBitmap* pBitmap);
BOOL LoadImages(LPCTSTR lpszResourceName);
BOOL LoadImages(UINT nIDResource);
void SetStdImages();
Overridable filter functions
virtual BOOL OnAddItem(CFontDescriptor* lpFont);
virtual BOOL OnAddMruItem(CFontDescriptor* lpFont);
MRU relative functions
BOOL AttachToMruFontList(LPCTSTR lpszMruListName);
BOOL SetMruFontListName(LPCTSTR lpszMruListName);
BOOL DetachFromMruFontList();
int AddFontsToMruList(LPCTSTR lpszFontName);
int AddFontsToMruList(CFontDescriptor* lpFont);
int AddFontsToMruList(CStringArray* lpStrFaceNames);
int AddFontsToMruList(CFontDescriptorArray* lpFontArray);
int AddSelectedToMruList();
int RemoveFontsFromMruList(LPCTSTR lpStrFaceName);
int RemoveFontsFromMruList(CFontDescriptor* lpFont);
int RemoveFontsFromMruList(CStringArray* lpStrFaceNames);
int RemoveFontsFromMruList(CFontDescriptorArray* lpFontArray);
int RemoveSelectedFontsFromMruList();
BOOL HasMruList();
void RefreshMruList();
void ClearMruList();
const CString& GetCustomMruListName();
int GetMruFontCount();
BOOL EnableMruListPersistence (BOOL bPersistence = TRUE);
BOOL IsMruListPersistenceEnabled ();
BOOL ReloadMruListSettings();
BOOL SaveMruListSettings();
Miscellaneous functions
int GetFontCount();
int GetSelectedCount();
int GetSelectedFontNames(CStringArray* pStrArray);
int GetSelectedFontDescriptors(CFontDescriptorArray* lpArray);
int ClearSelection();
BOOL SubclassDlgItem(UINT nID, CWnd* pParent);
BOOL SubclassWindow(HWND hWnd);
void SetSampleText(LPCTSTR lpszSampleText);
CString& GetSampleText();
No public data members.
CFont* GetFontObject(int nHeight = 0);
CString m_strFaceName;
int m_nFontType;
DWORD m_dwCharSets;
int m_nImage;
void LoadFontListSettings();
void SaveFontListSettings();
BOOL ReloadMruListSettings(LPCTSTR lpszMruListName);
BOOL SaveMruListSettings(LPCTSTR lpszMruListName);
BOOL EnableMruListPersistence (LPCTSTR lpszMruListName,
BOOL bPersistence = TRUE);
BOOL IsMruListPersistenceEnabled (LPCTSTR lpszMruListName);
The source files are divided into two projects: the library project and the demo project.
- Unzip the extlib.zip (respecting folder names) on your disk. You must get something like this:

- Open the FontControl.dsw project in Visual Studio 6 and recompile all the libraries you need (menu Build->Batch build-> and your choice).
- Prepare your development environment by adding xxx/ExtLib/Include and xxx/ExtLib/Lib to the VS6 search directories (menu Tools->Options->Directories).
Now, you are ready to use the CFontListBox class in your program.
Unzip demo.zip respecting folder names on your disk, open the project file, and recompile it.
Many CodeProject contributors will find here some pieces of their code, and I thank them for their (involuntary) help (but I have not noted the references of their articles: please send me a mail if you feel you are a contributor to this code and I shall modify this article with links to your code).
Those I remember are:
- 10th Jan, 2006 - Version 1.0
- 25th Jan, 2006 - Version 1.1
- Fixed a bug when sorting the font names.
- Fixed a bug when calculating the image index for some printer fonts.
- Fixed a bug in font enumeration.