Spring Richclient ~ Session Three ~ How did that work then?
At the end of the last session, we'd managed to get the Petclinic application up and running in its own directory, and located the main configuration file (richclient-application-context.xml). We'd also worked out what a few beans in there actually did, although there were many more still to look at. In the next couple of sessions, I'm going to have a closer look at the Rich Client specific beans in richclient-application-context.xml and work out what they do.
This session, let's find out how my "Hello, world!" change actually worked, by following the bean definitions through richclient-application-context.xml. We might even learn something about the Rich Client architecture on the way. Here goes...
The Lifecycle Advisor
The setup wizard from last session came up as the first thing after the splash screen, right? What controls in a Rich Client application which windows come up in which order? Let's do a search for setupWizard in the petclinic code and see what we come up with...
Aha. We briefly mentioned the lifecycle advisor last session and labelled it as an "application flow controller". This is beginning to make sense.
Looking at the source code for this object, I'm guessing the following method is called just before the main window of the application is opened:
Ok, so the "setupWizard" bean is searched for and "executed". Let's have a look at this bean.
The "setupWizard" bean
Here's the bean configuration from richclient-application-context.xml:
This is a simple bean to configure, a stock component of the Rich Client framework (from the class path), taking some license text from the petclinic application. So far, so good. Let's do what we did at the end of the last session in a little more detail, and look under the hood at SetupWizard.java. How does it create that first page?
This addPages() method must be called at some point during creation ... and the first page is a SetupIntroWizardPage. Looking at SetupIntroWizardPage.java, we find the key for "setup.intro.description".
That getMessage() method took quite a while to find - it can be traced right back to ApplicationServicesAccessor.java, a distant ancestor of this class:
This calls getMessage() on the application context. In a standard application context object, such as ours, this method looks for a bean with name "messageSource" and attempts to resolve the message key. Let's have a look at the "messageSource" bean next.
The "messageSource" bean
Familiar Spring territory here. This is a standard Spring ResourceBundleMessageSource. Check the javadocs for ResourceBundleMessageSource if you need more explanation of this one.
Give the above configuration, ResourceBundleMessageSource will try these locations in this order to find the message key we supply:
The last path here refers to a "standard" Rich Client properties files which ships with the Rich Client framework. Looking inside, this defines words for things like "File", "Edit", "Paste", and standard icons used in many applications. The really neat thing is that these are already translated into French, German and Dutch for me, which gives me a whole lot of localisation for free! This framework really is awesome.
Oh look, we've traced our message back through the bean hierarchy. That wasn't too bad at all, was it?
Summary
A quick summary of what we've learnt in this session:
- The petclinic lifecycle advisor controls when windows appear when the application runs, and most likely can be used for many other things.
- The standard spring "messageSource" bean is used by standard Rich Client objects such as SetupWizard to resolve message keys.
- Rich Client provides many standard messages for you, pre-localised in four languages. Neat.
Much of spring configuration we've looked at today looks like boilerplate xml to me - I don't think I'll need to change much at all. The only thing that looks like it needs changing here are the application specific messages.properties files, so that I can add the messages I want to appear.
Next session, we'll have a look at some of the other beans which use "messageSource".
Share
More articles
Spring Richclient ~ That's a wrap
My project is shrink-wrapped in virtual plastic and the client is happy, so that just about wraps up my look at Spring Rich Client.
After writing a full app from conception to completion, I thought that it was a useful and powerful framework deserving of attention. There are plenty of features you get thrown in for free, such as decent localisation support and excellent form binding capabilities. A couple of closing thoughts follow...
The framework needs more documentation. Once you master the basics you're ok, but I get the nagging feeling I missed whole sets of features simply because I didn't know they were there.
A maintained stable release would be a significant step forward. Once the project was easier to download and try out I think many more people would at least evaluate it. The more people using it, the more developers that might come forward, which has to be a good thing for the project.
I hope you enjoyed our little jaunt. Best of luck on your own Spring Rich Client journey.
Update: It looks like Spring: Rich Client have released 0.1.0 on sourceforge; the first stable revision! Should have checked before I wrote this post :) Congratulations to the Rich Client team - hopefully the project will move forward in leaps and bounds now.
Read moreSpring Richclient ~ A Journey
This series of articles documented my journey through the depths of Spring Rich Client in an attempt to learn how it all works.
- Introduction
- Session One: Making Petclinic work
- Session Two: Hello, World!
- Session Three: How did that work then?
- Session Four: Beans, shmeans and services
- Session Five: Applications, windows, views
- Session Six: The View
- Session Seven: Beating the Command Framework into Submission
- Session Eight: Such a Bind
- That's a wrap: Closing Thoughts
Spring Richclient ~ Session Eight ~ Such a bind
Let's look at the binding framework today. I've got an options dialog to write, and it looks like the binding framework could be just the ticket...
For the uninitiated, when we talk about "binding" we mean linking values in a text field on a form to values in our Java class. Essentially we want to turn:
into this:
And when the user hits "Save", we want the values in our java object to change.
This is a standard well-defined problem for which there exists a number of solutions. So what does Spring Rich Client give us to aid us in our quest? Let's have a look.
How do we make a form?
The first example that comes to mind of a view with fields on it is the "owner properties view" in the petclinic sample. This is shown when we right click an owner:
Let's have a look at the code which created this page, from OwnerManagerView.java:
Ok, to create a form, we do the following things:
- Grab the currently selected Owner.
- Create a form model for this Owner. So what's one of those? Turns out that the class has got some documentation - from FormModel.java: Ok, so it's an abstract representation of a form. Fine.
- Create an OwnerGeneralForm, passing a "child page" version of the form model. This makes sense - we've got two tabs, one for "General Info" one for "Address". Rich Client seems to links the "child page" FormModel it creates with the main wizard's FormModel.
- Do a similar thing for the OwnerAddressForm.
- Add both of these forms to a TabbedDialogPage, which I guess does the fancy tab stuff.
- Create a new TitledPageApplicationDialog which takes the TabbedDialogPage we created earlier. We override this dialog to provide stuff to do when it closes.
- Show the dialog!
When we're done with a form, we do the following:
- "Commit" the form; I guess this allows it to reconstruct our data from the form fields.
- Store the updated owner in the clinic.
- Signal to the main view that we might have changed something.
Ok, so we've seen how to create a new form, and how to extract the data. Let's try one of our own, and see how far we get.
Having a go at an options dialog
Ok, first things first - create an options command in commands-context.xml and add it to the edit menu. I was going for the tools menu, but I don't need one for this app, so I'll just call the menu option 'Preferences' and be all 'mac-like' :)
I need a data access object (DAO) wired into the "OptionsCommand", as the command has got to load and save the options from somewhere...
Ok, so we create a new command class in OptionsCommand.java and come to writing doExecuteCommand() (see session seven for details on writing commands). First we need the existing options, and then we need to create a FormModel based on these options, as explained above:
As we're not splitting up the options onto different pages (there are only three for this application!) we don't need any of that compound form model stuff. A simple call to FormModelHelper.createFormModel() is all we need.
We'll come to writing OptionsForm in a minute.
After that, we need a new dialog:
This seems straightforward. We create a new TitledPageApplicationDialog, with a FormBackedDialogPage (and therefore our optionsForm) controlling the contents. We override the onFinish() method to 'commit' the form and save the options in the DAO.
Then we show it:
...and that's it for OptionsCommand! Easy, huh?
Hang on, you're probably thinking: what about supplying the text to go into this dialog? This is all controlled by the FormModel we supplied, our own OptionsForm. Let's take a look at how that is implemented:
I've shown the whole class, simply to show how short it is. The createFormControl starts up a new builder, adds a "url" field, a "user" field and a "password" field and then spits out a JComponent (the basic Swing GUI object, for the uninitiated). There's not much to creating these FormModels, is there?
Note that a lot of what I've done here is based on convention. The names of the rows given to the form builder are expected to exist as JavaBean properties in the object you pass to the model. For example, when I do this in OptionsCommand:
...the Options object must contain getUrl()/setUrl(), getUser()/setUser(), and getPassword()/setPassword() methods for this to work. I'm a big fan of convention over configuration, it saves a lot of hassle.
How to we get those aforementioned messages to work? In the constructor show above, you pass a string detailing what you want the form to be called (mine is called "optionsForm", although it could easily be called "allyourbasebelongtous" if desired). Then, you simply add message keys to messages.properties based on that key:
Note that you can use basic HTML for formatting. Lovely.
The actual field names are controlled by further message keys in messages.properties, the names of which are simply the names of the JavaBean properties of our Options class:
(Password already seems to have an internal Spring Rich Client label set up, so I didn't bother adding it)
And... we're done. We're done? Surely not!
No, we really are done. That wasn't at all hard, was it? When I first implemented this, after I got it compiling, it actually worked first time, too, which shows how little code I needed to write to make it work; if there was any more than a tiny amount of code to write, I would have made a mistake :)
Regular Swing types are probably thinking: "That's it? There must be some catch!" No, that really is all the coding you have to do to get a bound form up and running in Spring Rich Client; and there's not a GridBagLayout in sight. This framework is rather good, isn't it? :)
Next time, we'll probably look the validation framework to ensure the user actually types in valid values...
Read moreSpring Richclient ~ Session Seven ~ Beating the Command Framework into Submission
There's been a bit of a gap between the last session and this one; mostly because I've been actually doing coding rather than just writing about coding. It's always worth actually doing a bit of coding once in a while; it's quite fun really, and as I also happen to like eating most days (as does my family), some real work is necessary from time to time...
Anyhow, I apologise for the delay. On with our journey.
We left off last time looking at the ownerManagerView, and seeing how that was put together. I promised at the end or that session to look at the command framework next, and try and work it out. So here goes.
"Scalpal, please"
Let's dissect the OwnerManagerView command code into its component pieces and see what it's made of. It looks like the OwnerManagerView creates a bunch of "command executors", which define what actually happens when a command is executed by the user. There's a lot of different executors in the OwnerManagerView class. Rather than trying to understand them all at once and fry my brain, I think that the best thing to do would be to head through a custom command called "new owner", and try to understand how it's created and what it does.
How does "new owner" work?
Right, time to find the new owner command. It turns out that it's defined in commands-context.xml. This useful little file maps various different command locations (menus, toolbars, that sort of thing) to actual commands that do stuff. Let's look for the new owner command - it's in there:
It's referenced by the "new menu", which is turn is referenced from the toolbar and the file menu. Neat.
So what does the command actually do? It runs the executor "newOwnerWizard". This bean is defined rather simply in the richclient-application-context.xml file:
Ok, so when you click on "new owner" you get a wizard up (this manifests as a little boxes with some blank fields to fill in). You enter a bunch of information, and when this is finished, this bangs an event over to the main owner view using spring's built-in ApplicationEvent stuff:
(code taken from NewOwnerWizard.java)
The owner view handles this message, tweaks the tree and redraws based on the new owner:
(code from OwnerManagerView.java)
Ok, I followed that (I think). Let's try and do a similar thing for the application I'm writing.
The File List View
I have a main pane which shows a list of files in a directory, called FileListView. I want to have a button which when clicked opens a JFileChooser and returns a directory. This then refreshes my view. Easy enough, you might be thinking. It turned out to be rather tricky unfortunately...
Approach One - fire an event to the application context
Ok, so I thought I'd set up my command like this:
This is a tiny bit of java classage that handles the file dialog (OpenFolderCommand.java):
FolderSelectedApplicationEvent is my own application event to tell us that a new folder is selected.
Ok, so in FileListView, I need this:
Done... right?
Wrong, unfortunately. The problem is that event fires in the application context defined by commands-context.xml, not the main application context defined by richclient-application-context.xml, so I can't talk to the window. What do I do? I do what all experienced and knowledgable techs do: panic and turn to google for help...
Approach Two - Move command to main application context
Luckily someone aeons ago (well, 2004) had the same problem and was smart enough to post to the Spring forums about it. The law of WAEFBUG is proved right again!
Ok, so based on that thread, I now know that A) this stuff should be clearer, and B) I can try moving my command to the main application context. This I do, but now it doesn't show the icons on the toolbar button correctly :( Rather than try and work out why that's the case, let's try another approach...
Approach Three - TargetableActionCommand
Let's do what petclinic appears to do and use a TargetableActionCommand. This blog post helped me out setting one of these up.
So here's the latest definition of the command:
This unfortunately didn't work. I can't wire into the fileListView as it's wrapped up inside a view descriptor object:
I don't want to write some sort of proxy object to handle this in the main application context either - that seems overkill.
Approach Four - Cheat (sort of)
Aha - what about this?
Let's just use the method described in "Approach One", except we'll fire the event at the parent context of the current application context, which just happens to be the main context. This might be cheating, but it works, and I've got a deadline looming.
Summary
Whew! Got there in the end. Here's a summary of the way I did this:
- Define a new command bean "openFolderCommand" in the command context.
- Code OpenFolderCommand, extending ActionCommand, and implementing the ApplicationContextAware interface to get the application context. Code it so that it opens the JFileChooser and fires a FolderSelectedApplicationEvent event at the parent context of its own context.
- Code the FolderSelectedApplicationEvent, which takes a File so we know which directory we picked.
- Handle the event in FileListView.
I'm sure there's a better and more Richclient-ish way to do much of this. If you know of one, please let me know!
Next session, I'll probably be looking at either the Master/Detail stuff, or perhaps getting my app onto Java Web Start successfully.
Read moreSpring Richclient ~ Session Six ~ The View
In this session we will look at the ownerManagerView and its associated descriptor. This is the bit which displays the tree view on the "Owner Manager" screen of the Petclinic sample application. Looks like the author got the bean name right :) (What's the hardest problem in computer science? It's naming a class well, so that the next coder can understand what the class does. Believe it).
Ok, let's have a look at the view descriptor and see how one of those is defined.
This is fairly straightforward - a descriptor has a class name to instantiate, and what looks like a list of properties to apply to that class on creation.
The view is created by the descriptor, using the createView() method. This, amongst other things, adds the view to the 'ApplicationEventMulticaster' (defined in richclient-application-context.xml) if the view of type ApplicationListener. I guess this is so the view can get messages from the outside world if necessary. So far, so good.
So what does this view look like: The main window looks like this, with the ownerManagerView defined in the centre:
Let's see how we define each segment of this view.
View Caption
AbstractView contains a bunch of accessor methods (that's a fancy word for a 'get' method, in case you weren't aware) to define things like caption, description, display name etc. These are mostly got from the descriptor class. In this case, our descriptor doesn't appear to define them - perhaps it uses defaults. Let's skip a few steps and cheat: We'll search for the caption text and see what we come up with.
messages.properties: ownerManagerView.label=&Owner Manager
Aha. Our old friend messages.properties. Let's see what happens if we change this.
messages.properties: ownerManagerView.label=&Chris' Owner Manager
Sure enough:
Looks like to define a caption name, it looks like you define the "<viewName>.label" message. And even better, you can define the name in one place and it's changed across the whole application, including in the menus. Neat.
I'm guessing the icon and other text options are defined in the same way.
Laying out the view
The OwnerManagerView derives from AbstractView. Poking through AbstractView, we find the following:
Ok, so that looks like the method that's called when the view needs to be created. How does OwnerManagerView define this?
Ok, we're in standard Java Swing territory here. First we create a shiny new panel, with a simple layout. We create the owner manager tree, a new scroll pane to hold it, and then add the scroll pane to the panel.
The Tree
The createOwnerManagerTree() method is fairly straightforward Java Swing component construction also, except that there are a number of utility classes and interfaces built into Spring Rich Client which help you out. The FocusableTreeCellRenderer class, for example, makes the tree cells work a little more like windows trees (therefore, how people expect them to work, for better or worse). Choosing what to display in each tree cell is standard Swing API legwork, except that Spring Rich Client makes it easy to pick an icon - see this snippet from the custom treeCellRenderer:
That refers to our plumbed in icon source bean, which we looked at in Session Four. Let's be mischievous and change this icon to the spring logo, and see what happens:
owner.bullet=spring-logo.gif
This is what we get:
Hmm - that doesn't look like the spring logo... But hang on, what's that on standard output?
WARNING: Unable to load image resource at 'class path resource [images/spring-logo.gif]';
returning the broken image indicator.
Oops. Looks like I typed the name incorrectly. Let's try:
owner.bullet=spring-logo.png
That's better. It's looks awful, but that's what you get for sticking so large an image on a tree cell. I'm surprised it worked at all, actually :)
Tree Events
How do we handle events generated by this tree? Check out this cool Spring Rich Client utility class, TreeStatusBarUpdater:
When we select a new node, this inline class allows us to easily supply a String to update the status bar with. That's really neat.
Similar utilities exist for the mouse listeners:
All you do is define whether or not to show the popup, and which menu to show if you do. Creating Java's Swing components in Spring Rich Client seems to be a breeze...
Summary
This session, we learned how to create and lay out views in Spring Rich Client. We looked at the view descriptor and how it uses default labels to display captions and icons. We saw how easy it is to create tree components that feel 'right', and we oohed at the really useful utility classes Spring Rich Client provides to lay out controls and handle events.
Next time, let's have a look at all those commands and executors we've seen inside the OwnerManagerView.java, and try to work all that out.
Read more