The noble art of creating property viewers

What is a property viewer anyway
The PropertyViewer class
Methods in PropertyViewer
Essential beans
Existing PropertyViewer subclasses
Which Item class uses which PropertyViewer class?
Styles
For the future: GenericPropertyViewers
10 steps to creating your own PropertyViewer subclass
Improvement issues

What is a property viewer anyway

A PropertyViewer is an object that originates from the server, is sent to the client, and takes care of all GUI-aspects related to a single Item in the White Orb world.  The specific tasks of a PropertyViewer are to:

  1. Be able to produce a GUI-component displaying properties of its item.
  2. Keep track of relevant changes to the item's properties, and keep the GUI updated.
  3. (not supported right now) - let the user change properties using the GUI, and forward such property change requests to the server.

The PropertyViewer class

This is the top-level class for all PropertyViewers.  It implements a simple PropertyViewer that could apply to ANY item, i.e. it displays the top-level properties: name and icon.

The PropertyViewer class also defines the structure of all more specific PropertyViewers (for example PhysicalPropertyViewer or CreaturePropertyViewer), which are to be implemented as subclasses of PropertyViewer.

Here's how an overview of the lifetime of a PropertyViewer:

1. the PropertyViewer is born

 Item has a method called getPropertyViewer(...) which returns an instance of a PropertyViewer who's specific purpose is to render a GUI for that item.

2. The PropertyViewer is sent to the client

The PropertyViewer doesn't really do any good on the serverside.  The client/server mechanism will instead send this object over to the client, and when the client receives it it calls viewer.setContext(...).  This is the client's way of say "Hi viewer, welcome to the client.  Here is your context, so go ahead and initialize yourself.".

3. The PropertyViewer initializes itself

setContext(...) is the "trigger" that tells the PropertyViewer it has arrived safely in the client, and that it is now time to construct the GUI.  The PropertyViewer will do the following in this method:

  1. Fetch a Tile for it's item.  This is available through getTile().  A Tile is a client-side proxy for an Item, so the server has an Item and the client has a corresponding Tile.
  2. Create the GUI.  This is done through the method createGUI(), which is meant to be overridden by subclasses (assuming the subclass wants a different GUI).
  3. Subscribe to property changes from the tile.  The methods getDefaultProps() and getDefaultChildProps() provide information about which properties are of interest to this PropertyViewer (i.e. which properties are being displayed in the GUI and need to be kept up-to-date).  These two methods can of course be overriden by subclasses.

  setContext(...) is not meant to be overridden by subclasses.

4. The client displays the PropertyViewer

Finally the client will probably want to display the properties in the user-interface (why else go to all the trouble of fetching the PropertyViewer from the server?).  PropertyViewer has a method for this: getComponent().  This will return an AWT component ready to display in a window or a panel.  Two things to note here:

Methods in PropertyViewer

 

Name Description To be used by Should be overridden?
setContext(...) see above The client only No.
createGUI() see above Internally Yes, in most cases. If not, the GUI of the subclass will be exactly the same as the superclass.
getComponent() see above The client and internally No.
getNameComponent() Returns the GUI-component displaying the name of the item:
Internally from createGUI(), by subclasses that want to use this component in their GUI. No.
getIconComponent() Returns the GUI-component displaying the icon:
Internally from createGUI() of subclasses (like above). No.
getItemViewerComponent() Returns the GUI-component displaying both the icon and name:
Internally from createGUI() of subclasses (like above). No.
getItem() The item ID integer Whoever No.
getTile() The Tile that this viewer represents Whoever No.
flashTile() Implementation of the TileListener interface.  Should cause the GUI to attract attention to itself by flashing or something.  The current implementation does nothing, in the future some kind of flash will probably be implemented in the TileLabel and TilePanel classes. The client, when for example this Tile is selected or an event occurrs Usually not.
getContext() the obvious... Internally by subclasses. No.
getStyle() The current "style" of this PropertyViewer (see chapter below) Internally from createGUI() of subclasses No.
setTileActionHandler(...) This is the object that decides what happens when the user tries to interact with a Tile (for example clicking on it with the mouse). The client Yes, in most cases.  All Tiles being displayed within the GUI should be given the same TileActionHandler, so this call should be forwarded through the container tree of the PropertyViewer GUI.

