A Graphical Shuttle

A graphical shuttle, also know as a Dual Listbox, or Dual Multiselect, or List Builder, or List Shuttle is a graphical entity that allows the user to move items between two lists. It is often used to allow the user to choose from a number of items in a list (usually on the left) and move them to the other list (usually on the right).

Here’s what it looks like in our assignment:

This assignment consists of three parts:

Part 1: A Graphical Shuttle Program

Part 2: A Shuttle Component

Part 3: A Virtual Supermarket

Please do the three parts in the order specified. You will find that the assignment builds on the previous section, so you should complete each part before moving on to the next part.

I want to see your results for each part separately, so I can see how you built things up.

Here’s an important note about how to go about this:

(Much of the following probably won’t make much sense until you’ve read the details in Part 1.)

Take some time to think about what you’re writing.  If you find yourself writing code that looks very similar to code that you’ve already written, then what does that suggest you should do?  

Please do not say “Cut and paste!” !!!!

You will probably find a number of opportunities to fold otherwise repetitive code into a small number of common (most likely private) methods – with suitable method arguments.

For example, you can cause an entry in one list to move to the other list in two ways:

  1. You can select the item, and then click on the appropriate button, or:
  2. You can simply double-click on the item.

Do not write duplicated code for these two approaches!

Another observation:  The actions to move an item from the left list to the right list are identical to the actions necessary to move an item from the right list to the left list, except for which list is the source and which is the target.  Again, try to avoid writing duplicate code!

By the end of this course, I’d like to believe that you can all do appropriate factoring of functionality to come up with a reasonably optimal, minimally code-duplicated solution.  So here’s an opportunity to practice that!


Part 1: A Graphical Shuttle Program

Write a Java application that implements a “Shuttle control”.

It should look like:

In other words, two List boxes — a left List and a right List — with two Buttons placed between them. (The contents of the Lists are just representative of what can be placed in there.)

Note: I want you to write the code to do this; do not merely use your Java IDE (JBuilder, NetBeans, Eclipse, etc.) forms generator to do it! (Although you can learn a lot about how to do it by experimenting with your IDE forms generator, if you have one!! However, that is not the point of this assignment.)

Note: Be sure to place the class in the package shuttle!

Now implement the program actions as follows:

  • The upper button (the one labelled “>>“) is enabled if an item is selected in the left list, and disabled otherwise. (Allow only a single selection.)
  • The lower button (the one labelled “<<“) is enabled if an item is selected in the right list, and disabled otherwise. (Allow only a single selection.)

For example:

  • When the upper button (“>>“) is clicked, the selected item in the left list is removed from the left list and inserted into the right list. Any selection in the right list is not changed.
  • When the lower button (“<<“) is clicked, any selected item in the left list is removed from the right list and inserted into the left list. Any selection in the left list is not changed.
  • In other words, list items are “shuttled” backwards and forwards between the list boxes.
  • After an item is selected in a list and then moved to the other list, the selection in the source list after the move will be as follows:
    • If there is an item immediately following the original item, then it will be selected.
    • Otherwise, if there is an item immediately preceding the original item, then it will be selected.
    • Otherwise, the list is empty, and there can be no selected item.
  • Double-clicking on an item is equivalent to selecting it, and moving it to the other list; it is exactly equivalent in all respects to selecting it, and clicking on the appropriate move button. (This should suggest opportunities for code reuse.)
  • Be sure that clicking on the x in the upper right hand corner of the application window causes the program to exit. (Use the Swing facilities to do this.)
  • When the upper button (“>>“) is clicked, the selected item in the left list is removed from the left list and inserted into the right list. Any selection in the right list is not changed.
  • When the lower button (“<<“) is clicked, any selected item in the left list is removed from the right list and inserted into the left list. Any selection in the left list is not changed.
  • In other words, list items are “shuttled” backwards and forwards between the list boxes.
  • After an item is selected in a list and then moved to the other list, the selection in the source list after the move will be as follows:
    • If there is an item immediately following the original item, then it will be selected.
    • Otherwise, if there is an item immediately preceding the original item, then it will be selected.
    • Otherwise, the list is empty, and there can be no selected item.
  • Double-clicking on an item is equivalent to selecting it, and moving it to the other list; it is exactly equivalent in all respects to selecting it, and clicking on the appropriate move button. (This should suggest opportunities for code reuse.)
  • Be sure that clicking on the x in the upper right hand corner of the application window causes the program to exit. (Use the Swing facilities to do this.)

NOTE: You will be using events; use the JDK 1.1+ event model; do not use the JDK 1.0 event model. (The JDK 1.1+ event model is the one that uses event listeners.)  You will have to investigate what events are generated by the various components you’re using, and handle those events appropriately.
In particular, the JList Swing component has two distinct types of events it can generate.  You will have to read the JList API (javadoc pages) carefully to determine both of them.
You will also most likely need to set up a model for your JList components.  I found DefaultListModel to be a convenient model to use.


