wxPython: Learning to Use Fonts

Have you ever wondered about how to change your font in wxPython? Well, now is your lucky day because that is exactly what this tutorial is going to cover. We will look at the following three items:

  • Built-in fonts of wxPython
  • The font dialog
  • The font enumerator demo from the wxPython demo

Fonts are an important part of your program and enhance your program’s readability. It’s always a good idea to make sure your fonts are the right size and place. Yes, that’s common sense, but sometimes common sense doesn’t happen in programming. Enough philosophizing. Let’s get to the meat of the article!

Built-in Fonts

You may not realize this yet, but wxPython has its own set of generic fonts! I’m not sure if they look the same across all platforms, but I’ll wager that they’re pretty close. Let’s take a look at a simple custom demo that shows what these fonts look like:

import random
import wx
import wx.lib.scrolledpanel as scrolled

########################################################################
class MyForm(wx.Frame):
    
    #----------------------------------------------------------------------
    def __init__(self):
        wx.Frame.__init__(self, None, wx.ID_ANY, "Font Tutorial")
 
        # Add a panel so it looks the correct on all platforms
        panel = scrolled.ScrolledPanel(self)
        panel.SetAutoLayout(1)
        panel.SetupScrolling()
        
        fontSizer = wx.BoxSizer(wx.VERTICAL)
        families = {"FONTFAMILY_DECORATIVE":wx.FONTFAMILY_DECORATIVE, # A decorative font
                    "FONTFAMILY_DEFAULT":wx.FONTFAMILY_DEFAULT,
                    "FONTFAMILY_MODERN":wx.FONTFAMILY_MODERN,     # Usually a fixed pitch font
                    "FONTFAMILY_ROMAN":wx.FONTFAMILY_ROMAN,      # A formal, serif font
                    "FONTFAMILY_SCRIPT":wx.FONTFAMILY_SCRIPT,     # A handwriting font
                    "FONTFAMILY_SWISS":wx.FONTFAMILY_SWISS,      # A sans-serif font
                    "FONTFAMILY_TELETYPE":wx.FONTFAMILY_TELETYPE    # A teletype font
                    }
        weights = {"FONTWEIGHT_BOLD":wx.FONTWEIGHT_BOLD,
                   "FONTWEIGHT_LIGHT":wx.FONTWEIGHT_LIGHT,
                   "FONTWEIGHT_NORMAL":wx.FONTWEIGHT_NORMAL
                   }
        
        styles = {"FONTSTYLE_ITALIC":wx.FONTSTYLE_ITALIC,
                  "FONTSTYLE_NORMAL":wx.FONTSTYLE_NORMAL,
                  "FONTSTYLE_SLANT":wx.FONTSTYLE_SLANT
                  }
        sizes = [8, 10, 12, 14]
        for family in families.keys():
            for weight in weights.keys():
                for style in styles.keys():
                    label = "%s    %s    %s" % (family, weight, style)
                    size = random.choice(sizes)
                    font = wx.Font(size, families[family], styles[style], 
                                   weights[weight])
                    txt = wx.StaticText(panel, label=label)
                    txt.SetFont(font)
                    fontSizer.Add(txt, 0, wx.ALL, 5)
        panel.SetSizer(fontSizer)
        sizer = wx.BoxSizer(wx.HORIZONTAL)
        sizer.Add(panel, 1, wx.EXPAND)
        self.SetSizer(sizer)
        
#----------------------------------------------------------------------
# Run the program
if __name__ == "__main__":
    app = wx.App(False)
    frame = MyForm().Show()
    app.MainLoop()

As you can plainly see, wxPython let’s you apply a wx.Font instance to your widget using the widget’s SetFont method, which applies the font. Most wx.Font objects are made up of the following: a size, font family (i.e. Swiss, Roman, Normal, etc), font style (i.e. italic or normal) , and font weight (i.e. bold or normal). You can mix and match these three characteristics to get what you want most of the time. If you want to use a font that’s installed on your system, well, we’ll get to that in the next section. It should be noted that the wx.Font class also accepts underline, face, and encoding arguments, but we won’t be covering those in this article. Check the documentation for more information.

Let’s take a moment and look at the code though. You will notice that we have three Python dictionaries that we will use to control the StaticText widget’s appearance. These dictionaries hold the most common families, weights and styles, respectively. We loop over each family, weight and style in a triple nested loop and mix and match them to create every type of standard font wx can do with these sets. Note that all we do is create a wx.Font instance and then apply it to the StaticText using the latter’s SetFont method. The rest of the code should be pretty easy to follow. Pretty simple, no?

Now we’re going to find out about the font dialog.

The Font Dialog

For this example, we’ll be using the wxPython’s demo code in a stand alone script. Basically, it shows you how to select a font, extract various bits of information from the selection and apply the settings to your widget of choice. Here’s the code:

