Spring Richclient ~ Session Two ~ Hello World

January 2006
By the way...This content is now pretty old: check the homepage for the latest.

Ok. Today, it's time to look under the hood.

Firstly, I decided to move the petclinic application to its own directory and clean up the paths a bit so that they reflected the usual way I organise java applications. Also, I wanted this to be a standalone application and therefore I needed to remove some of the relative paths in project.properties so that we didn't need the Rich Client source around to compile and run it. Finally, to satisfy my TDD cravings, I added junit test build targets in and created a dummy test to ensure junit was up and running.

After fixing a number of little build.xml bugs, I got the project compiling again in its own directory. So far, so good. Right, time to look at how this all works.

A look at the build.xml shows that the petclinic-standalone.jar we ran in the first session uses PetClinicStandalone.java as its entry point, so let's start with that:

public class PetClinicStandalone {
    public static void main(String[] args) {
        try {
            String rootContextDirectoryClassPath = "/org/springframework/richclient/samples/petclinic/ctx";
            String startupContextPath = rootContextDirectoryClassPath + "/common/richclient-startup-context.xml";
            String richclientApplicationContextPath = rootContextDirectoryClassPath
                    + "/common/richclient-application-context.xml";
            String businessLayerContextPath = rootContextDirectoryClassPath + "/common/business-layer-context.xml";
            String securityContextPath = rootContextDirectoryClassPath + "/standalone/security-context.xml";
            new ApplicationLauncher(startupContextPath, new String[] { richclientApplicationContextPath,
                    businessLayerContextPath, securityContextPath });
        } catch (Exception e) {
            System.exit(1);
        }
}

Ok, that looks straightforward. We hard code a number of paths to what look like XML files of spring beans, and pass them into an "ApplicationLauncher". They're split into two groups: the "startup context file", and an array of "the other context files".

The startup context

I'm guessing a "startup context file" contains special beans to help with the startup of the application. A quick look at ApplicationLauncher.java confirms this; the javadoc for the constructor reads:

Launch the application using the spring application context
at the provided paths for configuration. The startup context
path is loaded first to allow for quick loading of the
application splash screen.

Excellent. That must be what stuck that frog picture on my screen in session one. Let's have a look at the richclient-startup-context.xml file:

  <beans>
    <bean id="splashScreen" class="org.springframework.richclient.application.SplashScreen" singleton="false">
      <property name="imageResourcePath">
        <value>/images/splash-screen.jpg</value>
      </property>
    </bean>
  </beans>

A little more digging in the code shows that that "splashScreen" bean is actually a hard coded bean name in the ApplicationLauncher, so I guess if you want a splash screen in your application, just do exactly as the sample does.

At this point a strong temptation to vandalise splash-screen.jpg with a cheeky "Hello, world!" came over me (that would satisfy my brief for this session in one go, right?). But we wouldn't learn much from that, and I feel like I'm on a roll here.

Right, what about those other application context files? More looking through ApplicationLauncher.java shows me that they're piped into a good ol' ClassPathXmlApplicationContext, familiar to Spring aficionados the world over. One application context is created from all the files: I guess they're split up into different files so different variants of Petclinic (I'm thinking client/server here) can mix and match different beans as they wish.

Let's look at each of these files in turn:

security-layer-context.xml

This file contains a bunch of familiar looking ACEGI Security beans. I'm not going into the details of setting up ACEGI security - being a totally non-invasive security architecture, if you don't want it you shouldn't have to worry about it. At least, that's true for 'regular' Spring, and I'm guessing that it'll be the same here too.

business-layer-context.xml

This is familiar territory for anyone using spring for web applications. We find a data source (petclinic uses HSQL to store its data), a transaction manager, and a DAO in the form of a "clinic" bean. No suprises here.

richclient-application-context.xml

That leaves us with richclient-application-context.xml, which is all new. I have to say at this point I baulked slightly at the sheer number of new beans to learn. But hey, somebody wrote it, so someone must understand it, right? Therefore it must be possible to figure it out...

With that in mind, let's start with the first three beans:

<p>
  <bean id="application"
    class="org.springframework.richclient.application.Application">
    <constructor-arg index="0">
      <ref bean="applicationDescriptor"/>
    </constructor-arg>
    <constructor-arg index="1">
      <ref bean="petclinicLifecycleAdvisor"/>
    </constructor-arg>
  </bean></p>
  <p>
  <bean id="applicationDescriptor" class="org.springframework.richclient.application.support.DefaultApplicationDescriptor">
    <property name="version">
      <value>1.0</value>
    </property>
    <property name="buildId">
      <value>20041025001</value>
    </property>
  </bean>
  </p>
  <p>
    <bean id="petclinicLifecycleAdvisor"
    class="org.springframework.richclient.samples.petclinic.PetClinicLifecycleAdvisor">