Part 2: A Shuttle Component

Now, convert the Shuttle program you wrote in the last assignment into a Shuttle Component that may be added to a container (as other components like JButtonJList, etc. may be added to a container).

Here’s what to do:

  1. Create a Shuttle Component ClassCreate a class called Shuttle, as follows:
    • It should extend JPanel. Why?
    • Create a default constructor for the class. 
      • It should contain roughly the code you placed in the equivalent class in the first section of this assignment. (Things like setting the background color, adding/placing the left and right lists and the two buttons, etc.)
      • However, it should not contain code that moves anything into the two lists. That’s because we want to make those operations driven by other classes that will be making use of Shuttle components.
    • Have the class implement the following methods:
      • public void addToLeft(String item)
      • public void addToLeft(String[] items)
      • public void removeFromLeft(String item)
      • public void removeFromLeft(String[] items)
      • public void addToRight(String item)
      • public void addToRight(String[] items)
      • public void removeFromRight(String item)
      • public void removeFromRight(String[] items)
        (It should be obvious what each of these methods will do.)
    • You’ll have to have the Shuttle class implement the appropriate Listeners for the events that it will have to handle, and create the necessary methods for these Listeners. (Or use equivalent, perhaps inner, classes to handle them.)
  1. Add Support for a ShuttleListener

We’re going to turn our Shuttle into a real component that generates its own events, so that other classes can express interest in listening for and handling those events. To do this, we’ll follow the JDK 1.1+ event model and create:

  • ShuttleEvent class,
  • ShuttleListener class, and
  • ShuttleAdapter class.

What kind of events are we interested in for a Shuttle?  Regardless of the internal details, the only things of external interest are:

  • An item was added to the left list
  • An item was removed from the left list
  • An item was added to the right list
  • An item was removed from the right list

(Note that, if several items are added to a list in one operation (using the addToLeft(String[] items) or addToRight(String[] items) methods, or equivalent remove methods), an appropriate event should be generated for each separate item added.)

What kinds of information are we interested in for each of these events? In each case, the information we wish to convey is the string being added or removed.

So, create a public ShuttleEvent class, in package shuttle, as follows:

  • It should extend from class ComponentEvent. Why?
  • It should contain a set of public static final ints with unique values: LEFT_ITEM_ADDED, LEFT_ITEM_REMOVED, RIGHT_ITEM_ADDED, RIGHT_ITEM_REMOVED
  • A public constructor that accepts the same datatypes as the ComponentEvent constructor, plus the string item that was added to, or removed from, the list. It should save the item string in the ShuttleEvent instance.
  • public getItem() method that returns the item string.

Now, create a public ShuttleListener interface in package shuttle, as follows:

  • It should specify four methods:
    • public void leftItemAdded(ShuttleEvent event);
    • public void leftItemRemoved(ShuttleEvent event);
    • public void rightItemAdded(ShuttleEvent event);
    • public void rightItemRemoved(ShuttleEvent event);

That was easy! Now, create a public ShuttleAdapter class in package shuttle, as follows:

  • What should it extend? Implement?
  • What methods should it contain?

Hint: Take a look at other similar classes in the JDK 1.x class library.

  1. Use the ShuttleListener functionality

Now, we can start to make use of these classes.

  • To the Shuttle class, add the following methods:
    • public void addShuttleListener(ShuttleListener listener)
    • public void removeShuttleListener(ShuttleListener listener)

You will need to implement the code to add and remove a ShuttleListener, and also add and implement the necessary methods within the Shuttle class to cause all the registered ShuttleListeners to be notified when an event of interest happens.Hints: You will have to be very careful to write code that can deliver the event notifications to the registered Listeners, while at the same time handling requests to add and remove ShuttleListeners. I recommend that you use the javax.swing.event.EventListenerList class to implement this;  see its API javadocs for details of how to do this.

  1. Generating Events from the Add/Remove Methods

Each of the methods:

  • public void addToLeft(String item)
  • public void removeFromLeft(String item)
  • public void addToRight(String item)
  • public void removeFromRight(String item)

should not only perform the obvious addition or removal of an item to/from the corresponding list, but should also generate an appropriate event corresponding to the event types listed above, in the ShuttleEvent class. 