import wx
from wx.lib import stattext

#---------------------------------------------------------------------------

class TestPanel(wx.Panel):

    #----------------------------------------------------------------------
    def __init__(self, parent):
        wx.Panel.__init__(self, parent, -1)
        
        btn = wx.Button(self, -1, "Select Font")
        self.Bind(wx.EVT_BUTTON, self.OnSelectFont, btn)

        self.sampleText = stattext.GenStaticText(self, -1, "Sample Text")
        self.sampleText.SetBackgroundColour(wx.WHITE)

        self.curFont = self.sampleText.GetFont()
        self.curClr = wx.BLACK

        fgs = wx.FlexGridSizer(cols=2, vgap=5, hgap=5)
        fgs.AddGrowableCol(1)
        fgs.AddGrowableRow(0)

        fgs.Add(btn)
        fgs.Add(self.sampleText, 0, wx.ADJUST_MINSIZE|wx.GROW)

        fgs.Add((15,15)); fgs.Add((15,15))   # an empty row

        fgs.Add(wx.StaticText(self, -1, "PointSize:"))
        self.ps = wx.StaticText(self, -1, "")
        font = self.ps.GetFont()
        font.SetWeight(wx.BOLD)
        self.ps.SetFont(font)
        fgs.Add(self.ps, 0, wx.ADJUST_MINSIZE)

        fgs.Add(wx.StaticText(self, -1, "Family:"))
        self.family = wx.StaticText(self, -1, "")
        self.family.SetFont(font)
        fgs.Add(self.family, 0, wx.ADJUST_MINSIZE)

        fgs.Add(wx.StaticText(self, -1, "Style:"))
        self.style = wx.StaticText(self, -1, "")
        self.style.SetFont(font)
        fgs.Add(self.style, 0, wx.ADJUST_MINSIZE)

        fgs.Add(wx.StaticText(self, -1, "Weight:"))
        self.weight = wx.StaticText(self, -1, "")
        self.weight.SetFont(font)
        fgs.Add(self.weight, 0, wx.ADJUST_MINSIZE)

        fgs.Add(wx.StaticText(self, -1, "Face:"))
        self.face = wx.StaticText(self, -1, "")
        self.face.SetFont(font)
        fgs.Add(self.face, 0, wx.ADJUST_MINSIZE)

        fgs.Add((15,15)); fgs.Add((15,15))   # an empty row

        fgs.Add(wx.StaticText(self, -1, "wx.NativeFontInfo:"))
        self.nfi = wx.StaticText(self, -1, "")
        self.nfi.SetFont(font)
        fgs.Add(self.nfi, 0, wx.ADJUST_MINSIZE)

        # give it some border space
        sizer = wx.BoxSizer(wx.VERTICAL)
        sizer.Add(fgs, 0, wx.GROW|wx.ADJUST_MINSIZE|wx.ALL, 25)

        self.SetSizer(sizer)
        self.UpdateUI()

    #----------------------------------------------------------------------
    def UpdateUI(self):
        self.sampleText.SetFont(self.curFont)
        self.sampleText.SetForegroundColour(self.curClr)
        self.ps.SetLabel(str(self.curFont.GetPointSize()))
        self.family.SetLabel(self.curFont.GetFamilyString())
        self.style.SetLabel(self.curFont.GetStyleString())
        self.weight.SetLabel(self.curFont.GetWeightString())
        self.face.SetLabel(self.curFont.GetFaceName())
        self.nfi.SetLabel(self.curFont.GetNativeFontInfo().ToString())
        self.Layout()

    #----------------------------------------------------------------------
    def OnSelectFont(self, evt):
        data = wx.FontData()
        data.EnableEffects(True)
        data.SetColour(self.curClr)         # set colour
        data.SetInitialFont(self.curFont)

        dlg = wx.FontDialog(self, data)
        
        if dlg.ShowModal() == wx.ID_OK:
            data = dlg.GetFontData()
            font = data.GetChosenFont()
            colour = data.GetColour()

            self.curFont = font
            self.curClr = colour
            self.UpdateUI()

        # Don't destroy the dialog until you get everything you need from the
        # dialog!
        dlg.Destroy()


########################################################################
class MyForm(wx.Frame):
 
    #----------------------------------------------------------------------
    def __init__(self):
        wx.Frame.__init__(self, None, wx.ID_ANY,
                          "wx.FontDialog Tutorial")
        panel = TestPanel(self)
        
#----------------------------------------------------------------------
# Run the program
if __name__ == "__main__":
    app = wx.App(False)
    frame = MyForm()
    frame.Show()
    app.MainLoop()

