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

March 2006
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.

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

Spring Richclient ~ Session Five ~ Applications, windows, views

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

Thanks to the recently discovered Spring Rich Client wiki, I've discoved the basic architecture of a Rich Client application.

After reading the platform overview section on the wiki, I was at a bit of a loss with all the terms, so I thought I'd better draw a diagram. I cranked up OpenOffice Draw and the GIMP, and came up with this:

Basically, we've got an Application object, which contains one or more windows. Each of these contains a page, which in turn contains one or more views laid out upon it. The page manages the views, passing them lifecycle notifications and arranging them as it sees fit.

Ok, that makes more sense now it's in a diagram. Let's have a look at the Petclinic application, and see how that fits into this mould.

Taking a look back at our old friend richclient-application-context.xml, we can now understand Application and ApplicationDescriptor much more clearly. The ApplicationDescriptor simply defines properties of the standard Application object that's instantiated.

We were correct about the PetclinicLifecycleAdvisor: it backs the Application object and allows us to control coarse-grained application behaviour. There's an example of this sort of control on this wiki page which shows us how to add a confirmation window before closing the application. We've also got other examples in the PetclinicLifecycleAdvisor implementation - the "setupWizard" window is thrown up before the main application window is starting. See Session Three for the code.

Ok, so where in Petclinic are the main application windows defined? From running the application, we can clearly see that there's only one of them. Unfortunately looking at richclient-application-context.xml wasn't very helpful. In the end I did a search for ownerManagerView, which I figured must be added to the ApplicationPage somewhere. In my search I noticed that we pass the ownerManagerView into the Lifecycle advisor configuration:

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

Interesting. Time to look up "startingPageId" in ApplicationLifecycleAdvisor.java. The most helpful thing we run into is the afterPropertiesSet() method. This method, if it exists, this is called by the regular Spring Framework to allow people to check values were set correctly at the time of application launch. The relevant bit of Java code is below:

public void afterPropertiesSet() throws Exception {
  final Properties systemProperties = System.getProperties();
  if (systemProperties.get(EXCEPTION_HANDLER_KEY) == null) {
    systemProperties.put(EXCEPTION_HANDLER_KEY, getEventExceptionHandler().getName());
  }
  Assert.state(startingPageId != null,
    "startingPageId must be set: it must point to a page descriptor, or a view descriptor for a single view per page");
  }

The only relevant statement is the last one - it tells us that we need to set startingPageId to a "page descriptor", or a "view descriptor". I'm assuming here that Spring Rich Client uses the term "descriptor" classes to mean objects that describe how to set up other objects.

Ok, so what code actually sets up the page? A search for getStartingPageId leads to the jackpot, in ApplicationLauncher.java:

private void launchMyRichClient() {
  ...
  Application application = (Application)rootApplicationContext.getBean(APPLICATION_BEAN_ID, Application.class);
  application.openWindow(application.getLifecycleAdvisor().getStartingPageId());
  application.getLifecycleAdvisor().onPostStartup();
  ...
}

If I'd remembered ApplicationLauncher, I might have started there at the beginning of this session! This clearly starts the application and opens a window, passing the startingPageId (which refers to the ownerManagerView for PetClinic).

Hang on a minute though: the diagram above clearly states that a window contains a page, which contains a view - you might be asking "How can a window be opened with a view?". The afterPropertiesSet() error message above tells us that if you pass a view descriptor instead of a page descriptor, it'll just create a page with the one view you've described. Excellent.

Summary

We've learnt today how the main windows of applications are defined. It appears that creating your first main window is as simple as passing either PageDescriptor or a ViewDescriptor into the standard ApplicationLifecycleAdvisor. If you pass a PageDescriptor, an ApplicationPage will be created according to the descriptor object you pass. If, however, you pass ViewDescriptor, an ApplicationPage will be created with one view, created again according to the descriptor you passed in.

Next session, we'll have a look at that "ownerManagerView" ViewDescriptor, take it apart, and work out how to change it.

Read more