By generate an event I mean the following:

  1. Create an instance of ShuttleEvent, with the appropriate constructor arguments. This is the generated event.
  2. Use the EventQueue‘s postEvent() method to cause the event to be “fired” (see here for documentation of postEvent
  3. The result of calling postEvent will be to cause the event to be delivered in the Event Dispatch thread, which will cause the component’s processEvent method to be invoked with this event as a parameter. (See here for documentation on processEvent)  You will need to override the processEvent method in the shuttle component to cause it to process ShuttleEvents — don’t forget to call the processEvent method in the shuttle component’s superclass to process events that are not ShuttleEvents
  4. By analogy with the existing methods such as processComponentEvent, etc., I recommend that you write a processShuttleEvent(ShuttleEvent ev) method to do the processing of ShuttleEvents, called from your overriding processEvent method.  The processShuttleEvent method should be responsible for determining what kind of ShuttleEvent is being delivered, and as a result, which entry point in each of the ShuttleListeners will be called.  This is how your ShuttleEvents are delivered to your  ShuttleListeners.

Note that the methods:

  • public void addToLeft(String[] items)
  • public void removeFromLeft(String[] items)
  • public void addToRight(String[] items)
  • public void removeFromRight(String[] items)

should simply delegate the real processing of each individual item to the appropriate method to add or remove a single item.  Otherwise, you’re making work for yourself!

  1. Test It!!!

By now, you should already have created some kind of test for the functionality you’ve created above. I created a ShuttleTest class that exercised the functionality as thoroughly as I could, and this allowed me to debug where things didn’t work.

In particular, I set up a class that acted as a ShuttleListener, which printed out a suitable message whenever a ShuttleEvent was received.

I expect you to do something very similar.  In fact, I expect you to test all your assignments thoroughly!


Part 3: A Virtual Supermarket

Now write a Java program that uses several of these Shuttle components — a “Virtual Supermarket”.

Here is how it should look:

As you can see, it contains two Shuttle controls, labeled “Fresh Vegetables and Fruits” and “Groceries”, plus a “Shopping Cart” list on the right.

Hint: In order to get the alignment right, you will most likely have to use various components nested within panels, within panels, each using an appropriate choice of layout manager. Experiment!  I expect this learning process to take some time, but I think you’ll find it worth the time.

As you move items in each shuttle control to the right, the item should be replicated in the shopping cart list, so you get a “roll-up” of all the items selected from each shuttle. Similarly, if you move items back to the left in each shuttle control, the corresponding entry should be removed from the shopping cart list.

Once you have the layout done, you’ve done most of the work! All you have to do, then, is supply the appropriate list of items to be added to the left hand side of each shuttle control, and then connect the shuttle control behavior up to the main class, so that you can “intercept” when items are added to, or removed from, the right list of each shuttle control. That, given the functionality you’ve added to the shuttle class as specified above, I think you’ll find it pretty easy to hook things up!

The virtual supermarket should use the shuttle events that are emitted by the shuttles as a result of user actions on those shuttles, in order to populate or to depopulate the Shopping Cart.

Here’s what to do for the main class for this application, SuperMarket:

  • Make it extend JFrame.
  • Choose a class which will implement ShuttleListener which will listen for ShuttleEvents emitted from each Shuttle.
  • SuperMarket will have a main entry point for the class, so you can run it as an application, or you can choose to make it an applet instead (or both).
  • The default constructor, SuperMarket(), should do most of the work in terms of setting up the layout for the appropriate set of components. 
    It will likely take some time to get the layout correct, and you will probably use a bunch of different layout managers in nested containers. I found that it simplified my code to use a number of inner classes, one for each panel in the interface;  that made it easy to isolate the code for a panel, and know where things were happening.  Check that your frame behaves reasonably when you resize it.
    In addition, you’ll need to populate the left lists of the two shuttle components with the appropriate sets of vegetable/fruit or grocery items. Supply the two sets of items in the SuperMarket class.  
    Finally, it should register a ShuttleListener for both shuttle components.
  • In your shuttle listener class, if you are implementing the ShuttleListener interface directly, you must implement all of the four methods specified in that interface. Two of them will do nothing; the two that you truly implement will cause any items added to or removed from the right lists of the two shuttle controls to cause the same items to be entered into, or removed from, the “shopping cart” list.  
  • Consider using a class that extends ShuttleAdapter for this purpose.  You can’t do this if your JFrame or JPanel class is the listener, but If you use a separate class that extends ShuttleAdapter class, then you only have to implement two methods; ShuttleAdapter will already implement the remaining methods (which do nothing).

Once you’ve implemented the basic stuff, like the layout of the above window, the work of connecting things together should be quite simple.

Note: If you implemented Part B correctly, you should need to make zero changes to your Shuttle classes.  You should only be writing the necessary class(es) to use those Shuttle classes.  I don’t want to see any new versions of the Shuttle classes supplied with Part C.  If you can’t get your Part B classes to work in part C, go back to Part B and fix them, and only then return to working on Part C.

YAY! You’re done with this assignment! I hope you learned a lot!