Don't forget to call super.setSetTileActionHandler(...)!

getTileActionHandler(...) ... yeah... The client No.
setTitleVisible(...) Sets whether the title component should be visible or not:
Whoever No.
tileChildAdded, tileChildRemoved, tileDeselected, tileDestroyed, tilePropertyChanged, tileSelected These are all "notifications", i.e. the PropertyViewer is being notified of events related to the Tile that have occurred. The client only Yes, in those cases where new behaviour should be defined.  Don't forget to call super.XXX(...) unless you want to kill the existing behaviour. 
getDefaultProps() Returns an array of property constants that this PropertyViewer wants to track (via the methods above).  Internally from setContext(...). Yes, if you need to subscribe to new property values.  If so don't forget to include the superclass' defaultProps. 
getDefaultChildProps() Same as above, but this time it applies to the children of the item.   May return null if not interested in children at all (this is the default implementation) Internally from setContext(...) Yes (as above).

Essential beans

Few propertyviewers should really need to worry about subscribing to events, since plenty of components exist that take care of this automatically.  Here is a list of useful beans (all found under orb.client.beans) when assembling a GUI in the createGUI() method:

TilePanel

Displays the icon of a Tile.  Keeps itself updated if the icon changes. 

TileLabel

Displays the name of a Tile.  Keeps itself updated if the name changes.

PropertyPanel

Yes, a PropertyPanel uses a PropertyViewer. PropertyPanels could be use for "nested" PropertyViewers.  For example, the CreaturePropertyViewer (to the left) contains three tabs.  Each tab contains a PropertyPanel, so CreaturePropertyViewer never actually had to implement code for tracking the contents of those tabs.  Instead, it uses PropertyPanels for it's "held" tile, it's "skills" tile, and it's "effects" tile.  In this case each subpanel has setTitleVisible = false.

So PropertyPanels are useful in any PropertyViewer where more than just one tile is displayed.

TileCollectionPanel

 


This is similar to a PropertyPanel, however it displays only the children of its tile, instead of properties of the tile itself.   The picture to the right is an example of when to use it.  Bag uses a ContainerPropertyViewer (under orb.things).  A ContainerPropertyViewer inherits the icon and name components from PropertyViewer, adds a location component, and also shows contains an embedded TileCollectionPanel at the bottom showing the contents of the bag.

Existing PropertyViewer subclasses

Here is a list of the top-level PropertyViewer subclasses that exist today.  This is good to know, partly because it is good to be able to study their source-code when learning how to implement your own, but mostly because you will need to decide which PropertyViewer subclass to inherit from when creating your own.

PhysicalPropertyViewer

 

This is how a PhysicalPropertyViewer looks like in the current implementation.  Icon, name, location, and effects are displayed.  The icon and name components are inherited from PropertyViewer, so the only additions are the location label and the effects.

Obviously, this is not a long-term solution.  Most players will not notice "effects", so this is mostly for testing purposes.  Later on the PhysicalPropertyViewer will probably be revamped.  But then, of course, most subclasses of Physical should have their own property viewers.

ContentsViewer

 

A ContentsViewer does not show a name and icon by default, but can if desired (it is a boolean flag in the constructor).  A ContentsViewer shows the children of an item.  ContainerNode and all subclasses thereof can provide a ContentsViewer explicitely by calling getContentsViewer(...).
 

ContainerViewer

 

OK, here's the question I feared - what is the difference between a ContainerViewer and a ContentsViewer?  Well, first of all, ContentsViewers are very generic, they are used by ContainerNode and all subclasses thereof, available via the getContentsViewer(). 

The ContainerViewer is a specialized viewer for Containers.  Which raises the question - what is the difference between a Container and a ContainerNode?  Well look at the White Orb hierarchy.  A Container is a Thing is a Physical is a ContainerNode is a Node is an Item.  Get it?  So a Container is more "real", it has a location, effects, etc, like all other Things and Physicals, as opposed to a ContainerNode which is an abstract superclass for pretty much everything in the White Orb universe.

