One of the goals for the Printer’s Apprentice rewrite is to significantly simplify the code base. The code in the current version, 7.58, has roots going back to 1992 and Visual Basic 1.0. Much of the code is overly complex and function, rather than object, oriented.
Visual development environments make it very easy to build a great UI and then integrate code into the events such as Button_Click. Then next thing you know you have a great looking form with a ton of code tucked into the events.
That is is precisely how the code in Printer’s Apprentice evolved. Bad, bad, bad. So I’m working to change that approach with the new version. What I want to do with this posting is give you an idea of how the user interface objects consolidate code and how they communicate with each other.
Lets take a look at two objects, ctlInstalledFonts and cEnumFonts. ctlInstalledFonts handles the UI for selecting installed fonts in Printer’s Apprentice. cEnumFonts is a data layer that does the grunt work of reading fonts from the OS, linking them to registry entries and so forth.
On the left is the design view of the UI object. On the right are simplified class diagrams for the ctlInstalledFonts and cEnumFonts.
cltInstalledFonts only job is to present the UI for installed fonts. It does not actually enumerate or read installed fonts, that is a data operation handled by the other object, cEnumFonts.
The Fields section lists controls, properties and data entities in the control. The actual control contains many more entities. I’m only showing a few important ones for this discussion.
||Private instance of a cEnumFonts object.
||Listview control to display fonts.
||PropertySheet control below the listview. This displays more details about a font than can be displayed in the listview.
||Context menu that appears when the user right clicks a font in the listview.
Again, cEnumFonts is what actually queries the system and returns the list of fonts to ctlInstalledFonts.
||Gets an initial count of the fonts installed on the system.
||Function used by the Win32 EnumFontFamiliesEX() function. This is what steps through each font on a system.
||A sub that starts the font enumeration process.
||Number of fonts that were enumerated.
||An array of fonts that were enumerated.
||The handle of the window listing the fonts.
||Do we include Postscript based fonts or not?
||Triggered when a font family is about to be read.
||Triggered when a font family is read and created.
||Triggered when reading a font family is complete.
When Printer’s Apprentice starts, the main form creates an instance of ctlInstalledfonts. It in turn directs its private cEnumFont object to examine the system font list.
'walk the font list
As ctlInstalledFonts._EnumFonts walks through the font list, it triggers the following events for each font that it reads.
Public Event FontEnumStart(ByVal pFaceName As String)
Public Event FontEnumEnd(ByVal pEnumIndex As Integer, ByVal pFontCount As Integer)
These events allow the calling form to modify the user interface as events are triggered in the ctlInstalledFonts._EnumFonts object. For example, by hooking into the events I can update the splash screen and show progress as Printer’s Apprentice loads.
Using events in a UI control also makes it easy for one menu handler to process selections for both the main window menu and context menus. The main window and ctlInstalledFonts both have a Print menu.
ctlInstalledFonts has a series of customs events that all have the same signature as a standard menu call. Rather than handling print code right in the menu, the mnuPrint.Click() handler instead raises the event.
Public Event emnuPrint_Click(ByVal sender As System.Object, ByVal e As System.EventArgs)
Public Event emnuUninstall_Click(ByVal sender As System.Object, ByVal e As System.EventArgs)
Public Event emnuProperties_Click(ByVal sender As System.Object, ByVal e As System.EventArgs)
Private Sub mnuPrint_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles mnuPrint.Click
RaiseEvent emnuPrint_Click(sender, e)
And back in the main form, a single handler processes the Print selection for both menus.
#Region "File menu"
Private Sub mnuFilePrint_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) _
Handles mnuFilePrint.Click, ctlInstalledFonts.emnuPrint_Click, ctlFontFiles.emnuPrint_Click
End Sub #End Region
So rather than having print code scattered around the application, I use a PrintFonts() call inside a menu handler that processes the Click event for multiple menus.
This approach is certainly reducing the amount of code in Printer’s Apprentice.