<property name="windowCommandBarDefinitions">
      <value>org/springframework/richclient/samples/petclinic/ui/commands-context.xml</value>
    </property>
<property name="startingPageId">
      <value>ownerManagerView</value>
    </property>
  </bean></p>

From looking at ApplicationLauncher, "application" is a "magic name" for a bean - that is, the framework looks specifically for a bean called "application" in one of its XML files. It's set up with two other beans - applicationDescriptor and a lifecycle advisor. ApplicationDescriptor looks very straightforward - a version and a build number. I'm guessing that goes in the title, and perhaps an automated "Help - About" screen later on (here's hoping, anyway :).

The lifecycle advisor looks a little more involved. It appears to be configured with another xml file, which I'll look at in a bit, and a "starting page id" which refers to an "owner manager view". Looking at ApplicationLauncher (again), it appears that on launch a window is opened with this "owner manager view" page displayed first. After that, onPostStartup() is called on the lifecycle advisor.

From this it looks like the lifecycle advisor is used here as the application flow manager. The two methods overridden for the petclinic application look like they create the Setup Wizard and the login screen. I guess that there are many things one could add to this to perform custom actions at various points within the application.

Look and Feel

At this point I notice that the JGoodies support I raved about earlier is neatly encapsulated in one bean:

  <bean id="lookAndFeelConfigurer"
    class="org.springframework.richclient.application.config.JGoodiesLooksConfigurer">
<property name="popupDropShadowEnabled" value="false" />
<property name="theme">
      <bean class="com.jgoodies.looks.plastic.theme.ExperienceBlue"/>
    </property>
  </bean>

Searching through the Rich Client codebase turns up another magic bean name in ApplicationServices.java:

public static final String LOOK_AND_FEEL_CONFIGURER_BEAN_ID = "lookAndFeelConfigurer";

There's a whole lot of other magic bean names in there - if you are wondering what to name a bean, that's a good place to start looking!

Changing the Setup Wizard

Most of the rest of the beans I think I'll have to leave for this session, save the last one - the "setupWizard":

  <bean id="setupWizard"
    class="org.springframework.richclient.application.setup.SetupWizard">
<property name="licenseTextLocation">
      <value>/org/springframework/richclient/samples/petclinic/license.html</value>
    </property>
  </bean>

If I can modify the wizard to show a "Hello, World!" page, then I'll be happy.

Looking at the code for SetupWizard, I notice that it adds a SetupIntroWizardPage object. This object references a number of messages, including "setup.intro.description". Hah! I remembered that richclient-application-context.xml had some references to "message source" type beans, but rather than work all that stuff out at this stage, I cheat and search the whole source tree for "setup.intro.description". This turns up a reference in petclinic/ui/messages.properties:

setup.intro.description=The petclinic sample application demonstrates the base capabilities of Spring's Rich Client Project, built on standard J2SE Swing.

Right, time to fulfill the brief:

setup.intro.description=Hello, world! This page shows that I can trivially change a text file and by making text appear differently, I can impress my peers without really knowing what I'm doing.

A quick recompile, and here's the result:

It's not that impressive, I grant you. But at least it shows that if a Rich Client application is written correctly, you can leverage Spring's built in message support. This is vital for things like localisation.

Summary

A quick summary of what we've learnt in this session:

  • To launch a Spring Richclient Application, you use some boilerplate code in a standard main() method to initialise an ApplicationLauncher with some Spring beans XML files.
  • You may include an optional startup XML file containing one specially named bean for a splash screen.
  • Many Rich Client specific beans in the XML files are specially named and are probably required to be present. This means I'll most likely copy the Rich Client specific file and modify it, rather than creating from scratch.
  • Look and feel configuration is trivially handled through a specially named bean in the XML configuration files.
  • The setup wizard shipped with the Rich Client framework uses standard spring messaging by default, allowing us to trivially customise the text.

Next session, we'll look at some those of the other beans in richclient-application-context.xml and work out what on earth they're for. Stay tuned.

Share


More articles

Spring Richclient ~ That's a wrap

By the way...This content is now pretty old: check the homepage for the latest.

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 more

Spring Richclient ~ A Journey

By the way...This content is now pretty old: check the homepage for the latest.

This series of articles documented my journey through the depths of Spring Rich Client in an attempt to learn how it all works.

Read more

Spring Richclient ~ Session Eight ~ Such a bind

By the way...This content is now pretty old: check the homepage for the latest.

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:

  public class TrafficWarden {
    String name = "Trevor the Traffic Warden";
    String parkingTicketCount = 124023;
  }

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:

  public void execute() {
    final Owner owner = getSelectedOwner();
    ownerFormModel = FormModelHelper.createCompoundFormModel(owner);
    ownerGeneralForm = new OwnerGeneralForm(FormModelHelper.createChildPageFormModel(ownerFormModel, null));</p>
<p>    compositePage = new TabbedDialogPage("ownerProperties");
    compositePage.addForm(ownerGeneralForm);
    compositePage.addForm(new OwnerAddressForm(FormModelHelper.createChildPageFormModel(ownerFormModel, null)));</p>
<p>    TitledPageApplicationDialog dialog = new TitledPageApplicationDialog(compositePage, getWindowControl()) {
      protected void onAboutToShow() {
        ownerGeneralForm.requestFocusInWindow();
        setEnabled(compositePage.isPageComplete());
      }</p>
<p>      protected boolean onFinish() {
        ownerFormModel.commit();
        clinic.storeOwner(owner);
        ownersTreeModel.nodeChanged(getSelectedOwnerNode());
        return true;
      }
    };
    dialog.showDialog();
  }

Ok, to create a form, we do the following things:

  1. Grab the currently selected Owner.
  2. 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:
          A form model represents the state and behavior of a form independently from
          the UI used to present the form.
    Ok, so it's an abstract representation of a form. Fine.
  3. 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.
  4. Do a similar thing for the OwnerAddressForm.
  5. Add both of these forms to a TabbedDialogPage, which I guess does the fancy tab stuff.
  6. Create a new TitledPageApplicationDialog which takes the TabbedDialogPage we created earlier. We override this dialog to provide stuff to do when it closes.
  7. Show the dialog!

When we're done with a form, we do the following:

  1. "Commit" the form; I guess this allows it to reconstruct our data from the form fields.
  2. Store the updated owner in the clinic.
  3. 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' :)

<bean id="optionsCommand" class="uk.co.myco.myproj.command.OptionsCommand">
<property name="optionsDao"><ref bean="myDao"/></property>
</bean>
...
<bean id="editMenu" class="org.springframework.richclient.command.CommandGroupFactoryBean">
<property name="members">
<list>
      <value>selectAllCommand</value>
      <value>separator</value>
      <ref bean="optionsCommand"/>
    </list>
  </property>
</bean>

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:

final Options opt = optionsDao.getOptions();
optionsForm = new OptionsForm(FormModelHelper.createFormModel(opt));

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:

TitledPageApplicationDialog dialog = new TitledPageApplicationDialog(new FormBackedDialogPage(optionsForm), null) {
  protected boolean onFinish() {
    optionsForm.commit();
    optionsDao.storeOptions(opt);
    return true;
  }
};

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:

dialog.showDialog();

...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:

public class OptionsForm extends AbstractForm
{
  public OptionsForm(FormModel model)
  {
    super(model, "optionsForm");
  }</p>
<p>  protected JComponent createFormControl()
  {
    TableFormBuilder builder = new TableFormBuilder(getBindingFactory());
    builder.add("url");
    builder.row();
    builder.add("user");
    builder.row();
    builder.add("password");
    return builder.getForm();
  }
}

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:

final Options opt = optionsDao.getOptions();
optionsForm = new OptionsForm(FormModelHelper.createFormModel(opt));

...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:

optionsForm.title=Options
optionsForm.description=Change options <b>Note:</b> Clicking on 'OK' will save the options.<br />

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:

url.label=The U&amp;rl
user.label=&amp;Username

(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 more

Spring Richclient ~ Session Seven ~ Beating the Command Framework into Submission

By the way...This content is now pretty old: check the homepage for the latest.

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:

  <bean id="newOwnerCommand"
    class="org.springframework.richclient.command.TargetableActionCommand">
<property name="commandExecutor">
      <ref bean="newOwnerWizard"/>
    </property>
  </bean>

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:

  <bean id="newOwnerWizard"
	class="org.springframework.richclient.samples.petclinic.ui.NewOwnerWizard">
<property name="clinic">
      <ref bean="clinic"/>
    </property>
  </bean>

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:

 protected boolean onFinish() {
   Owner newOwner = (Owner)getNewOwner();
   clinic.storeOwner(newOwner);
   getApplicationContext().publishEvent(
     new LifecycleApplicationEvent(LifecycleApplicationEvent.CREATED, newOwner));
   return true;
 }

(code taken from NewOwnerWizard.java)

The owner view handles this message, tweaks the tree and redraws based on the new owner:

public void onApplicationEvent(ApplicationEvent e) {
  if (e instanceof LifecycleApplicationEvent) {
    LifecycleApplicationEvent le = (LifecycleApplicationEvent)e;
    if (le.getEventType() == LifecycleApplicationEvent.CREATED &amp;&amp; le.objectIs(Owner.class)) {
      if (ownersTree != null) {
        DefaultMutableTreeNode root = (DefaultMutableTreeNode)ownersTreeModel.getRoot();
        root.add(new DefaultMutableTreeNode(le.getObject()));
        ownersTreeModel.nodeStructureChanged(root);
      }
    }
  }
}

(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:

  <bean id="openFolderCommand"
        class="uk.co.mintcontent.uploader.command.OpenFolderCommand"/>

This is a tiny bit of java classage that handles the file dialog (OpenFolderCommand.java):

public void doExecuteCommand()
{
  final JFileChooser fc = new JFileChooser();
  fc.setFileSelectionMode(JFileChooser.DIRECTORIES_ONLY);
  int returnVal = fc.showOpenDialog(null);
  if(returnVal == JFileChooser.APPROVE_OPTION) {
    applicationContext.publishEvent(new FolderSelectedApplicationEvent(fc.getSelectedFile()));
  }
}

FolderSelectedApplicationEvent is my own application event to tell us that a new folder is selected.

Ok, so in FileListView, I need this:

public void onApplicationEvent(ApplicationEvent e)
{
  if (e instanceof FolderSelectedApplicationEvent)
  {
    FolderSelectedApplicationEvent fs = (FolderSelectedApplicationEvent)e;
    directory = fs.getDirectory();
    drawFileList();
  }
}

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:

  <bean id="openFolderCommand"
	class="org.springframework.richclient.command.TargetableActionCommand">
<property name="commandExecutor">
      <ref bean="fileListView"/>
    </property>
  </bean>

This unfortunately didn't work. I can't wire into the fileListView as it's wrapped up inside a view descriptor object:

  <bean id="fileListView"
        class="org.springframework.richclient.application.support.DefaultViewDescriptor">
<property name="viewClass">
    <value>uk.co.mintcontent.uploader.ui.FileListView</value>
  </property>
  </bean>

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?

  applicationContext.getParent().publishEvent(
    new FolderSelectedApplicationEvent(fc.getSelectedFile()));

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:

  1. Define a new command bean "openFolderCommand" in the command context.
  2. 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.
  3. Code the FolderSelectedApplicationEvent, which takes a File so we know which directory we picked.
  4. 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 more

Spring Richclient ~ Session Six ~ The View

By the way...This content is now pretty old: check the homepage for the latest.

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.

<bean id="ownerManagerView"
	class="org.springframework.richclient.application.support.DefaultViewDescriptor">
<property name="viewClass">
		<value>org.springframework.richclient.samples.petclinic.ui.OwnerManagerView</value>
	</property>
<property name="viewProperties">
		<map>
			<entry key="clinic">
				<ref bean="clinic"/>
			</entry>
		</map>
	</property>
</bean>

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:

protected abstract JComponent createControl();

Ok, so that looks like the method that's called when the view needs to be created. How does OwnerManagerView define this?

protected JComponent createControl() {
   JPanel view = new JPanel(new BorderLayout());
        createOwnerManagerTree();
        JScrollPane sp = new JScrollPane(ownersTree);
        view.add(sp, BorderLayout.CENTER);
        return view;
    }

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:

this.setIcon(getIconSource().getIcon("owner.bullet"));

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:

  ownersTree.addTreeSelectionListener(new TreeStatusBarUpdater(getStatusBar()) {
    public String getSelectedObjectName() {
      Owner selectedOwner = getSelectedOwner();
      if (selectedOwner != null) {
        return selectedOwner.getFirstName() + " " + selectedOwner.getLastName();
      }
      else {
        return "Owners";
      }
    }
  });

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:

  ownersTree.addMouseListener(new PopupMenuMouseListener() {
    protected boolean onAboutToShow(MouseEvent e) {
      return !isRootOrNothingSelected();
    }</p>
<p>    protected JPopupMenu getPopupMenu() {
      return getSelectedOwner() != null ? createOwnerPopupContextMenu() : createPetPopupContextMenu();
    }
  });

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