HowTos Display
Aus Salespoint
FormSheet
Create a FormSheet
Description: Although it is quite easy to initialize and display a FormSheet, it is a bit difficult to describe how to do it because of the vast opportunities to display them and the many possibilities to use the FormSheets and combine them with Components or even JComponents. The first thing you should do before you create your own FormSheet is to consult the framework API and take a closer look at FormSheet. There you will find all methods and of course the subClasses of FormSheet, like the LogOnForm, the MessageFormSheet, ListViewFormSheet or the TableForms, which are all specialized FormSheets of great use. Talking about the elements, every FormSheet needs a caption, which is normally defined by calling the constructor and at least some JComponents to display. These can be put together either by a FormSheetContentCreator or by creating and overwriting methods within a new FormSheet instance before or using it's methods during runtime. It is though recommended to use the ContentCreator as shown in this example, for it is able to serialize the Components and all their properities, esp. their behaviour, which will be lost if you just use FormSheet methods and serialize the Shop's status.
ToDo:
- Instantiate a subclass of FormSheetContentCreator.
- Add the constructor and call the superclass to inherit its methods.
- Add the protected void createFormSheetContent(FormSheet fs) method and therein add/remove your components to the FormSheet.
- Add the FormSheetContentCreator to your FormSheet class by calling the method addContentCreator(FormSheetContentCreator or by initializing in the constructor.
Example Source Code:
FormSheetContentCreator class:
1
public class DisplayFormSheetContentCreator extends FormSheetContentCreator
{
2
public DisplayFormSheetContentCreator()
{
super();
}
3
protected void createFormSheetContent(FormSheet fs)
{
// add/remove your components here
fs.removeAllButtons();
fs.addButton("TutorialButton", 1, new ArchitectureCustomAction());
}
}
Class that uses a FormSheet:
4
// adding the FormSheetContentCreator in FormSheet constructor
FormSheet fs = new FormSheet("DisplayFormSheet",
new DisplayFormSheetContentCreator(),
false);
// adding the FormSheetContentCreator by using the addContentCreator method
SingleTableFormSheet fs = SingleTableFormSheet.create(
"DisplayFormSheet",
catalog,
uigate);
fs.addContentCreator(new DisplayFormSheetContentCreator());
Define a FormSheet for a SalesPoint
Description: The recommended way to define a FormSheet for a SalesPoint is to redefine the method getDefaultFormSheet() of your Salespoint instance, which is used by the Framework to resolve the FormSheet which shall be displayed at that SalesPoint. You may also add a FormSheet during runtime by using the method setFormSheet(SaleProcess sp, FormSheet fs). As you can see, this method is also used to add a SalesProcess to the SalesPoint, which itself is able to display FormSheets. This example describes how to add a FormSheet to the SalesPoint, while the FormSheet itself should be assembled in a FormSheetContentCreator.
ToDo:
- Redefine the method getDefaultFormSheet() in the SalesPoint class.
- Return the FormSheet you want to use.
Example Source Code:
public class DisplaySalesPoint extends SalesPoint
{
public DisplaySalesPoint(String sName)
{
super(sName);
}
1
protected FormSheet getDefaultFormSheet()
{
2
return new FormSheet("DefaultFormSheet",
new DisplayFormSheetContentCreator(), false);
}
}
Define a StatusFormSheet for a SalesPoint
Description: SalesPoints are being displayed in a separate window but also have a StatusDisplay at the Shop, which is the TabbedPane in the Shop's Frame, labled the name of the SalesPoint. By bringing it on top, it shows what is defined as the StatusDisplay in your SalesPoint instance and also adds the Menu defined as StatusMenuSheet in the SalesPoint instance to the Shop's MenuSheet. By default, both, the StatusFormSheet and the StatusMenuSheet are empty. Feel free to use the StatusDisplay and MenuSheet, which are equally handled to the DefaultFormSheet and the DefaultMenuSheet except that due to the strong division of the Shop and it's SalesPoints it is not possible to have processes running on it. You may trigger a Processes on it, but they will always be displayed by the SalesPoint's window. Therefor a more suitable name would be "Statical Display". For further information on Processes refer to the section "Processes". This example describes how to define a FormSheet as the SalesPoint's StatusFormSheet, while the FormSheet itself should be assembled in a FormSheetContentCreator.
ToDo:
- Redefine the method getDefaultStatusFormSheet() in the SalesPoint class.
- Return the FormSheet you want to use.
Example Source Code:
public class DisplaySalesPoint extends SalesPoint
{
public DisplaySalesPoint(String sName)
{
super(sName);
}
1
protected FormSheet getDefaultStatusFormSheet()
{
2
return new FormSheet("StatusFormSheet",
new DisplayFormSheetContentCreator(), false);
}
}
Change the standard OK or CANCEL button behaviour
Description: A FormSheet initially has two buttons, one labeled "ok" and one with "cancel" on it. But they don't do anything by default, so you have to define their behaviour. There are mainly three ways to define the behaviour of the buttons. One is to create your own FormSheet and implement the methods ok() and cancel() and the other one is to remove the buttons and add your own ones with a FormSheetContentCreator. The second one is more commonly used, because it is less effort to add two buttons instead of creating lots of new FormSheets just to define the behaviour of a single click on a button. The third one is almost as common but due to the lack of influence on the button's look and feel less used. It's because here you only set an action to the standard button by resolving it with getButton(int id) and using setAction(Action action) on it. The button's ids are stored as static int in the FormSheet, where BTNID_CANCEL stands for the cancel button and BTNID_OK for the ok button. In order to make the behaviour serializable you have to define it within a FormSheetContentCreator. Otherwise the information will be lost after loading. It is also possible to alter the buttons design and caption when adding new ones.
ToDo:
- Initialize a new FormSheet.
- Create a new FormSheetContentCreator for the FormSheet and add it.
- In the protected void createFormSheetContent(FormSheet fs) method of the FormSheetContentCreator remove the OK-Button by calling removeButton(int id).
- Add a new FormButton using the addButton method. Like here you can shortly add an sale.Action to the button.
- Set a new sale.Action to the CANCEL-Button by fetching it with the getButton(int id) method and setting the action with setAction(Action).
Example Source Code:
class where FormSheet is set:
1
FormSheet sheet = new FormSheet("DisplaySheet", null);
2
sheet.addContentCreator(new DisplayFormSheetContentCreator());
FormSheetContentCreator class:
2
public class DisplayFormSheetContentCreator extends FormSheetContentCreator
{
3
protected void createFormSheetContent(FormSheet fs)
{
fs.removeButton(FormSheet.BTNID_OK);
4
fs.addButton("Ok", 102, new DisplayCustomAction());
5
fs.getButton(FormSheet.BTNID_CANCEL).setAction(new DisplayCustomAction());
}
}
Define an Action for a FormSheet Button
Description: The reason for adding buttons to your application is of course to let the user interact with it. By default, buttons don't have any functions, so you have to add a sale.Action to the button either by the method setAction(Action aAction) provided by the button or by already initializing the button with an action. Remember to put all this into a FormSheetContentCreator in order to have the Actions serialized as you save the Shop's status. Otherwise all the information will be lost after loading and the buttons will be useless.
ToDo:
- There are two ways of defining an Action:
- Create a class implementing the interface Action and add the public void doAction(SaleProcess process, SalesPoint point) throws Throwable method to it. Therein define what to do if the action was called.
- Create an anonymous implementation of Action and define what to do if triggered.
Example Source Code:
1 a
public class DisplayCustomAction implements Action
{
public void doAction(SaleProcess process, SalesPoint point) throws Throwable
{
// define what shall be done when triggering the action
point.runProcess(new DisplaySaleProcess("SampleProcess"));
}
}
1 b
public class DisplaySalesPoint extends SalesPoint
{
protected FormSheet getDefaultFormSheet()
{
FormSheet fs = new FormSheet("FormSheet", null);
fs.getButton(FormSheet.BTNID_OK).setAction(
new Action()
{
public void doAction(SaleProcess process, SalesPoint point) throws Throwable
{
// define what shall be done when triggering the action
point.runProcess(new DisplaySaleProcess("SampleProcess"));
}
});
}
}
Define an Action for a Button in a Component
Description: Sometimes you will need to add Buttons in the component part of a FormSheet, since the Button bar at the bottom may be not sufficient (e.g. too small). This has to be done within a FormSheetContentCreator when serialization is needed.
ToDo:
- Create a new FormSheetContentCreator with the standard constructor and add the createFormSheetContent() method.
- Create the JButton according to your needs.
- Associate the Action that should be executed when clicking with the Button. This is done with the ActionActionListener, that takes the Action itself aswell as the FormSheet that contains the button.
- Add the button to the FormSheet component. In this case this is done by creating a container for the button and setting it as FormSheet component. You could also use the getComponent() method of the FormSheet and manipulate the component directly (e.g. if it is already a special FormSheet).
Example Source Code:
1
public class DisplayFormSheetContentCreator extends FormSheetContentCreator
{
public DisplayFormSheetContentCreator()
{
super();
}
protected void createFormSheetContent(FormSheet fs)
{
2
JButton button = new JButton("TutorialButton");
3
button.addActionListener(new ActionActionListener(fs, new DisplayCustomAction()));
4
JPanel container = new JPanel();
container.add(button);
fs.setComponent(container);
}
}
Set error handling for a FormSheet
Description: According to the type of FormSheet you use (and the purpose you use them for), it could be useful to react on errors occurring in different ways. So it is, for example, not always senseful to stop the currently running process, whenever an error occurs if you are able to correct it programatically.
Most of the FormSheets use their own strategies for handling their tasks. Here you can setup the error handlers.
ToDo:
- Create a new FormSheetContentCreator with the standard constructor and add the createFormSheetContent() method.
- Get the FormSheet of the type you need. In our case we assume, we've been given a DataExchangeFormSheet and therefore can use it's strategy.
- Set the error handler you need. Basically, there are three possibilities:
- If the process has to be aborted immediately and an error message has to be displayed for the user, you can use the FormSheetStrategy.DEFAULT_ERROR_HANDLER. This is also set by default, so normally you don't have to do anything in this case.
- If the process can be continued, but a popup dialog shall be used displaying the error message for the user, you can use the FormSheetStrategy.MSG_POPUP_ERROR_HANDLER.
- In any other case, you will have to implement your own ErrorHandler.
Example Source Code:
1
public class DisplayFormSheetContentCreator extends FormSheetContentCreator
{
public DisplayFormSheetContentCreator()
{
super();
}
protected void createFormSheetContent(FormSheet fs)
{
2
DataExchangeFormSheet defs = (TwoTableFormSheet) fs;
3 a
defs.getStrategy().setErrorHandler(FormSheetStrategy.DEFAULT_ERROR_HANDLER);
3 b
defs.getStrategy().setErrorHandler(FormSheetStrategy.MSG_POPUP_ERROR_HANDLER);
3 c
defs.getStrategy().setErrorHandler(new FormSheetStrategy.ErrorHandler() {
public void error(SaleProcess p, int nErrorCode) {
System.out.println(p.getErrorMsg(nErrorCode));
}
});
}
}
Standard FormSheet tasks
Description: As described in #Create a FormSheet, SalesPoint provides you with a vast collection of standard FormSheets that are able to take care of most of the tasks that will occur in your application. We will now have a closer look at some tasks and how to solve them.
Display contents of containers: Containers are all kinds of Catalogs, Stocks, user lists and DataBastkets that are registered in your Shop. In order to display them, you can use a SingleTableFormSheet:
SingleTableFormSheet fs = SingleTableFormSheet.create(
"DisplayFormSheet",
catalog,
uigate);
fs.addContentCreator(new DisplayFormSheetContentCreator());
You can use any of the container types mentioned above by using the appropriate static create(...) method of the SingleTableFormSheet.
Add items to or remove items from containers: Adding or removing items is a bit more complicated than simply displaying them. In this example, we will concentrate on adding and removing items in a catalog.
- Create a SingleTableFormSheet as needed. Make sure to give the FormSheet exactly the same DataBasket that is currently used. If you are in process, you can simply use it's getBasket() method. If you don't, there will be no valid display update when changes on the container are performed.
- Add a new FormSheetContentCreator.
- If adding items is needed:
- Create an appropriate EditButtonStrategy:
- If you wish to add items to a catalog, you can use the AbstractAddCatalogItemStrategy class. Then you simply have to override it's createCatalogItem() method according to your needs and the type of CatalogItem you need.
- If you wish to add items to another container, you will have to create your own EditButtonStrategy. You can have a look at the AbstractAddCatalogItemStrategy as an example.
- Add the button to the FormSheet. Therefore we have to cast the FormSheet given to a SingleTableFormSheet.
- Create an appropriate EditButtonStrategy:
- If deleting items is needed:
- Create an appropriate EditButtonStrategy:
- If you wish to remove items from a catalog, you can simply use the DefaultRemoveCatalogItemStrategy class. No additional implementations are needed.
- If you wish to remove items from another container, you will have to create your own EditButtonStrategy. You can have a look at the DefaultRemoveCatalogItemStrategy as an example.
- Add the button to the FormSheet. Therefore we have to cast the FormSheet given to a SingleTableFormSheet.
- Create an appropriate EditButtonStrategy:
// The catalog item needed
public class DisplayCatalogItem extends CatalogItemImpl {
@RecoveryConstructor(parameters = {"m_sName"})
public DisplayCatalogItem(String sName) {
super(sName);
setValue(new DoubleValue(100.0d));
}
protected CatalogItemImpl getShallowClone() {
return null;
}
}
1
SingleTableFormSheet fs = SingleTableFormSheet.create(
"DisplayFormSheet",
catalog,
uigate);
2
fs.addContentCreator(new FormSheetContentCreator() {
protected void createFormSheetContent(FormSheet fs) {
3 a
EditButtonStrategy ebs = new AbstractAddCatalogItemStrategy(catalog) {
protected CatalogItem createCatalogItem(String sKey) {
return new DisplayCatalogItem(sKey);
}
};
3 b
((SingleTableFormSheet) fs).addAddButton(ebs);
4 a
ebs = new DefaultRemoveCatalogItemStrategy(catalog);
4 b
((SingleTableFormSheet) fs).addRemoveButton(ebs);
}
});
Move items between containers: In order to move items between containers, you can use the TwoTableFormSheet, that has been designed especially for this kind of task. Therefore you choose a source and a destination container. The FormSheet can move items between them in both directions, depending on how you wish to use it. There are exactly eleven possible combinations of source and destination containers:
Source | Destination | Default MoveStrategy |
CountingStock | CountingStock | CSCSStrategy |
StoringStock | StoringStock | SSSSStrategy |
CountingStock | DataBasket | CSDBStrategy |
DataBasket | CountingStock | DBCSStrategy |
StoringStock | DataBasket | SSDBStrategy |
DataBasket | StoringStock | DBSSStrategy |
Catalog | Catalog | CCStrategy |
Catalog | DataBasket | CDBStrategy |
DataBasket | Catalog | DBCStrategy |
Catalog | StoringStock | CSSStrategy Attention: You need to override the createStockItem() method, as it is application dependent. |
Catalog | CountingStock | CCSStrategy |
You can refer to the individual documentations on how they exactly work. When creating the DataExchangeFormSheet, simply use the create() that you need (giving null as DataBasket means the FormSheet will work outside any transitional context, causing all data manipulations to be executed immediately, without any rollback possibilities).
private FormSheet getMoveContentFormSheet(UIGate uigate, Catalog cSource, Catalog cDest)
{
DataExchangeFormSheet defs = DataExchangeFormSheet.create("Title",
cSource, cDest, (DataBasket)null, uigate, null);
return defs;
}
Update a formsheet display
Description: A common problem is notifying FormSheets that the data, or at least a part of it, has been changed by some process. Now it shall display the new data. The solution for this task is to use DataBaskets in the right way. You do not tell the formsheet that it shall update itself, rather you tell the modified data element to notify all it's listeners to update all their necessary parts (e.g. table cells).
ToDo:
- Create a new DataBasket.
- Retrieve the CatalogItem relative to the DataBasket and tell the get method to prepare the item for editing. After that you can manipulate it in any way you want.
- Call the DataBasket's commit method. This will put the updated item back in the container appropriately and tells all listeners such as visual display components to update themselves.
Example Source Code:
1
DataBasket db = new DataBasketImpl();
2
CatalogItem ci = catalog.get("TheItemsKey", db, true);
3
db.commit();
Use MessageForm and MessageFormSheet
Description:
The MessageForm class is a dialog which contains a MessageFormSheet. You can use the MessageFormSheet to display some information to the user inside your SalesPoint. Or you can use the MessageForm to do this inside a popup window. A MessageForm consists of the following parts:
owner | The owning window of the popup to realize some modality |
title | Title (caption) of the popup |
text | The text which should be displayed to the user |
type | Some value of the MessageFormType enumeration, which defines the available buttons |
icon | Some value of the MessageFormIcon enumeration, which defines the icon left to the text |
preferenceKey | If not null, this defines a key for PreferenceStorage and the anwer to the question will be saved to PreferenceStorage. If this happens the user will not face this message the next times and it will automatically return the previously pressed button. |
To display the MessageForm you need to call show() or showParallel(). Both methods differ in the fact that show() will end in a synchronous call and the other one in an asynchronous call. The asynchronous way should be preferred, but it is unable to return the pressed button. show() instead does this because it blocks until the user made its choice. If you have to call show() you need to do this form some thread outside the process machine, otherwise the application will hang because of a deadlock.
Tasks:
- Show an asyncronous MessageForm
- Show a "synchronous" MessageForm
Example Code:
1
new MessageForm(null, "ola", "Everything works.", MessageFormType.OK, MessageFormIcon.INFORMATION, null).showParallel();
2
new Thread() {
public void run() {
if (new MessageForm(null, "ola", "Sure you want to end the world?", MessageFormType.YES_NO, MessageFormIcon.QUESTION, null).show() == MessageFormResult.YES) {
//lets end the world
}
}
}.start();
//note that code execution will continue while showing the MessageForm, because we started it in a new thread
Use The multiple Layers
Description:
Each FromSheet has a JLayeredPane, which allows you put your components on 4 different layers. The first one is the normal content layer which you can get via
formsheet.getWrappedComponent();
The next one in the order is the modal layer
formsheet.getModalLayer();
Followed by the popup layer
formsheet.getPopupLayer();
And the drag layer on which all drag'n drop operations take place
formsheet.getDragLayer();
All layers, except the first one, have no layout manager by default so you need to set position and size of your component by hand, otherwise you won't see it. Theese layers make it possible to create some fancy effects.
Tasks:
- create a button
- set button's position and size
- put it in the popup layer
Example Code:
1
JButton button = new JButton("Hello, from popup layer");
2
button.setBounds(10, 10, 200, 40);
3
formsheet.getPopupLayer().add(button);
Customize the FormSheet
Description:
The FormSheetCustomizationListener allows you to paint your own background for the FormSheet's content area, for the button bar and even change the structure of the button bar.
Tasks:
- set the FormSheetCustomizationListener
- paint a rectangle in the content area
- paint a rectangle in the button bar
- add a label to the button bar
Example Code:
1
fs.setFormsheetCustomizationListener(new FormsheetCustomizationListener() {
public void paintButtonBarBackground(Graphics2D g, JComponent c) {
3
g.fillRect(10, 10, 100, 100);
}
public void paintComponentsBackground(Graphics2D g, JComponent c) {
2
g.fillRect(10, 10, 100, 100);
}
public JPanel setupButtonBar(JPanel bb) {
4
bb.add(new JLabel("Hello from button bar");
JPanel buttonPanel = new JPanel();
buttonPanel.setOpaque(false);
return buttonPanel;
}
});
MenuSheet
Define a MenuSheet
Description: A MenuSheet displays a menu, either as a JMenuBar, a JMenu or a JPopUpMenu . It is important to know that a MenuSheet can be displayed in both ways, horizontally and vertically. In the first case you see the added MenuSheetItems as dropdownmenus (like File, Edit, Help) or submenus (like About) and in the other case as the menuItems in the submenus (like new, load, save). You can define MenuSheets wherever you need them / want them to be. But in most cases you might want them to be just a Menubar at top of the Shop window or the ones of the SalesPoints. To put them right where they belong, you overwrite the method getDefaultMenuSheet() of your SalesPoint or the createMenuSheet() method of your Shop. As mentioned above, the MenuItems or MenuSheets contained by the first defined MenuSheet will be displayed horizontally. All the other MenuSheets and Items appear in the dropdown menu of the first MenuSheet. You can "group" the Items with MenuSheetSeparators.
ToDo:
- Locate the method where you want to define the MenuSheet. In this case a SalesPoint is chosen and its getDefaultMenuSheet() method overwritten.
- Create the top-level MenuSheet, it's the one that will be displayed horizontally and where all other MenuSheets in this case will be added to.
- Create another MenuSheet, it will the dropdown menu of the currently defined horizontal MenuSheet.
- Create one MenuSheetItem, name it and add the Action that should be performed when the item is clicked.
Add the MenuSheetItem to the dropdown menu. - Create a MenuSheetSeperator and add it to the menu.
- Create and add another MenuSheetItem to the menu.
- Add the newly created dropdown menu to the horizontal menu.
- Return the horizontal menu.
- Note: You can nest as many MenuSheets as you like and create really complex MenuSheet structures.
Example Source Code:
1
public MenuSheet getDefaultMenuSheet()
{
2
MenuSheet msMenu = new MenuSheet("HorizontalMenuSheet");
3
MenuSheet msSubMenu = new MenuSheet("myMenu");
4
MenuSheetItem msiSubItem1 = new MenuSheetItem("Perform an action",
new DisplayCustomAction());
msSubMenu.add(msiSubItem1);
5
MenuSheetSeparator mssSeperator = new MenuSheetSeparator();
msSubMenu.add(mssSeperator);
6
MenuSheetItem msiSubItem2 = new MenuSheetItem("Perform another action",
new DisplayCustomAction());
msSubMenu.add(msiSubItem2);
// add as many MenuSheetItems or MenuSheet as you like
.
.
.
7
msMenu.add(msSubMenu);
8
return msSubMenu;
}
Define a MenuSheet with JComponents
Description: If you want to improve the look of your menu, feel free to use JComponents. For further explanations on JComponents in menus "Using Swing Components" of java.sun.com. Adding JComponents to a MenuSheet is not as easy as adding framework MenuItems, because you can only add MenuSheetObjects with the add method. To add a JComponent anyway, you have to acces the JMenuBar in which the MenuSheet is being displayed by calling the getMenuBar() method. To this you add the JMenu with all the JMenuItems like CheckBoxes or RadioButtons added to it and which you initialize before.
ToDo:
- Locate the method where you want to define the MenuSheet. In this case a new method getJComponentMenuSheet() was created.
- Create the top-level MenuSheet, the one that is displayed horizontally.
- Initialize a JMenu using the javax.swing package.
- Initialize chosen JMenuItems and add them to the JMenu. Refer to "Using Swing Components" about possible item types.
In this case a JCheckBoxMenuItem and a JRadioButtonMenuItem where added. - Get the JMenuBar of the top-level MenuSheet and add the newly created JMenu to it.
- Finally return the MenuSheet.
Example Source Code:
1
public MenuSheet getJComponentMenuSheet()
{
2
MenuSheet msMenu = new MenuSheet("JComponentMenu");
3
JMenu jmMenu = new JMenu("JMenuItems");
4
JCheckBoxMenuItem jcbItem1 = new JCheckBoxMenuItem("JCheckBox");
jmMenu.add(jcbItem1);
JRadioButtonMenuItem jrbmItem2 = new JRadioButtonMenuItem("JRadioButtonMenuItem");
jmMenu.add(jrbmItem2);
5
msMenu.getMenuBar().add(jmMenu);
6
return msMenu;
}
Define a MenuSheet for a Shop
Description: Menus are essential for users interacting with an application. To create a menu in a Shop you will need to redefine the method createShopMenuSheet() of your instance of Shop.
ToDo:
In your Shop class create the method createShopMenuSheet(). Create your desired MenuSheet and return it. Therefore refer to #Define a MenuSheet.
Example Source Code:
1
public class DisplayShop extends Shop
{
.
.
.
public MenuSheet createShopMenuSheet()
{
2
MenuSheet msMenu = new MenuSheet("ShopMenu");
// add your MenuSheets and MenuSheetItems as needed
.
.
.
return msMenu;
}
.
.
.
}
Define a MenuSheet for a SalesPoint
Description: Normally you display a SalesPoint in a new window. To add a menu to it, you simply have to redefine the method getDefaultMenuSheet() of your instance of SalesPoint.
ToDo:
- In your SalesPoint class create the method getDefaultFormSheet().
- Create your desired MenuSheet and return it. Therefore refer to #Define a MenuSheet.
Example Source Code:
1
public class DisplaySalesPoint extends SalesPoint
{
.
.
.
public MenuSheet getDefaultMenuSheet()
{
2
MenuSheet msMenu = new MenuSheet("HorizontalMenuSheet");
// add your MenuSheets and MenuSheetItems as needed
.
.
.
return msSubMenu;
}
.
.
.
}
Define a StatusMenuSheet for a SalesPoint
Description: SalesPoints are being displayed in a separate window but also have a StatusDisplay at the Shop, which is the TabbedPane in the Shop's Frame, labled the name of the SalesPoint. By bringing it on top, it shows what is defined as the StatusDisplay in your SalesPoint instance and also adds the Menu defined as StatusMenuSheet in the SalesPoint instance to the Shop's MenuSheet. By default, both, the StatusFormSheet and the StatusMenuSheet are empty. Feel free to use the StatusDisplay and MenuSheet, which are equally handled to the DefaultFormSheet and the DefaultMenuSheet except that due to the strong division of the Shop and it's SalesPoints it is not possible to have processes running on it. You may trigger a Processes on it, but they will always be displayed by the SalesPoint's window. Therefor a more suitable name would be "Statical Display".
ToDo:
- In your SalesPoint class create the method getDefaultStatusMenuSheet().
- Create your desired MenuSheet and return it. Therefore refer to #Define a MenuSheet.
Example Source Code:
1
public class DisplaySalesPoint extends SalesPoint
{
.
.
.
public MenuSheet getDefaultStatusMenuSheet()
{
2
MenuSheet msMenu = new MenuSheet("StatusMenu");
// add your MenuSheets and MenuSheetItems as needed
.
.
.
return msMenu;
}
.
.
.
}
Alter a Shop's MenuSheet during runtime
Description: Sometimes there are reasons to change a Shop's MenuSheet during runtime. But in the class Shop a method setMenuSheet(MenuSheet ms) like the one in the class SalesPoint doesn't exists. This makes it a little more complicate. It is necessary to get the Shop's frame, which is actually a MultiWindow. The class MultiWindow provides the necessary public void setMenuSheet(MenuSheet newMenuSheet) method. Like this it is possible to change the MenuSheet, whenever wanted.
ToDo:
- Get the Shop's frame, while casting it as a MultiWindow.
- Create the new MenuSheet for the Shop.
(See also: #Define a MenuSheet). - Set the new MenuSheet on the MultiWindow.
Example Source Code:
1
DisplayShop displayShop = new DisplayShop();
MultiWindow multiWindow = (MultiWindow) displayShop.getShopFrame();
2
MenuSheet menuShop = super.createShopMenuSheet();
// get the first menu bar
MenuSheet menuBar = (MenuSheet)menuShop.getTaggedItem(SHOP_MENU_TAG, false);
// create new MenuSheetItem
MenuSheetItem menuItemNew = new MenuSheetItem("Important", "Important_TAG",
new sale.Action()
{
public void doAction (SaleProcess p, SalesPoint sp)
{
System.out.println("very important action!");
}
});
// add it to the MenuSheet
menuBar.add(menuItemNew);
3
multiWindow.setMenuSheet(menuShop);
Create your own MenuSheet type
Description: If you regularly need your own MenuSheet types, it may seem a bit inconvenient to create the item and afterwards manually change it's peer as described in #Define a MenuSheet with JComponents. In these cases you can create your own MenuSheet type by extending SalesPoint's MenuSheetObject according to your needs. This example describes how to create a quite simple radion button menusheet item. You can also refer to the MenuSheetItem if you wish extra functionality. Some omitted features are:
- Thread safety (via locks and synchronization)
- Support for adding Actions
- Button grouping
For the first two features, have a look at MenuSheetItem (link above), where they are already implemented. The third one could be realized using maps and Integers.
ToDo:
- Subclass the MenuSheetObject. Since we wish to react on clicks on the item afterwards, we also implement the ActionListener interface.
- Create a new constructor and explicitely invoke the super constructor with the given name. While doing so, we can already think of any parameters the constructor could need later. In our case these are (besides the caption) the intial checked state.
- Add the members the item we will need. These are, in our case a reference to the swing peer radio menuitem and a menu and the checked state.
- Implement the constructor. You could also add additional constructors that use standard values for some of the parameters.
- Override the neccessary methods from MenuSheetObject, such that they reflect the changes in the correspondend peers:
- getPeer:
Returns a JMenuItem (or a subclass of it) that represents the MenuSheetItem in a JMenu. - getMenuPeer:
Returns a JMenu that has the same caption as this MenuSheetItem and has this item as single entry. - setCaption:
Sets the caption of both the menu item peer and the menu peer. - setVisible:
Set the visibility as needed. If it is set to false, you can invalidate the peers (e.g. by setting them to null) and then recreate them when needed.
- getPeer:
- Add methods according to the needs of the new MenuSheetItem type. In our case we need a getter and a setter for the checked status.
- Implement the method from the ActionListener interface. Since we added the item itself as ActionListener for the menu item peer, we can react on clicks on it and synchronize it's checked state with the checked member of the MenuSheetItem itself.
Example Source Code:
// needed imports
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import javax.swing.JMenu;
import javax.swing.JMenuItem;
import javax.swing.JRadioButtonMenuItem;
import sale.MenuSheetObject;
1
public class DisplayMenuSheetItem extends MenuSheetObject implements ActionListener {
3
protected transient JRadioButtonMenuItem m_jrbmiPeer;
protected transient JMenu m_jmMenuPeer;
private boolean m_fChecked;
2
public DisplayMenuSheetItem(String sCaption, boolean checked) {
super(sCaption);
4
m_jrbmiPeer = null;
m_jmMenuPeer = null;
m_fChecked = checked;
}
5 a
public JMenuItem getPeer() {
if (m_jrbmiPeer == null) {
m_jrbmiPeer = new JRadioButtonMenuItem(getCaption(), m_fChecked);
m_jrbmiPeer.addActionListener(this);
m_jmMenuPeer = null;
}
return m_jrbmiPeer;
}
5 b
public JMenu getMenuPeer() {
if (m_jmMenuPeer == null) {
m_jmMenuPeer = new JMenu(getCaption());
JRadioButtonMenuItem jmi = new JRadioButtonMenuItem(getCaption(), m_fChecked);
jmi.addActionListener(this);
m_jmMenuPeer.add(jmi);
}
return m_jmMenuPeer;
}
5 c
public void setCaption(String caption) {
super.setCaption(caption);
if (m_jrbmiPeer != null) {
m_jrbmiPeer.setText(getCaption());
}
if (m_jmMenuPeer != null) {
m_jmMenuPeer.setText(getCaption());
m_jmMenuPeer.getItem(0).setText(getCaption());
}
}
5 d
public void setVisible(boolean fVisible) {
super.setVisible(fVisible);
if (!fVisible) {
m_jrbmiPeer = null;
m_jmMenuPeer = null;
}
}
6
public boolean isChecked() {
return m_fChecked;
}
public void setChecked(boolean checked) {
m_fChecked = checked;
getPeer().setSelected(m_fChecked);
}
7
public void actionPerformed(ActionEvent e) {
m_fChecked = m_jrbmiPeer.isSelected();
}
}
LogFile Management
Display log file content
Description: In a logfile you can log any event of your framework-application, wether it is a commited process or a rollback, a User logging on, a wrong password entered, just anything. To display a logfile in a FormSheet, use the LogTableForm, which is a subclass of FormSheet and contains a JLogTable. Remember that the information in that table is being read at creation time and won't be updated when the logfile changes.
ToDo:
- Instantiate a FileInputStream.
- Lead that stream into the LogInputStream which will look at the chosen file as a logfile.
- Initialize a new LogTableForm that is made for displaying LogInputStreams.
- Set the LogTableForm to the FormSheet of the SalesPoint by calling setFormSheet(SaleProcess sp, FormSheet fs).
- Catch the relevant exceptions.
Example Source Code:
try
{
1
FileInputStream fileStream = new FileInputStream("machine.log");
2
LogInputStream logStream = new LogInputStream(fileStream);
3
LogTableForm logForm = new LogTableForm("View log file", logStream);
4
setFormSheet(null, logForm);
}
5
catch(FileNotFoundException fileEx)
{
// exception handling here
}
catch(IOException ioEx)
{
// exception handling here
}
catch(InterruptedException interruptEx)
{
// exception handling here
}
User Management
Define a UserFilter
Description: Once you have a huge amount of registered Users in your application, you may want to display only a certain amount of them. This can be accomplished by the UserFilter. UserFilter is an interface, but don't worry, there's not much to implement. You only have to define the method match(User user), which shall return true when the User matches the criteria you want him to be displayed for.
ToDo:
- Create a class implementing UserFilter.
- Add the constructor and invoke the super constructor.
- Implement the public boolean match(User user) method.
Therein decide which user shall be filtered and which not.
Example Source Code:
1
public class DisplayUserFilter implements UserFilter
{
2
public DisplayUserFilter()
{
super();
}
3
public boolean match(User user)
{
return user.getCapability("AdminRights").isGranted();
}
}
Display Users in a JUserList
Description: Like TableFormSheets display the content of CountingStocks or Catalogs, a JUserList displays the Users managed by a UserManager. The JUserList is a JComponent and thereby can easily be displayed by a FormSheet. In this example we do not only display the Users, but use the method User.getCapabilityCheckBox(), too, so we can also edit the Capabilities of a User. With the UserManager determined as the GlobalUM, the JUserList can be initialized with its default constructor and will use the GlobalUM. Otherwise you have to initialize it with the UserManager you want to display the content of. The JUserList being a JComponent makes it possible to add other components to it, like in this case the JCheckBox. It is disabled at first, because initially no User is being selected in the JUserList. In order to enable the CheckBox after a selection, we added a ListSelectionListener to the JUserList and let the valueChanged(ListSelectionEvent) method update the CheckBox. In this example we put all this into a SaleProcess, which is the easiest way, but not the only one. Be careful to initialize the UIGate with the new FormSheet and then add the JComponent to the Formsheet, otherwise the Gate won't display the JUserList. For more information about processes, please refer to the section Processes.
ToDo:
- Add a JUserList to the attributes of the SaleProcess in order to make it available all over the process. Do the same for a JCheckBox, Transition and UIGate. All four will be needed.
- In your Gate initialize the JUserList. The default constructor will do, because we use only one UserManager and declared it GlobalUM in the Shop instance.
- Before we add the JCheckBox that later on will be the CapabilityCheckBox, we set the Layout of the JUserList and after that add the blank JCheckBox.
- In order to update the state of the JCheckBox, add a ListSelectionListener to the JUserList. This will enable the JCheckBox as soon as a User is selected in the List and even update the value of the JCheckBox referring to the selected User.
- Create a FormSheet (with buttons and so on, set by FormSheetContentCreator) where the JUserList will be displayed in.
- Create a new UIGate containing that FormSheet.
- Call FormSheet.setComponent at the FormSheet above to display the JUserList and the UIGate is ready to be displayed.
- Instantiate the Transition that updates the JCheckbox. Therein remove the old Box, create a new from the JUserList, add it to the JUserList and return the UIGate from the perform method.
The order of the last two steps is quite important, because the other way around (first setting the Component to the FormSheet and then adding it to the UIGate), the Gate won't display the JUserList!
Example Source Code:
public class DisplaySaleProcess extends SaleProcess
{
1
private JUserList userList;
private JCheckBox checkBox;
private Transition transitionSelect;
private UIGate uig_user;
public Gate getUserGate()
{
2
userList = new JUserList();
3
userList.setLayout(new GridLayout());
checkBox = new JCheckBox();
userList.add(checkBox);
4
userList.addListSelectionListener(new ListSelectionListener()
{
public void valueChanged(ListSelectionEvent listSelectionEvent)
{
uig_user.setNextTransition((Transition) transitionSelect);
}
}
);
5
FormSheet formSheet = new FormSheet("UserEditor", null);
formSheet.addContentCreator(
new DisplayFormSheetContentCreator());
6
uig_user = new UIGate(formSheet, null);
7
formSheet.setComponent(userList);
8
transitionSelect = new Transition()
{
public Gate perform(SaleProcess process, User user)
{
userList.remove(checkBox);
checkBox = userList.getSelectedUser().getCapabilityCheckBox("Office");
userList.add(checkBox);
return uig_user;
}
};
return uig_user;
}
}
Define a User's Capability
Description: To display the status of a user's Capability, the class User has the method getCapabilityCheckBox(String nameOfCap). This will return a JCheckBox with the DisplayName of the Capability on it and it will be checked or not, depending on wether the user is granted that capability. The tricky thing about this is the CapabilityDisplayName which is used as the caption of the JCheckBox. You have to extend the java.util.ResourceBundle at first and put the DisplayName (the + caption matching to the locale) into the keyList. For more information on ResourceBundles go to ResourceBundle of the Java API and setDisplayNameResourceBundleName of the Framework API. For the complete example refer to #Display Users in a JUserList.
ToDo:
- Resolve the User you want to display the Capability of and call the getCapabilityCheckBox(String nameOfCap) method in order to get the JCheckBox.
- Add the JCheckBox to the Container you want it to be displayed in.
You don't have to take care about any action triggered by the CheckBox. The status of the Capability will be updated automatically at the User by the Framework, so it can also be used to edit the Capabilites.
Example Source Code:
1
checkBox = userList.getSelectedUser().getCapabilityCheckBox("Office");
2
userList.add(checkBox);
Use a LogOnForm
Description: A LogOnForm is a special FormSheet with a ComboBox and an optional PasswordField. It can be used where ever you want a User to be identified. The ComboBox will show the Users stored in the UserManager and in the PasswordField you can enter the password. If the identification has been successful, the LogOnForm will return the User that was identified, otherwise null.
ToDo:
- Instantiate the LogOnForm with
- its title
- the prompt of the ComboBox
- the prompt of the PasswordField
- boolean wether password is needed or not
- the UserManager storing the User to be identified
- a Comparator for the appearance of the Users.
(null -> Users will be ordered by their names) - a Filter in order to display only certain Users.
(null -> all Users will be displayed)
- Display the FormSheet.
The method getResult() will return the User if the identification was successful, else null.
Example Source Code:
1
LogOnForm formLogOn = new LogOnForm(
"Please identify yourself",
"Username: ",
"Password: ",
true,
UserManager.getGlobalUM(),
null,
null);
2
uig_logon.setFormSheet(formLogOn);
Render additional User Information
Description: Default renderers for Users reserve some area for self defined, user specific information. You can easily render your custom information into this arey by overriding the renderAdditionalInfoArea method in the User class. You will get a JPanel which you cann fill with any swing components you want.
Tasks:
- override renderAdditionalInfoArea method
- add some senseless components
Example Code:
public class MyUser extends User {
1
public void renderAdditionalInfoArea(JPanel panel, boolean isSelected) {
2
panel.add(new JLabel("here we go"));
}
}
Tables
Create a Table
Description: A Table normally is used to display the content of a data container like a Catalog or a CountingStock. To make your own datastrucure fully compatible to the framework, it is quite important to understand which classes participate in the displaying and editing process.
- JAbstractTable:
First of all there is the JAbstractTable, which just displays any table. To let it know, which kind of data it shall display and how to do this, it needs an AbstractTableModel, which is being added in the constructor. - AbstractTableModel:
The AbstractTableModel deals with technical issues, like the properities of the cells of each column, for example the ability to edit them or how the data shall fit into the rows. This is also where the association of records to cells is being resolved. The AbstractTableModel itself needs a TableEntryDescriptor to resolve the input of each cell from the given data container. - TableEntryDescriptor:
As said above, a TableEntryDescriptor handles the input of each cell and the association of records with them, processing each row and filling it column by column. A row represents an entry of the data container.
Here is the place, where the number of columns is being defined and which cells shall be editable. In such a case, the TableCellEditor is being assigned to the cell. This is also the place where the most mistakes are being made, because you also have to define the CellRenderers for each cell, which causes class CastExceptions when done wrong.
All these parts are already defined for the default data structure of the Framework. There are SingleTableFormSheets and TwoTableFormSheets (FormSheets with two SingleTables on them and some more functionality) which have static create methods where you only have to add a caption, a DataBasket and the data container(s) you want to be displayed while the Framework puts everything together. You then only have to define the buttons and at the TwoTableFormSheets the MoveStrategy for the interaction between the two SingleTables.
As example a SingleTableFormSheet displaying a Catalog is presented.
ToDo:
- First alternative:
Call SingleTableFormSheet.create(String caption, Catalog c, Gate g) to initialize the SingleTableFormSheet and assign it to the UIGate. - Second alternative that enables you to redefine any part of the Table to fit to your own data structure:
- Initialize a new JAbstractTable with
- the TableModel (here it is a CatalogTableModel) containing
- the Catalog
- a DataBasket (if needed) and
- a Comparator (if needed (if the data container isn't sorted or the display shall be orderd differently)) and
- a new TableEntryDescriptor (here it is a DefaultCatalogItemTED, because it is specialized on processing CatalogItems, which are contained in the Catalog)
- Put the JComponent on the FormSheet.
- Assign the FormSheet to the UIGate.
Example Source Code:
1
SingleTableFormSheet stfs =
SingleTableFormSheet.create(
// the FormSheet's caption
"Display a Catalog",
// the Catalog to be displayed
(Catalog) catalog,
// the Gate at which to display the FormSheet
(UIGate) uig_initial,
// the TableEntryDescriptor that displays a Catalog
new DefaultCatalogItemTED());
2
2 a
JAbstractTable abstractTable =
new JAbstractTable(
2 b
new CatalogTableModel(
2 c
(Catalog) catalog,
2 d
(DataBasket) null,
2 e
(Comparator<CatalogItem>) null,
2 f
new DefaultCatalogItemTED())
);
2 g
FormSheet formSheet = new FormSheet(
"Display a Catalog",
abstractTable);
2 h
uig_initial.setFormSheet(formSheet);
Create a sorted Table
Description: Sorted tables are like normal ones, except that they have a certain order for displaying the content. This example is almost the same as in #Create a Table. Only the Comparator is new, which is necessary to define the sort order for the table. To do so, you have to implement the method public int compare(Object object1, Object object2), which shall return an int value corresponding to your desired sort order. As described in the Java API, the int should be negative if the second Object is of a higher value, zero if the Objects are "equal" and of positive if the first Object shall be on top. For more information on the Interface java.uitl.Comparator, please refer to the latest API.
ToDo:
- Initialize the Gate where the FormSheet shall be displayed in.
- Get the Catalog the content shall be displayed of.
- Create the Comparator and implement the method public int compare(Object object1, Object object2) to return an integer suiteable to the order of the compared elements.
- Call SingleTableFormSheet.create(String caption, Catalog c, Gate g, Compatator co, TableEntryDescriptor ted) to initialize the SingleTableFormSheet and assign it to the UIGate.
Example Source Code:
1
UIGate uig = new UIGate(null, null);
2
Catalog catalog = Shop.getTheShop().getCatalog("SimpleCatalog");
3
// the implemenatation of the used Comparator
Comparator<CatalogItem> comparator = new Comparator<CatalogItem>()
{
// the method used for comparison
public int compare(CatalogItem item1, CatalogItem item2)
{
// get the first CatalogItem's Value
// get the second CatalogItem's Value
NumberValue numValue2 = (NumberValue) item2.getValue();
// convert Values to int
int intValue1 = numValue1.getValue().intValue();
int intValue2 = numValue2.getValue().intValue();
// compare Values
return intValue1 - intValue2;
}
};
4
SingleTableFormSheet stfs =
SingleTableFormSheet.create(
"VideoCatalog",
catalog,
uig,
comparator,
new DefaultCatalogItemTED());
Define a TableCellEditor
Description: A TableCellEditor is needed to define the Component that displays the entry of a cell in a JTable, to check wether an edited value is valid and to inform it's parent table that an editing event finished. Our CellEditor will have a util.swing.JTextInput as the Component to display and edit the entry. The validity of a initial cell value is being tested in public Object getCellEditorValue() by simply trying to parse it to a Currency. If parsing fails, zero will be returned and thereby no wrong value may be put into the cell and cause any trouble. The validity of an edited cell value is being checked in public boolean stopCellEditing, which informs the calling member that a correct value has been entered. It checks the value just like getCellEditorValue() by trying to parse it. If it fails, there will be an error message and editing cannot be stoped until a valid value is being entered or editing is being aborted by pressing the escape key.
ToDo:
- Create a class extending DefaultCellEditor.
- Initialize the attributes in the constructor.
- Define how to get the CellEditors Component and its initial value to display and check its validity. In our case we return a util.swing.JTextInput for reasons of serializability with the initial value being checked by trying to parse it. If that fails, zero will be returned.
- Define how to resolve the value of the cell. This is needed in case of table or window updating.
- Define how to indicate the successful attempt of editing a cell. Here we, too, try to parse the entered value, but do not return zero if it fails. There will be an error message instead and the return of false, which prevents the editing to stop until a valid value is being entered or it is being aborted.
Example Source Code:
1
public class DisplayCellEditor extends DefaultCellEditor
{
// needed for the JTextInput
private String[] result;
2
public DisplayCellEditor(String[] result, String init)
{
super(new JTextInput(result, init));
this.result = result;
}
3
// define the Component and the value it shall display first
public Component getTableCellEditorComponent(
JTable jTable,
Object value,
boolean isSelected,
int row,
int column)
{
// fetch the Component and the initial value
Component component = super.getTableCellEditorComponent(
jTable,
value,
isSelected,
row,
column);
// initialize the JTextInput, which is needed for editing
// and used because it is serializable
((JTextInput)component).setText(
((Currency<CurrencyItem>) Shop.getTheShop().getCatalog(EuroCatalog)).toString((NumberValue) value)
);
// and return it
return component;
}
4
public Object getCellEditorValue()
{
// parse the value of the JTextInput
try
{
return ((Currency<CurrencyItem>) Shop.getTheShop().getCatalog(EuroCatalog)).parse(result[0]);
}
catch (ParseException pexc)
{
// return zero if it fails
return new IntegerValue(0);
}
}
5
// indicate wether an editing was successful or not
public boolean stopCellEditing()
{
try
{
((Currency<CurrencyItem>) Shop.getTheShop().getCatalog(EuroCatalog)).parse(result[0]);
}
catch (ParseException pexc)
{
// create an error message if parsing fails
JOptionPane.showMessageDialog(null, "Incorrect value entered, has to be " Euro"");
// return fals to stay in edit mode
return false;
}
return super.stopCellEditing();
}
}
Define a new TableLayout
Description: The TableLayout is the definition of how the information provided by the entries of a data container, which in tables are being represented as records, shall be split and put into the cells. As it is described in #Create a Table, this is what a TableEntryDescriptor deals with. It defines the number of columns the table shall have, the headers of the columns, which TableCellRenderer or TableCellEditor shall be used and which value goes into a column. The most important thing in a TED is to return the right Classes, CellRenderer or CellEditors for each column, because the displayed value is being visualized by a JComponent and the different types are being processed by the Renderer or Editor in very different ways to cast them. Mistakes will oftenly cause ClassCastExceptions, followed by huge error messages concerning the table rendering. All this is defined columnwise, because the framework processes the records and fills each row, representing a record, column by column, representing values from the record. This example is taken from the DefaultCatalogItemTED of the framework. It is called ..CatalogItem.. because it deals with the entries of a Catalog, which are CatalogItems. So it is normally used with a CatalogTableModel. For more information on this, please refer to #Create a Table.
ToDo:
- Extend the AbstractTableEntryDescriptor which implements most of the methods of the interface TableEntryDescriptor quite sufficiently, so we only have to redefine those which do not fit to our data structure.
- Call the superclass in the Constructor in order to initialize the TED properly.
- Define the number of columns needed by redefining the method public int getColumnCount(). In this case it returns 2, because this TED only displays the name and value of the CatalogItem.
- Define the header of the columns with the method public String getColumnName(int index). Here an array of Strings is being used, but this can be done by anything that returns the right caption to the given index, beginning with '0' for the first column (if, selectCase or like in this example, an array).
- Define the class which each column shall deal with, ie. the class of the entrie's value that goes in that column, like in this case a String in the first and a value in the second one. Here, too, anything does, as long as it returns the right class to the right index.
- Define the entry that goes into each column of a line in public Object getValueAt(Object data, int index). The returned Object is of course the value to be written into column number 'index'. data is the data container entry, which is the record associated to the processed line. As it is a CatalogItem in this case, it is casted to it and then it's metods are being used to resolve the input for the cells.
Example Source Code:
1
public class DisplayCatalogItemTED extends AbstractTableEntryDescriptor
{
2
public DisplayCatalogItemTED()
{
super();
}
3
// here we need only two columns, one for the name of the CatalogItem and one for it's value
public int getColumnCount()
{
return 2;
}
4
// the header of the columns, "Name" for the first, "Value" for the second one
public String getColumnName(int index)
{
String[] names = {"Name", "Value"};
return names[index];
}
5
// The first column deals with Strings and the second one with Values.
// This is rather important, because wrong definitions will cause ClassCastExceptions.
public Class<?> getColumnClass(int index)
{
Class[] classes = {String.class, Value.class};
return classes[index];
}
6
// Here the cell-entry is being resolved, column by column,
// taken from the Object data, which is a CagtalogItem in this case
// The returned Object's class has to fit to the definition above.
public Object getValueAt(Object data, int index)
{
if(index == 0)
{
return ((CatalogItem) data).getName();
}
else
{
return ((QuoteValue) ((CatalogItem) data).getValue()).getBid();
}
}
}
Create an editable Table
Description: Tables are not only useful to display the content of data containers, but also to edit it. To do so, you may define certain columns as editable. The processing of a cell in a column is then being done by a javax.swing.table.TableCellEditor, which you add in the TableEntryDescriptor of your editable table. As you can see, the TableCellEditor interface can be found in the javax package and is not part of the SalesPoint classes. There you can also find implementations of it, like the DefaultCellEditor, which provides three JComponents to edit the value of a cell: A JCheckBox, a JComboBox and a JTextField. For more information on the DefaultCellEditor, please refer to the Java API documentation.
In this example we use a simple CatalogItem with a name and a string value to edit. Therefor we have to define a TableCellEditor as well as a TableEntryDescriptor that allows us the editing.
ToDo:
- the CatalogItem
(See also: #Define a simple CatalogItem and #Define an advanced CatalogItem)- Create a subclass of CatalogItemImpl.
- Define a string variable that represents the string value. Add the constructor, call super with the item's name as attribute and set the string value.
- Create the protected CatalogItemImpl getShallowClone() method and return a item copy.
- Add the public String getStringValue() to return the string value.
- For setting a new string value we define a setter method: public void setValue(String value).
- the CellEditor
(See also: #Define a TableCellEditor)- Create a class extending DefaultCellEditor.
- Initialize the attributes in the constructor.
- Define how to get the CellEditors Component and its initial value to display.
- Define how to resolve the value of the cell. This is needed in case of table or window updating.
- Define how to indicate the successful attempt of editing a cell. In this case we only call the super-method, but use this to eventually check the input values.
- the AbstractTableEntryDescriptor
(See also: #Define a new TableLayout)- Create a subclass of AbstractTableEntryDescriptor.
- Add the Catalog and DataBasket variables to get them easier resolved later.
- Add the necessary TableCellRenderers to the attributes. We need one for the CataloItem's name and one for its string value.
- Add the constructor with the Catalog and DataBasket as arguments.
- Initialize the attributes.
- Set the number of cells. We need 2, one for name and one for value.
- Set the column's head. self-explanatory.
- Set the CellRenderers for each cell. index specifies the column, so ew return the on the beginning declared renderers for our 2 columns.
- Because of the TableCellRenderers there need no ColumnClasses to be defined. Otherwise you would return the classes of the column elements like the column heads, e.g. return new String[] { String.class, Integer.class }[index];.
- Define the editable columns. We only want column 1 with the string value be editable.
- Assign the TableCellEditors. For the editable column we use the newly created CellEditor that enables editing. The other column calls the super method and therefore is not editable.
- Define how to resolve the value to be put into the cells. Because we display our editable CatalogItems the Object record is cast to them. Then simply the get methods of our item are used.
- Define how to set the edited values to the CatalogItem they belong to. For identifying the item we cast the Object record to CatalogItem and get the name. Afterwards we get the item from the catalog set by the TED's constructor for editing and set the new string value. For getting the catalog handle the possible VetoException.
Example Source Code:
CatalogItem class:
1 a
public class DisplayEditableCatalogItem extends CatalogItemImpl
{
1 b
// define a string variable and add constructor
private String value;
public DisplayEditableCatalogItem(String name, String value)
{
super(name);
this.value = value;
}
1 c
// return item copy
protected CatalogItemImpl getShallowClone()
{
return new DisplayEditableCatalogItem(this.getName(), this.value);
}
1 d
// return method for the string value
public String getStringValue()
{
return this.value;
}
1 e
// setter method for the string value
public void setValue(String value)
{
this.value = value;
}
}
CellEditor class:
2 a
public class DisplayEditableCellEditor extends DefaultCellEditor
{
// needed for the JTextInput
private String[] result;
2 b
// initialize the attributes in the constructor
public DisplayEditableCellEditor(String[] result, String init)
{
super(new JTextInput(result, init));
this.result = result;
}
2 c
public Component getTableCellEditorComponent(
JTable jTable,
Object value,
boolean isSelected,
int row,
int column)
{
// fetch the component and tis initial value
Component component = super.getTableCellEditorComponent(
jTable,
value,
isSelected,
row,
column);
// initialize the JTextInput
((JTextInput)component).setText(value.toString());
return component;
}
2 d
// define how to resolve the value of the cell
public Object getCellEditorValue()
{
return result[0];
}
2 e
// define how to indicate the successful attempt of editing a cell
public boolean stopCellEditing()
{
return super.stopCellEditing();
}
}
AbstractTableEntryDescriptor class:
3 a
public class DisplayEditableTED extends AbstractTableEntryDescriptor
{
3 b
// Add Catalog and DataBasket variables
private DataBasket dataBasket;
private Catalog catalog;
3 c
// add TableCellRenderers
private TableCellRenderer rendererName;
private TableCellRenderer rendererValue;
3 d
// add constructor
public DisplayEditableTED(Catalog catalog, DataBasket dataBasket)
{
3 e
// initialize the Catalog and DataBasket
this.catalog = catalog;
this.dataBasket = dataBasket;
// initialize the TableCellRenderers with DefaultTableCellRenderer
rendererName = new DefaultTableCellRenderer();
rendererValue = new DefaultTableCellRenderer();
// set the align of the values cell to center. this is just optical baublery.
((DefaultTableCellRenderer) rendererValue).setHorizontalAlignment(
SwingConstants.CENTER);
}
3 f
// set column count
public int getColumnCount()
{
return 2;
}
3 g
// set column heads
public String getColumnName(int index)
{
String[] names = new String[]{
"Name",
"Value"};
return names[index];
}
3 h
// assign the cell renderers
public TableCellRenderer getCellRenderer(int index)
{
switch(index)
{
// displays the items name
case 1:
return rendererValue;
// displays the items string value
default:
return rendererName;
}
}
3 i
// returns null, because the TableCellRenderer is already defined
public Class<?> getColumnClass(int index)
{
return null;
}
3 j
// set column 1 editable
public boolean isElementEditable(Object record, int index)
{
return (index == 1);
}
3 k
// assign CellEditors
public TableCellEditor getCellEditor(int index)
{
if(index == 1)
return new DisplayEditableCellEditor(new String[1], "");
else
return super.getCellEditor(index);
}
3 l
// define how to resolve the entries of a row, column by column
public Object getValueAt(Object record, int index)
{
// cast the record to our CatalogItem
DisplayEditableCatalogItem item = (DisplayEditableCatalogItem) record;
// returne the values corresponding to the column
switch(index)
{
case 0:
return item.getName();
case 1:
return item.getStringValue();
default:
return null;
}
}
3 m
// define how to change the edited value in the CatalogItem
public void setValueAt(Object record, int index, Object value)
{
// define a CatalogItem, initially set to null
DisplayEditableCatalogItem item = null;
// get the current item's identifier
String name = ((CatalogItem) record).getName();
try
{
// get the item from the catalog for editing
item = (DisplayEditableCatalogItem) this.catalog.get(name, dataBasket, true);
// set the items new string value
item.setValue(value.toString());
}
// catch the relevant exception
catch (VetoException e)
{
e.printStackTrace();
}
}
}
List Views
List Views are powerful visual components for product presentations. They support scaling at runtime, categories, a live search, drag'n drop and flexible rendering. It also supports Salespoint's data classes (Catalog, Stock, DataBasket, UserManager) in the same way tables does it. The core component is the JListView class with its specific subclasses for the individual framework components (e.g. JCatalogTable). The JListView uses a ListModel and a ListCellRenderer. ListModels are available for each framework component (JCatalogListModel) to provide actual data to the ListView. The ListCellRenderer visualizes the items of the ListView. You cann use any subclass of ListCellRenderer but the recommended way is to use subclasses of the AbstractListViewRenderer which are more suitable and provide high performance rendering. Specific framework subclasses of AbstractListViewRenderer are also available (e.g. CatalogListViewRenderer). Use List Views similar to Tables but with JListView instead of JAbstractTable and ListViewFormSheet instead of SingleTableFormSheet.
Use ListViewFormSheet
Description:
So lets create a ListViewFormSheet. You can create theese FormSheets in th same way you create SingleTableFormSheets. Here we want to use a CountingStock as data source and customize the default renderer a little bit. Each ListView has an orientation this could be:
HORIZONTAL | Left aligned and horizontally wrapped |
HORIZONTAL_CENTERED | Centered and horizontally wrapped |
VERTICAL | Left aligned and vertically wrapped |
VERTICAL_CENTERED | Centered and vertically wrapped |
After creating the ListViewFormSheet we set the cell constrains which define some constraints for the slider used for scaling. First parameter is the minimum cell size. this is a relative value like all other parameters between zero and one. this means, if the slider is moved to the left end, the cell width respectively height (depends on orientation of the ListView) will be this value multiplied with width the total width respectively height available to the ListView. Next parameter is the maximum, followed by the initial size. The last parameter defines the ratio of height and width of a cell.
All ListViewRenderers derived from CatalogListViewRenderer can take some rendering attributes which can be linked by a logical or ( | ). Theese attributes define, what should be displayed and how. Further you can set the so called shadow style which is one of NONE, FRONT or BEHIND. This defines if and where the image shadow should be displayed.
Tasks:
- create the ListViewFormSheet
- set the cell constraints
Example Code:
1
ListViewFormSheet slvfs = ListViewFormSheet.create(
"",
gate,
theCountingStock
null,
null,
ListViewOrientation.VERTICAL_CENTERED,
new CountingStockListViewRenderer(
ShadowStyle.FRONT,
null,
StoringStockListViewRenderer.RENDER_LAYOUT_UPON_THE_OTHER
| StoringStockListViewRenderer.RENDER_SHOW_NAME
| StoringStockListViewRenderer.RENDER_ORIENTATION_NAME_CENTER));
2
slvfs.setCellConstraints(.3f, 1f, 1f, .7f);
Mix Tables and ListViews in DataExchangeFormSheet
Description:
The DataExchangeFormSheet makes it possible to move items from one data element(Catalog, Stock, Databasket) to another. Therefor it basically consists of two components which can exchange their items via drag'n drop. Each component can either be a JAbstractTable or a JListView. Two define which is used and set additional contruction parameters you need to use the DataExchangeOptions class. It consists of two references to a DataExchangeOption object and a DataExchangeView value which defines the arrangement of both components. DataExchangeOption has two subclasses: TableDataExchangeOption and ListViewDataExchangeOption, which define additional attributes to the specific component. See the source and javadoc for more information.
Tasks:
- get a copy of one of the predefined DataExchangeOptions
- set a TED for the source table
- set cell contraints for the destination ListView
- create the DataExchangeFormSheet
Example Code:
1
DataExchangeOptions options = DataExchangeFormSheet.OPTION_TABLE_LISTVIEW.copy();
2
((TableDataExchangeOption) options.getSrcOption()).setTed(new MyTED());
3
((ListViewDataExchangeOption) options.getDestOption()).setCellConstraints(.25f, 1f, .35f, 1f);
4
DataExchangeFormSheet defs = DataExchangeFormSheet.create(
"caption", sourceElement, destinationElement, options);
Create custom Renderer
Description:
To display additional things of your CatalogItem or StockItem in the ListView you need to derive the AbstractListViewRenderer class or one of its subclasses. In most cases you should derive from the CountingStockListViewRenderer, because even a JCatalogListView uses this renderer to display a counter in drag'n drop operations. Now override the render method. To get information about the running rendering process you should use the renderContext property, which gives information about the object that is being rendered, the mouse over state, selection state, etc. Notice that the renderer also renders the separators. If renderContext.rerender is true, the cell is already rendered but maybe needs to update its values.
Tasks:
- derive the CountingStockListViewRenderer class
- define the new label to display
- create the 3 default constructors
- implement a method for component creation
- implement a method for setting the values
- implement a method for integrating the new label
- override the render method
Example Code:
1
public class MyListViewRenderer extends CountingStockListViewRenderer {
2
protected JLabel myCustomLabel = null;
3
public CountingStockListViewRenderer() {
super();
}
public CountingStockListViewRenderer(ShadowStyle shadowStyle, Locale locale) {
super(shadowStyle, locale);
}
public CountingStockListViewRenderer(ShadowStyle shadowStyle, Locale locale, int renderAttributes) {
super(shadowStyle, locale, renderAttributes);
}
4
protected void initializeCustomComponents() {
myCustomLabel = new JLabel();
}
5
protected void initializeCustomValues() {
myCustomLabel.setText(getCatalogItem(renderContext).getSomeProperty());
}
6
protected void renderCustomRack() {
imagePanel.add(myCustomLabel);
}
7
protected void render() {
super.render();
if (renderContext.elementType == RENDER_ELEMENT_TYPE_CELL) {
if (workingComponent.renderContext.rerender) {
myCustomLabel = imagePanel.getComponent(imagePanel.getComponentCount() -1);
initializeCustomValues();
return;
}
initializeCustomComponents();
initializeCustomValues();
renderCustomRack();
}
}
protected void renderComponent(RenderContext renderContext, Graphics2D g) {
//here you can override the background painting. we do nothing here....just to show you
super.renderComponent(renderContext, g);
}
}