This code is pretty straightforward, but let’s take a moment to break it down anyway. We’re most interested in the UpdateUI and OnSelectFont methods, but don’t be afraid to look at the rest. There’s a neat part in the __init__ as well, where we learn how to get the font that’s applied to the StaticText and change its weight. Hint: look for the section where the “self.ps” is defined.

Anyway, we’re going to work backward and start in the OnSelectFont method. Why? Because it calls the other interesting method! First off, we need to create a wx.FontData object that will hold information related to the font we choose in our font dialog later on. Then we set the object’s current color and font to the current font’s settings. Next we create a wx.FontDialog instance and show the dialog! Here we pull out all the font data we need, in this case, the color and font and then we call our update method.

In the UpdateUI method, we set one of our static text controls to the chosen font. Then we extract various bits of information from the currently chosen font and display that information in the appropriate labels. As you can see, we chose to extract the point size, family, style, weight, face and native font information. Now we can move on to our last demo!

The FontEnumerator

This example is also taken from the wxPython demo. Here’s what it has to say about the wx.FontEnumerator widget:

wxFontEnumerator enumerates either all available fonts on the system or only the ones with given attributes – either only fixed-width (suited for use in programs such as terminal emulators and the like) or the fonts available in the given encoding.

On my Windows XP machine, it seems to be all available fonts on the system. Here’s the code for your perusal:

# fontEnumDemo.py

import wx

########################################################################
class TestPanel(wx.Panel):
    
    #----------------------------------------------------------------------
    def __init__(self, parent):
        wx.Panel.__init__(self, parent, -1)

        e = wx.FontEnumerator()
        e.EnumerateFacenames()
        elist= e.GetFacenames()

        elist.sort()

        s1 = wx.StaticText(self, -1, "Face names:")

        self.lb1 = wx.ListBox(self, -1, wx.DefaultPosition, (200, 250),
                             elist, wx.LB_SINGLE)

        self.Bind(wx.EVT_LISTBOX, self.OnSelect, id=self.lb1.GetId())

        self.txt = wx.StaticText(self, -1, "Sample text...", (285, 50))

        row = wx.BoxSizer(wx.HORIZONTAL)
        row.Add(s1, 0, wx.ALL, 5)
        row.Add(self.lb1, 0, wx.ALL, 5)
        row.Add(self.txt, 0, wx.ALL|wx.ADJUST_MINSIZE, 5)

        sizer = wx.BoxSizer(wx.VERTICAL)
        sizer.Add(row, 0, wx.ALL, 30)
        self.SetSizer(sizer)
        self.Layout()

        self.lb1.SetSelection(0)
        self.OnSelect(None)
        wx.FutureCall(300, self.SetTextSize)

    #----------------------------------------------------------------------
    def SetTextSize(self):
        self.txt.SetSize(self.txt.GetBestSize())

    #----------------------------------------------------------------------
    def OnSelect(self, evt):
        face = self.lb1.GetStringSelection()
        font = wx.Font(28, wx.DEFAULT, wx.NORMAL, wx.NORMAL, False, face)
        self.txt.SetLabel(face)
        self.txt.SetFont(font)
        if wx.Platform == "__WXMAC__": self.Refresh()
        
########################################################################
class MyForm(wx.Frame):
 
    #----------------------------------------------------------------------
    def __init__(self):
        wx.Frame.__init__(self, None,
                          title="wx.FontEnumerator Tutorial",
                          size=(800,600))
        panel = TestPanel(self)
        
        
#----------------------------------------------------------------------
# Run the program
if __name__ == "__main__":
    app = wx.App(False)
    frame = MyForm()
    frame.Show()
    app.MainLoop()

Alright, this code is easy too. What’s going on here, you ask? Well, we’re dealing with Python, that’s what! Python just rocks! Let’s break this code down and see what’s going on. It looks like all we need to do is create an instance of a wx.FontEnumerator. Note that it doesn’t take any arguments…at least, it doesn’t in the demo and the documentation doesn’t mention any either. Next we enumerate the font face names. Finally, we create a list of face names, sort them and put the list in a wx.ListBox.

Note: In the original demo, the author used “list” as the variable name for the next line, but since that’s a keyword in Python (and very bad form in general), the code in this example has replaced “list” with “elist”.

The next interesting piece of code happens in the OnSelect method, which is fired whenever the user selects an item in the list box. As you might have guessed, we grab the face name from the ListBox and use it to create a font which is then applied to a static text widget. This allows us to see what the font looks like.

Wrapping Up

Now you should know how to use wxPython’s standard fonts as well as the one’s that are loaded on your machine. You now know how to extract a lot of information about a particular font as well, which may come in handy when troubleshooting why a font isn’t behaving the way you expect. Remember, you can apply these fonts to more than just the StaticText widget. In fact, most widgets have a SetFont method, so you can apply your font preferences to most of your user interface.

Further Reading

Source Code