Sponsored Link •
|
Summary
Combine the power of Python with the polish of Flash to create a desktop application. This approach will also work with any language that has support for creating an XML-RPC server (Java, Ruby, C++, to name a few). Also, The XML-RPC server can easily be on another machine.
Advertisement
|
I have been a Python enthusiast for over 10 years. Its power and expressiveness has made it my first choice when solving my own programming problems.
However, whenever I've wanted to create a program with a user interface -- an end-user application, for example -- I've had problems. There have always been too many choices for Python GUI libraries, and each one has its own idiosyncrasies. I've studied most of them to one degree or another and over time WxPython (based on WxWindows) seems to keep pulling in front of the pack, but in my own experience I've found it far from the ideal solution. Mostly it seems like too much work, which is true for most of the Python GUI libraries.
The more I work with it, the more I find Flex to be preferable to any other GUI solution that I've explored. As I learn more about Actionscript, I gain greater appreciation for all the effort invested by the designers of that language to make life easy for the programmer. The combination of MXML and Actionscript is amazingly well-balanced; MXML keeps you at the "big picture" level most of the time, and you can easily drop down into Actionscript to gain detailed control. Flex was designed with user interfaces at the forefront, so it includes true properties and events as well as "data binding" to automatically move data from one place to another when something changes. It's unfortunate features like this never made it into Java.
And then there's Flexbuilder. Built atop Eclipse, this provides an incredibly powerful development environment with autocompletion, integrated help, debugging, and more. I haven't seen support for any of the Python GUI libraries that even comes close.
There are numerous RPC solutions which allow you to cross between one language and another, but I've had the most experience -- and the most satisfying experiences -- using XML-RPC. So far it's solved all the problems that I've encountered, and it's simple to understand and straightforward to use. And it's an excellent solution for distributed systems, since you can effortlessly cross over machine boundaries (you do, however, need to come up with your own callback approach when starting long-running processes on remote machines).
Python has the philosophy of "batteries included," which means that there's a very good chance that whatever you need is part of the standard library. This isn't just convenient; the standard libraries tend to get much greater focus and support than open-source projects, so the quality of the code tends to be better.
The SimpleXMLRPCServer library allows you to easily create a server. Here's about the simplest server you can create, which provides two services to manipulate strings:
import sys
from random import shuffle
from SimpleXMLRPCServer import SimpleXMLRPCServer
class MyFuncs:
def reverse(self, str) :
x = list(str);
x.reverse();
return ''.join(x);
def scramble(self, str):
x = list(str);
shuffle(x);
return ''.join(x);
server = SimpleXMLRPCServer(("localhost", 8000))
server.register_instance(MyFuncs())
server.serve_forever()
You can publish regular functions (not associated with a class), but I find the class approach is cleaner. This particular server is located on the current machine at port 8000, and that's all the client needs to know.
Once the server is started, we need to make a client that will connect to the server and call the services. This is also very easy to do with Python using the standard ServerProxy class in the xmlrpclib library:
from xmlrpclib import ServerProxy
server = ServerProxy("http://localhost:8000")
print server.reverse('giraffe')
print server.scramble('giraffe')
Once you make a connection to the server, that server acts like a local object. You call the server's methods just like they're ordinary methods of that object.
This is about as clean an RPC implementation as you can hope for (and other Python RPC libraries exist; for example, CORBA clients). But it's all text based; not very satisfying when trying to create polished applications with nice GUIs. What we'd like is the best of all worlds -- Python (or your favorite language) doing the heavy lifting under the covers, and Flex creating the user experience.
One thing I've found a little puzzling about Flex is the decision about which libraries are included in the standard distribution. For example, in earlier versions there was an MP3 player component, but for some reason this has vanished (even thought the Flash player itself directly supports MP3). And I personally like XML-RPC a lot, so it would seem logical (to me) to include that as well. Fortunately, other people have written replacements, and the Flex packaging mechanism makes these easy to incorporate into your own projects.
After hunting around, I found an Actionscript XML-RPC client library, along with a description and an example, here. It was written by "Akeem"; I basically worked from his code but I trimmed it down to make it easier to read.
To use the library, download it and unpack it somewhere. The package includes all the source code and the compiled as3-rpclib.swc library -- the .swc extension indicates an archive file, and pieces of this library can be pulled out and incorporated into your final .swf. To include the library in your project, you must tell Flexbuilder (you can get a free trial or just use the free command-line tools, and add on the Apollo portion) where the library is located by going to Project|Properties and selecting "Apollo Build Path," then choosing the "Library path" tab and pressing the "Add SWC..." button. Next, you add the namespace ak33m to your project as seen in the code below, and you're ready to create an XMLRPCObject.
Note: the only reason I used Apollo here was that I was thinking in terms of desktop applications with nice UIs. You can just as easily make it a Flex app.
Here's the entire Apollo application as a single MXML file, which I'll explain in detail:
<?xml version="1.0" encoding="utf-8"?>
<mx:ApolloApplication xmlns:mx="http://www.adobe.com/2006/mxml"
xmlns:ak33m="http://ak33m.com/mxml" layout="absolute">
<mx:Form>
<mx:FormHeading label="String Modifier"/>
<mx:FormItem label="Input String">
<mx:TextInput id="instring" change="manipulate()"/>
</mx:FormItem>
<mx:FormItem label="Reversed">
<mx:Text id="reversed"/>
</mx:FormItem>
<mx:FormItem label="Scrambled">
<mx:Text id="scrambled"/>
</mx:FormItem>
</mx:Form>
<ak33m:XMLRPCObject id="server" endpoint="http://localhost:8000"/>
<mx:Script>
<![CDATA[
import mx.rpc.events.ResultEvent;
import mx.rpc.events.FaultEvent;
import mx.rpc.AsyncToken;
import mx.controls.Alert;
import mx.collections.ItemResponder;
private function manipulate() : void {
server.reverse(instring.text).addResponder(new ItemResponder(reverseResult, onFault));
server.scramble(instring.text).addResponder(new ItemResponder(scrambleResult, onFault));
}
private function reverseResult(event : ResultEvent, token : AsyncToken = null) : void {
reversed.text = event.result.toString();
}
private function scrambleResult(event : ResultEvent, token : AsyncToken = null) : void {
scrambled.text = event.result.toString();
}
private function onFault (event : FaultEvent, token : AsyncToken = null) : void {
Alert.show(event.fault.faultString, event.fault.faultCode);
}
]]>
</mx:Script>
</mx:ApolloApplication>
MXML is a higher-level abstraction of an application than is Actionscript, but the compiler translates MXML into Actionscript, and you can choose to write your entire application in Actionscript instead (if you want to do a lot of extra work -- you don't gain anything from it).
An MXML file is proper XML, so it begins with the standard XML tag. The body begins with a tag that determines whether it is a Flex or Apollo application, followed by XML namespaces. The first namespace is the standard mx that you'll see in every application, but the second ak33m is the special one added to use Akeem's XMLRPCObject. This is a good example of how to incorporate an external library (you can also just place the source file in your project directory).
The focus of MXML is organizing and laying out components. Note that the main body tag includes a layout attribute; "absolute" means that the components will be glued to their positions regardless of the size or proportion of the application.
MXML provides support for form creation via various Form... tags; it's possible to create these more verbosely, but notice that using the Form... tags requires less work than it would in HTML (there are also helpful fancy components like calendar date choosers, and a number of standard validators which automatically check user input). The fact that the result will look the same under every browser on every platform, without any extra effort, makes creating forms this way very appealing.
Each FormItem can have a label, and it neatly organizes the label with the item (something that would take a table in HTML). The TextInput component allows the user to enter text, while the Text component is for display. I'm just showing you the very basic use of these components; they can do a lot more.
Every component can be given an id so it can be referenced elsewhere in the program; it's like a variable name for that instance of a component. In this program I only give ids to components that I actually reference.
Each component supports a set of events, and you can attach functionality to an event. In the cast of the TextInput, the change event (fired when the text in the input box is changed in any way) calls the manipulate() method, defined at the bottom of the program.
The XMLRPCObject, in this case, only needs an id and the "endpoint" to tell it where the server is.
A Script object is wrapped in CDATA because it may contain special characters that need to be escaped. Here, you see Actionscript code embedded directly into the MXML file, but it could also have been placed in an external .as file and referenced from within the MXML.
One of the first things you'll notice about Actionscript, as a Java programmer, is how remarkably similar it is to Java. Many of the basic concepts and even keywords are the same, which makes it relatively easy for a Java programmer to learn. However, Actionscript often makes things a lot easier than Java does -- for example, while packaging and import statements are very similar, you are not faced with the nightmares of Java's CLASSPATH.
Some things are different. As you can see, it's possible to have free-standing functions. And when you define a function, you must actually use the function keyword (Actionscript is actually a superset of Javascript). Type declarations are optional (although Flexbuilder issues warnings, and I like my warnings to be useful so I add the necessary syntax); without them you get dynamic typing. Type declarations are expressed after type identifiers or function argument lists, following a colon.
Note the manipulate() function, which takes no arguments and returns void. Remember we set up the TextInput instring to call manipulate() whenever the input text is changed. When this method is called, it uses the server XMLRPCObject to call both the methods available on the server. At first glance, it looks similar to the Python client.
However, the Python client was making synchronous calls -- it waited for the XML-RPC server to return the result before continuing. Flex is set up for asynchronicity, which means that it can initiate a call and continue processing while that call runs. The problem with asynchronicity is that you need to somehow catch the result, and the solution is to install a callback function which will automatically be called when the result comes back. Flex comes with builtin support for installing asynchronous callbacks, as you can see from the import statements.
The callbacks are attached as ItemResponders to the object returned from the XML-RPC call, using the addResponder() method. The first argument of the ItemResponder constructor is the function to be called upon success, the second argument is the function to be called in the event of failure. For both calls we are only interested in the first argument that comes back, which contains the result value from the XML-RPC call (from the code, you can see that it's possible to construct more complex callbacks). All the "success" methods do is insert the results in the appropriate text fields.
One of the benefits of this approach is that the UI is not only fast, it's completely separated from the implementation of the underlying program logic, which makes it easier to apply performance improvements. In Python 2.5, for example, the new ctypes library makes it very easy and fast to call DLLs written in another language, so spot performance tuning becomes quite easy. If more dramatic measures become necessary, the approach used in this article allows you can swap out the underlying application without changing the UI. In any event, remember that UI events are rarely the bottleneck in a program's performance, and even if you do run into issues -- dealing with graphics, for example -- Flash is designed to optimize this kind of performance.
For me, the promise of this approach is in programmer productivity. You can write the underlying program logic in using the most productive language for the job, and produce the UI with a system (Flex) that's optimized for that purpose.
I'm currently reading Rich Internet Applications with Adobe Flex and Java by Fain, Rsputnis and Tartakovsky, and Programming Flex 2 by Kazoun and Lott, although there are lots of other books out there on Flex and Actionscript.
Watch my calendar for upcoming Flex/Apollo events as well as the Rich Internet Application Summit.
Have an opinion? Readers have already posted 37 comments about this weblog entry. Why not add yours?
If you'd like to be notified whenever Bruce Eckel adds a new entry to his weblog, subscribe to his RSS feed.
Bruce Eckel (www.BruceEckel.com) provides development assistance in Python with user interfaces in Flex. He is the author of Thinking in Java (Prentice-Hall, 1998, 2nd Edition, 2000, 3rd Edition, 2003, 4th Edition, 2005), the Hands-On Java Seminar CD ROM (available on the Web site), Thinking in C++ (PH 1995; 2nd edition 2000, Volume 2 with Chuck Allison, 2003), C++ Inside & Out (Osborne/McGraw-Hill 1993), among others. He's given hundreds of presentations throughout the world, published over 150 articles in numerous magazines, was a founding member of the ANSI/ISO C++ committee and speaks regularly at conferences. |
Sponsored Links
|