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.