Both Paul McNett and I, the creators of Dabo, come from a long background developing desktop database applications in Visual FoxPro. Most of you probably have never worked with VFP, and many of you probably have never even heard of it, and that's unfortunate, as it is a powerful object-oriented tool for working with data. However, over the years Microsoft (which bought Fox in 1992) has not marketed it at all, and instead pushed more expensive products such as SQL Server, and lately, .Net.
As a result, the market for VFP developers shrank dramatically. Both Paul and I independently started looking around for alternatives, and we both ended up choosing Python for its power and clarity. But there was no equivalent to VFP: nothing where you could create a desktop application that could query and update data. We talked about this at a conference in 2003, and joked that if we couldn't find what we wanted, we should write it ourselves. We had a good laugh at the time, but less than six months later, we started getting serious about it, and began developing the framework.
By the way, there are well over 100,000 Visual FoxPro developers in the USA alone, and also large numbers of developers overseas: Brazil, Russia and the Czech Republic have large, active VFP communities. As VFP becomes more marginalized by Microsoft, and the market for VFP apps dries up even more, these developers will be looking for alternatives. One of our goals is to make the transition to the open source world as easy as possible for these people, and so there are touches of VFP throughout Dabo. Unless you're familiar with VFP you would never notice them, but those VFP developers to whom I've showed Dabo recognize them, and it makes them feel more comfortable with the framework right away.
We're aiming to create a cross-platform framework to enable developers to easily and flexibly create database applications. Our primary focus is on desktop apps, as this is the area in the Python world that is sorely lacking. There are a ton of excellent tools in Python for creating web applications, but few, if any, for creating desktop apps.
We have designed the framework to be a true 3-tier design, separating out the UI and database backend code from the business logic. This, we believe, leads to more robust applications that can better adapt to changes. Switching the backend from MySQL to PostgreSQL will require changes to the data tier code, but should not affect the business rules or the user interface code at all. This design also allows Dabo to work with several UI toolkits, making for a potentially much wider market. We chose wxPython as our initial toolkit to support, because being a two-man development team, we had to narrow our focus. wxPython provides a wide-ranging set of controls that look native on Windows, OS X and Gtk. After we reach our 1.0 release, we plan on developing the classes to work with other toolkits, but for the time being, wxPython is the UI toolkit for Dabo.
Dabo also supports Unicode throughout, and we have the beginnings of a localization framework in place, thanks to one of our contributors, Vladimir Sekissov. He's providing the Russian and English versions, and has made it fairly simple for other languages to be added.
Because Dabo will make developing your apps easier. We've taken the most tedious and error-prone parts of creating an app, and either automated or greatly simplified them. Dabo is created by developers for developers.
The current release of Dabo (as of this writing: February 2005) is 0.3. That seems pretty immature, but we're being very conservative with our release numbering. We have a series of things we plan on completing in order to feel confident in making a 1.0 release, and don't want to use phony release numbers to make us look better than we are.
Having said that, there are applications written in Dabo are already in use in several companies. Of course, it's always risky to use a pre-release product for production work, as the API may still change, but this demonstrates the robustness of its data handling. I have been using Dabo to manage all of the MySQL data on my website for over six months now.
So what's missing? Well, most of the work remaining to be done in Dabo is in the UI layer; the business object and data tier classes are largely complete. Here's a brief list of what we plan on achieving for a 1.0 release:
If you're interested in keeping up with the ongoing development of Dabo, or especially if you'd like to contribute to its progress, be sure to subscribe to the Dabo-Dev email list. All of the design discussions take place there, and all commits to the Subversion repository are also posted to this list. Or if you're just interested in learning more about how to use Dabo in your development work, you can join the Dabo-Users list.
Let's face it: more and more companies are moving away from the days of one single OS. It simply doesn't make sense to lock yourself into a single-platform solution. Dabo code runs without modification on Windows (2000 and XP), Linux/Unix (Gtk), and Mac OS X, and creates native UI controls on each platform. Here is one of our sample apps running on different platforms:
Mac OS X |
Linux / Gtk |
Windows 2000 |
Windows XP |
One of the most powerful concepts in Dabo is that of Data Binding. This refers to the ability to set 2 properties on a control, DataSource and DataField, corresponding to the table and column whose data you want displayed/edited in that control, and automagically have that control display the desired value. This is a two-way binding, so changes to that control can also be saved back to the database. Normally this can be a very tedious process: ensuring that any change to a value in, say, a given TextBox, gets propagated back to the correct column in the correct record of the correct table in the correct database on the correct host. Dabo makes Data Binding fast and easy.
This is one of the places in a data-intensive app where a developer spends a lot of time coding to ensure the integrity of the data. Every app has such requirements: last name cannot be empty; purchase price cannot exceed credit limit; double-entry accounting records must balance; and so forth. Dabo makes it easy to handle this task by creating a single method in the business object where you place this validation code. This method, validateRecord(), gets called for every record before the record is saved to the backend.
One thing that makes writing validation code even simpler is Dabo's convention that allows a bizobj to refer to the values of the columns in the table it is managing as if they were attributes of the bizobj itself. For example, if you were working with a bizobj for your Customer table, and that table had a field named 'last_name', you would enforce a rule that required that this last_name field cannot be empty as follows:
if not self.last_name:
return "Last name cannot be empty"
That's it! Returning any non-empty string from validateRecord() will raise an exception that will prevent the record from being saved. This exception will be caught by the UI, which then displays it to the user.
There are several UI toolkits available to a developer, and they all have their strengths and weaknesses. They also have very different APIs: code written for wxPython won't do anything in TkInter, for example. And many of the APIs are convoluted and confusing.
Dabo addresses these problems by creating a consistent, simplified interface for UI controls, no matter what UI toolkit you're using. In other words, the same code that you would write to work with, say, a TextBox when using wxPython will work if your code had to run with Tkinter. The Dabo classes wrap the different UI's classes, hiding the differences and presenting a consistent interface to the developer.
It's difficult to explain how much simpler Dabo code is than standard UI code, so here's an example that shows wxPython code, and the code to do the exact same thing in Dabo. This example defines a UI window (called a 'frame' in wxPython, 'form' in Dabo), adds a button with text, and displays a message when clicked.
import wx class WxDemoFrame(wx.Frame): def __init__(self, parent, id): # Note the requirement for always passing an 'id'. super(WxDemoFrame, self).__init__(parent=parent, id=id) # If we want to to set the text for a frame, we have # to call 'SetTitle()' self.SetTitle("This is a wx Frame") self.SetSize((300, 400)) btn = wx.Button(self, -1) # Now we want to set the text on the button. This # time we have to remember that buttons use a # method called 'SetLabel()' rather than 'SetTitle()' # as the frame class does. btn.SetLabel("Click Me") # Gotta send tuples to set the size or position. # Even if you only want to change the width or # the y-position. You also have to remember which # comes first: width or height. btn.SetSize((90, 24)) btn.SetPosition((50, 50)) # Here's another of those easy-to-remember # constants. Every control has its own set of event # names you have to remember. btn.Bind(wx.EVT_BUTTON, self.onClick) def onClick(self, evt): # Oh, yeah, those constants are real easy to remember!! dlg = wx.MessageDialog(self, "I've been clicked!", "wx Sample", wx.OK | wx.ICON_INFORMATION) # We have to tell the dialog to show itself... dlg.ShowModal() # and then remember to release it afterwards. dlg.Destroy if __name__ == '__main__': app = wx.PySimpleApp() frm = WxDemoFrame(None, -1) # OK, now we have to tell Python to show the form # Shouldn't this be by default? frm.Show(1) app.MainLoop()
import dabo dabo.ui.loadUI("wx") class DaboDemoForm(dabo.ui.dForm): def initChildObjects(self): # Add the button. We can pass all of the relevant property settings # to the 'addObject()' method, and they will be applied to the resulting # control when it is created. self.addObject(dabo.ui.dButton, Name="btn", Caption="Click Me", Width=90, Height=24, Left=50, Top=50) # Most controls have an obvious event. Menus get selected, # buttons get clicked, check boxes get toggled. Dabo wraps # these primary events into a single event called 'Hit'. Much easier # to remember, and consistent across various controls. self.btn.bindEvent(dabo.dEvents.Hit, self.onClick) def onClick(self, evt): # The dMessageBox class has a number of functions that # handle the most common needs for displaying messages # to the user: info (for non-critical messages); stop (for serious # problems that require immediate attention); and 'areYouSure', # for when you want to confirm a potentially critical action by the # user. Just pass the message and an optional title, and Dabo # handles the rest for you; dabo.ui.dMessageBox.info(message="I've been clicked", title="Dabo Sample") if __name__ == '__main__': app = dabo.dApp() # Just tell Dabo what form class you want to display # when the app starts up. Dabo will handle the rest. app.MainFormClass = DaboDemoForm app.setup() app.start()
Both these examples will run, assuming that you have wxPython and/or Dabo installed. You can see that the Dabo code is much briefer, cleaner and easier to follow. What you don't see, unless you actually run the above code, is that the wxPython application creates the main window and nothing more, while the Dabo version also includes a full menu, supporting the usual cut/copy/paste functions, an 'About...' window, and even a Command Window that allows you to interactively debug your running application!
There are several places just in this short example where the Dabo UI code is much cleaner. Let's just look at the example of displaying the simple message to the user.
In wxPython, you need to create the dialog by not only passing it the message to be displayed and the title, you also have to include constants to tell it what buttons to include and what icon to display. You then have to explicitly show it, and remember to release it after. With Dabo, you simply call the info() function of the dMessageBox module, and the framework handles all the rest for you.
Dabo also makes it easy to create a consistent look in your application through the use of style dictionaries. Every Dabo control has an optional parameter named properties that accepts a dictionary containing the settings you want the controls to have. By passing the same dictionary to multiple controls, you can easily achieve a clean, consistent look. Here's a simple example that will create 3 labels lined up on their left sides, along with a text boxes.
labelDict = {"FontFace" : "Verdana", "FontSize" : 12, "FontBold": True, "Left" : 100 } textboxDict = labelDict.copy() textboxDict["FontBold"] = False textboxDict["Left"] = 150 self.addObject(dabo.ui.dLabel, properties=labelDict, Caption="First", Top=10) self.addObject(dabo.ui.dLabel, properties=labelDict, Caption="Second", Top=30) self.addObject(dabo.ui.dLabel, properties=labelDict, Caption="Third", Top=50) self.addObject(dabo.ui.dTextBox, properties=textboxDict, Top=10) self.addObject(dabo.ui.dTextBox, properties=textboxDict, Top=30) self.addObject(dabo.ui.dTextBox, properties=textboxDict, Top=50)
What's even more powerful than just cutting down keystrokes is that it is now easy to globally change the appearance of your app by only changing things in a single place. This is analogous to the advent of CSS for controlling the appearance of HTML. Before CSS, you had to do extensive search-and-replace if you wanted to change the appearance of your site; with CSS, you change the style definition in one place, and every page that uses that style instantly reflects the change. Dabo has implemented that approach into its UI classes, so that managing the appearance of your app is greatly simplified.
This is even more important in a cross-platform world. You may want a particular font on Windows, and another on the Mac. Just create your property dictionaries dynamically in code in the setup of your app, and that's it! Every form that uses these dicts will now look the way you want it to. With other UIs, you have to change this code in every form you run, and if your requirements ever change, you have to find every place that define these settings, and change each one. Certainly not an enjoyable task, and one that is prone to errors.
Dabo requires:
Probably the easiest way to get started is to run the AppWizard. This wizard asks you a few questions about your data connection, gets a list of tables available in your database and lets you pick which ones you want to work with in your app, and creates all the code needed for a fully-functional Dabo application that can query your data, display the matching results, edit the data, and commit the changes.
There is a publicly-accessible MySQL database available for you to test Dabo with. It is set up as the default connection in the wizard. Feel free to add, edit, or even delete records - the tables are refreshed every so often from backups.
Of course, your freshly-created app will be far from polished. For one thing, the field names will be used for the labels on the app just as they appear in the table. So if you have a field called 'first_name', that's how it will appear in your app. Most users will find this incredibly ugly, preferring something more like 'First Name'.
Second, all of the fields in the table will be included in your app; this is rarely what you want. Some fields, such as primary keys, foreign keys and timestamps won't mean anything to the user, and some other fields may simply not be relevant to the app at hand. You will want to be able to choose which fields appear in your application's forms.
Finally, the default order that the fields appear in your app is that order that the database returns them. This is rarely the order that your users expect to see them.
To address these issues, we've created the FieldSpecEditor. The wizard gets the table and field information from the database, and stores it in an XML file with the extension '.fsxml'. You can certainly edit this file by hand, as it is simply a text file, but the FieldSpecEditor makes this so much easier. You can use it to change the caption for each field, the type of the control that will be used to edit the field's value, and control the order that they appear in (or whether they appear at all). There is a preview button that brings up a form that uses dummy data so that you can see the effect of your changes before saving them.
Once you have the app looking the way you want, you run the main.py file in your app's directory, which brings up the main Dabo window. From here you can edit any of the tables in your app.
Each editing form consists primarily of a three-page PageFrame, with pages for entering your query criteria, seeing the results of your query listed in a grid, and editing individual records.
The Select page allows the user to enter search criteria for the various fields, along with how to match (exact match, begins with, contains, greater than, etc.). Clicking the Requery button starts a process that constructs a valid SQL command from your user's entries, passes that to the backend database class for execution, retrieves the results, and displays them in a grid. You can select the record you want from the displayed results, and edit that record on the third page of the form.
Of course, the wizard-generated apps are not your only option. You can most certainly create your own apps, although doing so will require coding the classes you need by hand. There is one tool that can make defining your database connection easier, though: CxnEditor, the Connection Editor. This tool allows you to enter your connection information and test it, and then save the settings into a special XML file that Dabo uses to connect to database backends. Here's a sample screenshot of the Connection Editor in action:
Once your connection is defined, save it to your app's directory. By default, Dabo will read in any files with the .cnxml extension for connection information. This connection is passed to your business objects (or 'bizobjs', in Dabo-speak), where it is used to create Cursor instances that do the actual connecting to the database.
This is very much a work-in-progress, and is not yet ready for prime time. So what I want to talk about here are the design goals for this tool, and where we are at right now in achieving these.
We both feel that using Sizers to layout your UI interfaces is vastly superior than absolute positioning for many reasons, not the least of which is the difference in fonts and the appearance of controls across the various platforms. You can line up a set of textboxes so that they look perfect in Windows, and when you run that code on the Mac, the controls are not aligned at all. By using sizers you can make user interfaces that are much more consistent across platforms.
As a result, the Dabo Designer will allow you to take a form and add controls to it, handling the positioning of the controls using sizers to control layout. Needless to say, creating a tool that can do this is a lot of work, so it still has a long way to go. As of this writing (February 2005), the Designer can handle single control classes, with live editing of the control's properties via the Prop Sheet, a companion form that displays all the available props for a class, and allows for simple editing. See the screenshot below:
Right now I am working on integrating connection definition files into the Designer, so that the Designer can query the database to get the tables and columns involved. The eventual goal is to present this information to the developer, and allow her to select the fields that are needed, and have them added to the form with the proper labels and control types, all nicely arranged, with all the Data Binding properties filled in accordingly. I don't know if this will be ready by PyConDC 2005, but if it is, I'll be certain to demo it.
Besides data entry and editing, generating reports on the data is a large part of writing a database application. We haven't made any final decisions, but we're actively looking into integrating ReportLab into Dabo. It looks promising, but there is a lot of work to be done before this reporting is fully functional in Dabo.
Dabo is still quite young, but already has significant capabilities. We have lofty goals indeed, and are well aware that implementing these goals will take time. But we feel that we're off to a great start, and that as more developers begin working with Dabo, it will develop the polish that will make it a truly great application framework.
This work is licensed under a Creative Commons License.