So ContainerViewer is "prettier" and less generic than ContentsViewer.

CreaturePropertyViewer

 

A CreatureProperytViewer inherits the icon, name, and location components from Physical.  After that it adds the HP and the tabset to the right. 

CreaturePropertyViewer is a good demo of how to make a more advanced PropertyViewer.  It is assembled using mostly other beans.

This is ofcourse a temporary GUI.  It will be updated when we have the skill and anatomy system in place. And the effects-tab is there for testing purposes (and for fun!), normally players can't see their own souls... :o).

Which Item class uses which PropertyViewer class?

The PropertyViewer class chosen by each class' implementation of getPropertyViewer depends on how that Item thinks it should be portrayed in a GUI.  If not specified, the PropertyViewer of the superclass is inherited.  This is nice because this means you don't have to implement a PropertyViewer unless you actually want a different GUI.

Here is a summary of the current mappings:
 

Class getPropertyViewer() returns a... getContentsViewer() returns a...
Item PropertyViewer no such method here...
  Node "" "" no such method here...
    ContainerNode "" "" ContentsViewer
      Physical PhysicalPropertyViewer "" ""
        Creature CreaturePropertyViewer "" ""
      Thing PhysicalPropertyViewer "" ""
        Collection "" "" "" ""
          IndividualCollection "" "" "" ""
            Container ContainerPropertyViewer "" ""
              Bag "" "" "" ""

("" "" means 'same as above')

Styles

By now you may have noticed an integer value called "style" being passed around when dealing with PropertyViewers.  This represents the general layout of the PropertyViewer - currently two styles are supported: STYLE_TALL and STYLE_FLAT.

The createGUI() method should adapt itself to the current style setting (can be checked using getStyle()).  This makes it easier to fit the PropertyViewer into the containing GUI (unless the PropertyViewer is floating in its own window).  Look at the player client GUI.

Here you can see examples of the two styles.  On the left is a CreaturePropertyViewer with style PropertyViewer.STYLE_TALL.  On the bottom right is the same CreaturePropertyViewer with style PropertyViewer.STYLE_FLAT.  Thus the same property viewer can be fit into both tall and flat panels.  Later on we may add more such constraint variables or take a more formal approach to size constraints (like specifying constraints in more specific ways, like using coordinates) but for now this is perfectly adequate.

For the future: Generic PropertyViewers

I might someday create a GenericPropertyViewer which dynamically creates a GUI based on what types of properties the Tile has. In order for this to be possible, we must add some more introspection functionality.  These GUIs will probably not be so pretty, but they will be very practical.  Especially when dealing with Tiles that normal players won't be seeing, i.e. admin-related stuff, for which we don't want to spend lots of time building pretty PropertyViewer classes...

But this is for the future.

10 steps to creating your own PropertyViewer subclass

Now you know all there is to know about PropertyViewers, and are burning with eagerness to create your own.  This final chapter will try to describe this in an easy step-by-step manner.

Step 1 - choose an appropriate superclass

There are no strict rules here, it depends entirely on how you want the GUI to look.  Although a general tip is to look at the Item hierarchy and extends the PropertyViewer that your item's superclass uses.  PhysicalPropertyViewer is a nice one to extends, as it is quite simple but yet does add useful stuff like the location label.

Step 2 - create instance variables for each subcomponent

Your GUI will most likely contain a mix of inherited components and new components.  Create instance variables for each new component. Important: make these transient!  Otherwise you will get NotSerializableExceptions when the PropertyViewer is sent from the server to the client.   Here is an example from PhysicalPropertyViewer:

private transient TileLabel location = null;
private transient TileCollectionPanel effects = null;

So PhysicalPropertyViewer  inherits the name and icon components, but has it's own location and effects component.
 

Step 3 - implement createGUI()

First of all, you must let your superclass initialize itself.  This is done using something like this (from PhysicalProperyViewer):

Component superComponent = super.createGUI();

Now you also have a reference to the complete GUI of your superclass.  This is useful if you want to embed the superclass GUI as a whole.  This is what PhysicalPropertyViewer does, since it does contain the complete PropertyViewer GUI (the icon and the name components).

