A few days ago, I wrote an article about using ConfigObj with wxPython. The first question I was asked about the article regarded using a configuration file to generate the dialog. I thought this was an interesting idea, so I took a stab at implementing that functionality. Personally I think it would be probably be better to just create the dialog using XRC and use ConfigObj to help manage which dialog file is loaded that way. However, this was an intriguing exercise to me and I think you’ll find it enlightening too.

DISCLAIMER: This is a total hack and may or may not serve you needs. I give various suggestions for expanding the example though, so I hope it’s helpful!

Now that that’s out of the way, let’s create a super simple configuration file. We’ll call it “config.ini” for convenience’s sake:

config.ini

server = Update Server:
username = Username:
password = Password:
update interval = Update Interval:
agency = Agency Filter:
filters = ""
 
[Values]
server = http://www.someCoolWebsite/hackery.php
username = ""
password = ""
update interval = 2
agency_choices = Include all agencies except, Include all agencies except, Exclude all agencies except
filters = ""

This configuration file has two sections: Labels and Values. The Labels section has the labels we will use to create wx.StaticText controls with. The Values section has some sample values we can use for the corresponding text control widgets and one combo box. Note that the agency_choices field is a list. The first item in the list will be the default option in the combo box and the other two items are the real contents of the widget.

Now let’s take a look at the code that will build the dialog:

preferencesDlg.py

import configobj
import wx
 
########################################################################
class PreferencesDialog(wx.Dialog):
    """
    Creates and displays a preferences dialog that allows the user to
    change some settings.
    """
 
    #----------------------------------------------------------------------
    def __init__(self):
        """
        Initialize the dialog
        """
        wx.Dialog.__init__(self, None, wx.ID_ANY, 'Preferences', size=(550,300))
        self.createWidgets()
 
    #----------------------------------------------------------------------
    def createWidgets(self):
        """
        Create and layout the widgets in the dialog
        """
        lblSizer = wx.BoxSizer(wx.VERTICAL)
        valueSizer = wx.BoxSizer(wx.VERTICAL)
        btnSizer = wx.StdDialogButtonSizer()
        colSizer = wx.BoxSizer(wx.HORIZONTAL)
        mainSizer = wx.BoxSizer(wx.VERTICAL)
 
        iniFile = "config.ini"
        self.config = configobj.ConfigObj(iniFile)
 
        labels = self.config["Labels"]
        values = self.config["Values"]
        self.widgetNames = values
        font = wx.Font(12, wx.SWISS, wx.NORMAL, wx.BOLD)
 
        for key in labels:
            value = labels[key]
            lbl = wx.StaticText(self, label=value)
            lbl.SetFont(font)
            lblSizer.Add(lbl, 0, wx.ALL, 5)
 
        for key in values:
            print key
            value = values[key]
            if isinstance(value, list):
                default = value[0]
                choices = value[1:]
                cbo = wx.ComboBox(self, value=value[0],
                                  size=wx.DefaultSize, choices=choices, 
                                  style=wx.CB_DROPDOWN|wx.CB_READONLY, 
                                  name=key)
                valueSizer.Add(cbo, 0, wx.ALL, 5)
            else:
                txt = wx.TextCtrl(self, value=value, name=key)
                valueSizer.Add(txt, 0, wx.ALL|wx.EXPAND, 5)
 
        saveBtn = wx.Button(self, wx.ID_OK, label="Save")
        saveBtn.Bind(wx.EVT_BUTTON, self.onSave)
        btnSizer.AddButton(saveBtn)
 
        cancelBtn = wx.Button(self, wx.ID_CANCEL)
        btnSizer.AddButton(cancelBtn)
        btnSizer.Realize()
 
        colSizer.Add(lblSizer)
        colSizer.Add(valueSizer, 1, wx.EXPAND)
        mainSizer.Add(colSizer, 0, wx.EXPAND)
        mainSizer.Add(btnSizer, 0, wx.ALL | wx.ALIGN_RIGHT, 5)
        self.SetSizer(mainSizer)
 
    #----------------------------------------------------------------------
    def onSave(self, event):
        """
        Saves values to disk
        """
        for name in self.widgetNames:
            widget = wx.FindWindowByName(name)
            if isinstance(widget, wx.ComboBox):
                selection = widget.GetValue()
                choices = widget.GetItems()
                choices.insert(0, selection)
                self.widgetNames[name] = choices
            else:
                value = widget.GetValue()
                self.widgetNames[name] = value
        self.config.write()
        self.EndModal(0)
 
########################################################################
class MyApp(wx.App):
    """"""
 
    #----------------------------------------------------------------------
    def OnInit(self):
        """Constructor"""
        dlg = PreferencesDialog()
        dlg.ShowModal()
        dlg.Destroy()
 
        return True
 
if __name__ == "__main__":
    app = MyApp(False)
    app.MainLoop()

To start, we subclass a wx.Dialog and all its createWidgets method. This method will read our config file and use the data therein to create the display. Once the config is read, we loop over the keys in the Labels section and create static text controls as needed. Next, we loop over the values in the other section and use a conditional to check the type of widget. In this case, we only care about wx.TextCtrl and wx.Combobox. This is where ConfigObj helps since it actually can typecast some of the entries in our configuration file. If you use a configspec, you can get even more granular and that may be the way you’ll want to go to extend this tutorial. Note that for the text controls and combo box, I set the name field. This is important for saving the data, which we’ll be seeing in just a moment.

Anyway, in both loops, we use vertical BoxSizers to hold our widgets. You may want to swap this for a GridBagSizer or FlexGridSizer for your specialized interface. I personally really like BoxSizers. I also used a StdDialogButtonSizer for the buttons at the suggestion of Steven Sproat (Whyteboard). If you use the correct standard ids for the buttons, this sizer will place them in the right order in a cross-platform way. It’s quite handy, although it doesn’t take many arguments. Also note that the documentation for this sizer implies you can specify the orientation, but you really cannot. I spoke with Robin Dunn (creator of wxPython) about this issue on IRC and he said that epydoc was grabbing the wrong docstring.

The next method that we care about is onSave. Here is where we save whatever the user has entered. Earlier in the program, I grabbed the widget names from the configuration and we loop over those now. We call wx.FindWindowByName to find the widget by name. Then we use isinstance again to check what kind of widget we have. Once that’s done, we grab the value that the widget holds using GetValue and assign that value to the correct field in our configuration. When the loop finishes, we write the data to disk. Immediate improvement alert: I have no validation here at all! This is something for you to do to extend this example. The last step is to call EndModal(0) to close the dialog and, in turn, the application.

Now you know the basics of generating a dialog from a configuration file. I think using some kind of dictionary with widget type names (probably in strings) might be an easy way to make this script work with other widgets. Use your imagination and let me know what you come up with.

Note: All code tested on Windows XP with Python 2.5, ConfigObj 4.6.0, and Validate 1.0.0.

Further Reading

Downloads

Print Friendly