Dr. Dobb's is part of the Informa Tech Division of Informa PLC

This site is operated by a business or businesses owned by Informa PLC and all copyright resides with them. Informa PLC's registered office is 5 Howick Place, London SW1P 1WG. Registered in England and Wales. Number 8860726.


Channels ▼
RSS

C/C++

Programmer's Toolchest


May01: Programmer's Toolchest

The wxWindows Cross-Platform Framework

A C++ framework for building cross-platform applications

Vadim is finishing his PhD in theoretical physics as well as working on a variety of open-source projects. He can be reached at Vadim.zeitlin@dptmaths .ens-cachan.fr.


Scripting wxWindows with wxPython


The framework wxWindows is a freely available C++ cross-platform GUI originally created by Julian Smart. wxWindows subscribes to the "write once, build anywhere" ideology — you should be able to compile your program without changing anything under all supported platforms. But wxWindows is much more than just a thin wrapper around GUI controls. It also includes classes for working with various OS-dependent services — threads, sockets, high-resolution timers, and more mundane things such as files — in a portable manner. Of course, it also has everything you expect to find in a GUI toolkit, including common controls, common dialogs, document/view classes, support for printing, drag-and-drop, and so on.

wxWindows (available at http://www .wxwindows.org/) currently supports Win32, UNIX/GTK+ (GTK+ is a free toolkit, which is used with X11 and can be used on almost all UNIX workstations, including Linux), and MacOS (both Versions 8 and X). There are also Win16, Motif, and OS/2 ports in progress. Plans for ports to BeOS and QNX have also been announced. wxWindows is free, meaning you don't have to pay any money for using it and, unlike GPL or L-GPL licenses, allows for use in any program under any other license (including commercial programs).

Furthermore, a separate library (wxBase) can be built from the same source tree including only the nonGUI parts of the framework. It contains several data-structure classes (as wxWindows predates STL, it has its own string, list, array, hash table classes, and a powerful wxDateTime class); OS-abstraction classes (wxThread, wxSocket, and wxStopWatch); and utility classes (wxCmdLineParser; wxConfig for working with INI and other configuration files; wxDllLoader for run-time binding; and more). The same classes are available in the full wxWindows library as well, but wxBase is ideal for writing console programs that should run under both Windows and UNIX, be it a socket server or a script to update a database periodically.

A vCard Viewer/Editor Example

In addition to extensive documentation (provided in LaTeX, HTML, PDF, or WinHelp formats), wxWindows includes almost 70 sample programs to illustrate wxWindows features. In this article, I'll present another sample that is useful in its own right. The sample program is a vCard editor. vCards are small ASCII files, usually having a VCF extension with personal information such as name, address, e-mail, and the like; see http://www.imc .org/pdi/. The vCard editor presented here will produce a Vcard similar to that in Figure 1. The complete program, including source and binaries, is available electronically; see "Resource Center," page 5. While there is not enough space to discuss the program in its entirety, I will focus on some key points. However, a set of programmer's notes is available electronically; again, see "Resource Center."

The sample program has three parts:

  • A wrapper around a low-level vCard SDK that lets you deal with vCards as C++ objects of class wxVCard, instead of using the rather nonintuitive C API.

  • A dialog to view or edit vCard, which is exposed to the rest of the program via two functions, allowing the editing and creating of a card.

  • The main module that implements the top-level frame allowing users to work with vCards.

Like most wxWindows programs, the main program file, wxcard.cpp, has an application class (vCardApp) and the main window class (vCardFrame). Almost all wxWindows programs have these two classes: The application class is important because program execution normally doesn't start in the familiar main() function in wxWindows, but in the OnInit() member function, which usually is used to create the main application window. Other wxApp methods may be overridden, such as OnRun(), which by default just runs the main event loop of a GUI program or OnExit() where you can clean up resources allocated in OnInit().

The bulk of wxcard.cpp deals with the main frame class rather than with vCardApp. I could have used wxDocView classes, which implement the MVC paradigm in wxWindows to perform common operations such as opening and saving files, but for simplicity, I decided to perform these operations manually. A nice feature of wxDocView is that it lists the most recently used files; however, you can also access this list manually with the wxFileHistory class (though I did not do so in this example).

The frame constructor is where you create all of the vCard's components — the menus, tool and status bars, and child controls. The menus may be created from resources but it is also easy to do it manually: See the first 20 lines of vCardFrame:: vCardFrame() in Listing One. You create the toolbar buttons in the same manner. Manual creation is adequate for our simple example, but a better way to do this in a larger program would be to use a table containing the buttons IDs and the labels. Toolbar buttons have the same IDs as the menu commands, which will conveniently let the program process the commands from both of them in the same handler. Finally, you create the list control in the so-called "report mode," resulting in Figure 2.

Once everything is in place, you add event processing to enable responses to menu and toolbar commands. For this you need the event tables that let you associate a handler (C++ method) with an external event. Any class handling the events must have an event table that maps the event types to the corresponding handlers. This table is declared in the class declaration using the DECLARE_ EVENT_ TABLE() macro, as in Listing Two.

The first few commands in Listing Two's map menu (and toolbar, because they have the same IDs) commands to the vCardFrame methods. The next one maps all "Update UI" events in the range between the two given IDs to the same method, and the next line will call OnListItemActivated() whenever the list item is double clicked or Enter is pressed. Finally, the last one is an event of a different kind as it doesn't correspond to any particular control, but is called whenever users try to close the frame (whether this is done by using the Close button of the frame or the Quit command from the menu) and intercepts it to ask the usual question, "Do you want to save changes?" in this handler.

Implementing handlers is straightforward, as most of the menu operations can also be performed from the program. This is why I often create DoSomething() for each OnSomething() where the requested action is really performed. A few handlers are more interesting: First, OnClose() shows that some events can be vetoed. This happens if users decide not to abandon the changes — and then the frame is not closed. The event parameters not only carry the information about events, but can also return something to wxWindows.

Second, OnQuit() shows how to exit a wxWindows application. Just call Close(), which will try to close the main frame (it may not yet happen as OnClose() will be called from Close() and may veto this). If the frame closes, the application will terminate as it is the last top-level application window and closing it, by default, terminates the application.

Some menu commands don't make sense if no card is selected in the list control. Clearly, they must be disabled if there is no selection, and there are two ways to do this: either by tracking the selection in the control and enabling or disabling the commands whenever it changes, or by using the OnUpdateUI handler. Although the first method is fine for small programs, it is easy to forget to update something somewhere when the number of GUI elements grows. Using OnUpdateUI, you can let wxWindows track the state of these items itself. It will send wxUpdateUIEvent during the idle time to get the current state of the controls as in Listing Three. In this case, all the logic for updating the UI is contained in one place instead of being scattered all over the program.

Writing Dialogs

Writing a dialog involves two key steps: creating the controls in it and transferring the data to and from those controls. Creating an attractive dialog is not simple, even for one platform, as its size should adapt to the font used. Windows programmers use dialog units that depend on the font metrics. This is, however, not enough for cross-platform applications as the controls themselves have different sizes as well — even expressed in dialog units. Another related program is that the strings in other languages never have the same extent as the original ones and even if a dialog looks nice in the English version of the program, for example, in the German one, the static labels will almost surely appear truncated.

wxWindows provides two ways to deal with this problem: constraints and sizers. Both of them use the real size of the controls for layout. The sizers are the wxWindows analog of Java layout managers (the syntax is different, but the idea is the same). Although slightly less powerful than the constraints, they are much simpler to use and I use them in wxVCardDialog (vcarddlg.cpp), as in Listing Four. A benefit of using sizers is that the dialog can be made resizeable without any extra effort — they take control of resizing automatically.

Although sizers simplify the dialog creation greatly, using a dialog builder such as wxDesigner (http://www.roebling.de/) is even easier and can be used to produce the dialog's creation code automatically. However, doing it manually is not that hard even if slightly tedious. The sizers are just the boxes, and the goal of the game is to assemble the entire dialog from such boxes, making them of fixed size or expandable/shrinkable as required.

Data Transfer and Validation

When the dialog is created, the controls must be populated with their initial values, and when the dialog is dismissed with the OK button, the data should be stored back in the program variables. As always, there are several ways to do it: One method is to use validators which can also check that the data is correct; another is to do it manually in TransferDataToWindow() and TransferDataFromWindow(): virtual methods, which are called when the dialog is created and when the OK button is pressed, respectively.

Using validators is a better way to transfer data than doing it manually, but in this example it is simpler for me to choose the second method as I also want to set the dirty flag for the values that have really changed, which the validators don't directly support.

Writing both TransferData() functions is straightforward. Unlike MFC, wxWindows doesn't have DDX/DDV macros to simplify writing these functions, but you may easily define your own just as I did. Because vCards have many properties, I defined the macros VCARD_PROP_ TO_ CTRL and CTRL_TO_VCARD_PROP to simplify the code. See the file vcarddlg.cpp for more details. TransferDataFromWindow() may be False to prevent the dialog from closing if some control contains an invalid value.

Using Various Controls

Most of the controls used in the dialog are straightforward: Buttons, text controls, and check and list boxes are just what you'd expect. Other controls are slightly more interesting: For example, wxCalendarCtrl provides a way to pick a date using a calendar. It has several configurable options as most of the more complicated wxWindows controls do.

Standard controls are never enough, so you can create your own, of course. A simple example is the wxDateBrowseButton in Listing Five, which is combined with a text entry zone and lets users browse for the date by popping up a date selection dialog.

Conclusion

Many other features of the Vcard example are discussed in the Programmer's Notes (prgnotes.txt) available electronically. These include image support (wxWindows supports all the major formats such as BMP, GIF, JPEG, and TIFF); custom classes for Internet access (wxURL, wxHTTP, and wxFTP); an HTML sublibrary (wxHTML), sound class (wxWave), internationalization (i18n); writing multithreaded wxWindows programs, TCP sockets, wxODBC, and Unicode.

Among the planned features for the next release of wxWindows is XML-based resource files format, full Unicode support for wxGTK, OpenSSL support, and wxRegEx class and ports to other platforms (WinCE, BeOS, and QNX are the primary targets, as well as a Linux FrameBuffer port). This will be made easier by the ongoing project to add support for generic (as opposed to native) controls and themes to wxWindows.

DDJ

Listing One

// vCardFrame: the main application window
// ctor: create the frame and its child controls
vCardFrame::vCardFrame()
          : wxFrame(
                    NULL,                   // no parent
                    -1,                     // default id
                    "",                     // title (will be set later)
                    wxDefaultPosition,
                    wxSize(400, 250)
                   )
{
    // create the menu
    wxMenu *menuFile = new wxMenu("", wxMENU_TEAROFF);
    menuFile->Append(vCard_File_New, _("&New\tCtrl-N"), 
                                          _("Create new card file"));
    menuFile->Append(vCard_File_Open, _("&Open...\tCtrl-O"), 
                                          _("Open a card file"));
    menuFile->Append(vCard_File_Save, _("&Save\tCtrl-S"),
                                      _("Save the card file"));
    menuFile->AppendSeparator();
    menuFile->Append(vCard_File_Quit, _("E&xit\tAlt-X"), 
                                      _("Quit this program"));
    wxMenu *menuEdit = new wxMenu("", wxMENU_TEAROFF);
    menuEdit->Append(vCard_Edit_Add,  _("&Add card..."), 
                                      _("Add a new card to the file"));
    menuEdit->Append(vCard_Edit_Edit, _("&Edit card..."), 
                                      _("Edit the selected card"));
    menuEdit->Append(vCard_Edit_Delete, _("&Delete card"), 
                                       _("Delete the selected card"));
    wxMenu *menuHelp = new wxMenu("", wxMENU_TEAROFF);
    menuHelp->Append(vCard_Help_About, _("&About...\tF1"), 
                                       _("Show about dialog"));
    wxMenuBar *menuBar = new wxMenuBar;
    menuBar->Append(menuFile, _("&File"));
    menuBar->Append(menuEdit, _("&Edit"));
    menuBar->Append(menuHelp, _("&Help"));

Back to Article

Listing Two

BEGIN_EVENT_TABLE(vCardFrame, wxFrame)
            EVT_MENU(vCard_File_New,    vCardFrame::OnNewFile)
             ... 
            EVT_MENU(vCard_Help_About,  vCardFrame::OnAbout)
            EVT_UPDATE_UI_RANGE(vCard_Edit_Edit, vCard_Edit_Delete,
                                vCardFrame::OnUpdateEditMenu)
            EVT_LIST_ITEM_ACTIVATED(vCard_ListCtrl,
                                    vCardFrame::OnListItemActivated)
            EVT_CLOSE(vCardFrame::OnClose)
END_EVENT_TABLE()

Back to Article

Listing Three

void vCardFrame::OnUpdateEditMenu(wxUpdateUIEvent& event)
        {
            // edit and delete operations only make sense 
            //  if there is a selected card
            event.Enable( GetSelection() != -1 );
        }

Back to Article

Listing Four

// vCard editing dialog
class wxVCardDialog : public wxDialog
{
public:
    wxVCardDialog(wxWindow *parent, wxVCard *vcard);
    virtual ~wxVCardDialog() { delete m_imagelist; }
    virtual bool TransferDataToWindow();
    virtual bool TransferDataFromWindow();
protected:
    // event handlers
    void OnEmailAdd(wxCommandEvent&);
    void OnEmailModify(wxCommandEvent&);
    void OnEmailDelete(wxCommandEvent&);
    void OnAddrAdd(wxCommandEvent&);
    void OnAddrModify(wxCommandEvent&);
    void OnAddrDelete(wxCommandEvent&);
    void OnPhoneAdd(wxCommandEvent&);
    void OnPhoneModify(wxCommandEvent&);
    void OnPhoneDelete(wxCommandEvent&);
    void OnUpdateEMail(wxUpdateUIEvent&);
    void OnUpdateAddress(wxUpdateUIEvent&);
    void OnUpdatePhone(wxUpdateUIEvent&);
    // creates a vertical sizer containing the buttons used with listboxes:
    // add/modify/remove
    wxSizer *CreateLboxButtonsSizer(wxWindow *parent, int idFirstButton);

    // creates a horizontal box sizer containg a label and the control
    wxSizer *CreateLabelSizer(wxWindow *parent, const wxString& label,
                              wxControl *control, bool resizeable = FALSE);
    // returns the label in the lbox for this address/phone
    wxString GetAddressLabel(const wxVCardAddressData& data) const;
    wxString GetPhoneLabel(const wxVCardPhoneData& data) const;

    // adds an address/phone to the list
    void AddAddress(const wxVCardAddressData& data);
    void AddPhone(const wxVCardPhoneData& data);
private:
    // the GUI controls holding vCard data
    wxTextCtrl *m_fullname, *m_firstname, *m_lastname, *m_middlename,
               *m_prefixname, *m_suffixname, *m_orgname, *m_department,
               *m_title, *m_birthday, *m_comments, *m_url, *m_key;
    wxRadioBox *m_keyKind;
    wxListBox *m_emails, *m_addresses, *m_phones;
    wxImageButton *m_photo, *m_logo;
    wxImageList *m_imagelist;
    // the card we're editing
    wxVCard *m_vcard;

    // all addresses/phones
    wxVCardAddresses m_addrData;
    wxVCardPhones m_phoneData;

    bool m_emailDirty, m_addrDirty, m_phoneDirty;
    DECLARE_EVENT_TABLE()
};

Back to Article

Listing Five

// wxDateDialog and wxDateBrowseButton
wxDateDialog::wxDateDialog(wxWindow *parent, const wxString& title,
                           const wxDateTime& dt)
            : wxDialog(parent, -1, title)
{
    wxBoxSizer *sizerTop = new wxBoxSizer(wxVERTICAL);
    wxStaticBoxSizer *sizerAddr = new wxStaticBoxSizer
            ( new wxStaticBox(this, -1, _("&Choose date")), wxVERTICAL );
    m_calendar = new wxCalendarCtrl(this, -1);
    sizerAddr->Add(m_calendar, 1, wxGROW | wxALL, 5);
    sizerTop->Add(sizerAddr, 1, wxGROW | wxALL, 5);
    sizerTop->Add(CreateButtonSizer(wxOK | wxCANCEL), 0,
                  wxALIGN_RIGHT | (wxALL & ~wxRIGHT), 10);
    SetAutoLayout(TRUE);
    SetSizer(sizerTop);
    sizerTop->Fit(this);
    sizerTop->SetSizeHints(this);
    Centre();
}
void wxDateDialog::OnCalendar(wxCalendarEvent& event)
{
    EndModal(wxID_OK);
}
void wxDateBrowseButton::DoBrowse()
{
    wxString str = m_text->GetValue();
    wxDateTime dt;
    if ( str.length() )
        dt.ParseDate(str);

    wxDateDialog dialog(this, _("Select the birthday date"), dt);
    if ( dialog.ShowModal() == wxID_OK )
    {
        m_text->SetValue(dialog.GetDate().FormatISODate());
    }
}

Back to Article

Listing Six

from wxPython.wx import *
class MyFrame(wxFrame):
    def __init__(self):
        wxFrame.__init__(self, None, -1, "Hello Sample", size = (150, 100))
        self.Center()
        b = wxButton(self, -1, "Click Me")
        EVT_BUTTON(self, b.GetId(), self.OnClick)
    def OnClick(self, event):
        dlg = wxMessageDialog(self, "Hello World!")
        dlg.ShowModal()
        dlg.Destroy()
app = wxPySimpleApp()
frame = MyFrame()
frame.Show(true)
app.MainLoop()

 


Related Reading


More Insights






Currently we allow the following HTML tags in comments:

Single tags

These tags can be used alone and don't need an ending tag.

<br> Defines a single line break

<hr> Defines a horizontal line

Matching tags

These require an ending tag - e.g. <i>italic text</i>

<a> Defines an anchor

<b> Defines bold text

<big> Defines big text

<blockquote> Defines a long quotation

<caption> Defines a table caption

<cite> Defines a citation

<code> Defines computer code text

<em> Defines emphasized text

<fieldset> Defines a border around elements in a form

<h1> This is heading 1

<h2> This is heading 2

<h3> This is heading 3

<h4> This is heading 4

<h5> This is heading 5

<h6> This is heading 6

<i> Defines italic text

<p> Defines a paragraph

<pre> Defines preformatted text

<q> Defines a short quotation

<samp> Defines sample computer code text

<small> Defines small text

<span> Defines a section in a document

<s> Defines strikethrough text

<strike> Defines strikethrough text

<strong> Defines strong text

<sub> Defines subscripted text

<sup> Defines superscripted text

<u> Defines underlined text

Dr. Dobb's encourages readers to engage in spirited, healthy debate, including taking us to task. However, Dr. Dobb's moderates all comments posted to our site, and reserves the right to modify or remove any content that it determines to be derogatory, offensive, inflammatory, vulgar, irrelevant/off-topic, racist or obvious marketing or spam. Dr. Dobb's further reserves the right to disable the profile of any commenter participating in said activities.

 
Disqus Tips To upload an avatar photo, first complete your Disqus profile. | View the list of supported HTML tags you can use to style comments. | Please read our commenting policy.