In other cases you may want to embed only parts of the superclass GUI.  If such methods are provided by your superclass (which they should be, if it was well coded) then you might add code like this:

Component effectsComponent = getEffectsComponent();

If you are subclassing PhysicalPropertyViewer then this method is provided.  It gives you the GUI-component that displays the effects.  CreaturePropertyViewer is a subclass of PhysicalPropertyViewer, and it uses this method to fetch the effects panel and place it inside the tabset.  PropertyViewer has similar methods, like getIconComponent() and getNameComponent().

Anyway now you initialize your instance variables and put all these different components together into a new pretty GUI.  The usual messy AWT-code in other words, putting components  into panels etc.  Don't forget to check getStyle() so that you get the right GUI shape.

After putting everything together into one component (typically a Panel), return it.  Congratulations!  This was the most complex bit.

Step 4 - implement setTileActionHandler(...)

This method should apply to all Tiles contained within your GUI, so make sure this call is propagated on to all your sub-tiles.  Make sure you call super.getTileActionHandler(...) at some point. Here is an example from PhysicalPropertyViewer:

public void setTileActionHandler(TileActionHandler handler) {
  if (location != null)
    location.setTileActionHandler(handler);
  if (effects != null)
    effects.setTileActionHandler(handler);
  super.setTileActionHandler(handler);
}

Step 5 - declare which properties you are interested in

Hopefully most of your GUI is made up of beans that keep themselves updated. However there may be a few properties that you need to handle yourself.  If so read on.

Implement getDefaultProps() and (if applicable) getDefaultChildProps().  If you do this then property subscriptions will be automatically setup for you by PropertyViewer.setContext(...).

Step 6 - catch events

Implement the event handler methods for the events that you are interested in (i.e. events that require you to update your GUI, and that are not automatically taken care of by the beans contained within).  Here is a list of all the event handler methods in PropertyViewer.  You are free to override any one.

public void tileSelected(Tile tile);
public void tileDeselected(Tile tile);
public void flashTile(Tile tile);
public void tilePropertyChanged(Tile tile, int propertyCode, Object newValue);
public void tileChildAdded(Tile tile, Tile child);
public void tileChildRemoved(Tile tile, Tile child);
public void tileChildrenAdded(Tile tile, Tile[] children);
public void tileDestroyed(Tile tile);

If you override any of these, make sure you call super.XXX as well, so you don't accidentally kill some existing behaviour.

Step 7 - implement service methods

This is an optional step, and is only there to make life easier for further subclasses, i.e. other classes that want to subclass your class.  These methods let a subclass pick and choose among your GUI elements when putting together its GUI.  Here are examples of service methods (from Physical):

protected Component getLocationComponent();
protected Component getEffectsComponent();
public void setEffectsVisible(boolean visible);
public void setLocationVisible(boolean visible);

The getXXXComponent methods are protected because they are specifically made for the benifit of subclasses.   The setXXXVisible methods are public because they are available to anyone.  The reason for this is that getXXXComponent can be used to damage the GUI, while setXXXVisible methods are more safe.  It is up to you if you want to follow this design principal, but I recommend it.

Step 8 - have a cup of tea

Congratulations, your PropertyViewer is complete!

You've worked hard, I can tell!  Go have a cup of tea and let your brain regain some energy. Earl Gray is best, with two spoons of sugar and a few drops of milk (lab experiments show that this works best).

Step 9 - override getPropertyViewer(...)

Which item-type should use this PropertyViewer?  Override getPropertyViewer(...) in that class so that is uses your shiny new PropertyViewer subclass.  Here is an example from Creature:

public PropertyViewer getPropertyViewer(AgentContext context) {
  return new CreaturePropertyViewer(this, context);
}

Step 10 - test it!

Now you have done everything, and are ready to test!  Modify TestWorld if necessary so that it adds some of your items to the world, run the client, and client on your item. Voilą! I bet you got a NullPointerException!  Haha!  Go back and debug.

Improvement issues

I am quite satisfied with this design.  However there are (of course) a few things to be improved/added:




Henrik Kniberg

Last updated: