2011년 11월 30일 수요일

SWT Part 2 - How to use combo, list, table, and tree controls


Programmers use the Standard Widget Toolkit (SWT) and JFace libraries to develop graphical user interfaces (GUIs) for the Eclipse environment, and to develop stand-alone GUI native applications.
In the first installment of this series, "How to create a simple SWT application," I introduced you to Eclipse, the Eclipse SWT, and the JFace GUI tool kits to construct Eclipse and stand-alone rich GUIs. I also introduced the basic label, text, and button GUI controls, and the composite, group, and shell container types. Finally, I showed you how to combine these controls into a simple working application.
Here, you will learn how to add menus to your application, use some list input controls, and use the more advanced table and tree container controls. I also will demonstrate some best practices by employing service methods to easily build GUIs. Finally, I will show you how to extract reusable function into a base application class.
Except where noted, all the widgets and controls discussed are in the org.eclipse.swt.widgets package.

Menus

All but the most primitive GUI applications require menus. Menus add to any GUI's usability. Menus are dynamically presented selection lists that correspond to functions available for use (often called commands) or GUI states. As you would expect, you create menus with the menu widget. Menus can contain other menus or menuItems, which can contain menus (that is, a hierarchy of menus). MenuItems represent the commands you can perform or the GUI state you selected. Menus can be associated with the application's (that is, shell) menu bar or they can be pop-up menus that float over the application window.
You must define menus as one of three mutually exclusive styles:
  1. BAR acts as the menu bar for the shell
  2. DROP_DOWN drops down from the menu bar or a menu item
  3. POP_UP pops up from the shell, but is contextual to a specific control
Menus support some additional optional styles:
  • NO_RADIO_GROUP does not act as a radio group; use it when the menu contains RADIO-style items
  • LEFT_TO_RIGHT or RIGHT_TO_LEFT selects the text direction
You must define menuItems as one of five mutually exclusive styles:
  1. CHECK can be persistently selected (that is, checked)
  2. CASCADE contains a menu that should drop down
  3. PUSH acts like a button that causes an immediate action
  4. RADIO acts like a CHECK where only one of the items with this type can be selected
  5. SEPARATOR acts as a separator (often a bar) between groups of items; this item has no function
Creating a menu system is fairly complex. Listing 1 shows a code sample that creates an operable menu system.

 http://www.ibm.com/developerworks/library/os-jface2/index.html




Listing 1. Creating a menu system and a pop-up menu
import org.eclipse.swt.SWT;

import org.eclipse.swt.widgets.*;

import org.eclipse.swt.events.*;

import org.eclipse.swt.graphics.*;

  :

Shell shell = ...;

  :

Label body = ...;

  :

// Create the menu bar system

Menu main = createMenu(shell, SWT.BAR | SWT.LEFT_TO_RIGHT);

shell.setMenuBar(main);



MenuItem fileMenuItem = createMenuItem(main, SWT.CASCADE, "&File", 

                                       null, -1, true, null);

Menu fileMenu = createMenu(shell, SWT.DROP_DOWN, fileMenuItem, true);

MenuItem exitMenuItem = createMenuItem(fileMenu, SWT.PUSH, "E&xit\tCtrl+X", 

                                       null, SWT.CTRL + 'X', true, "doExit");



MenuItem helpMenuItem = createMenuItem(main, SWT.CASCADE, "&Help", 

                                       null, -1, true, null);

Menu helpMenu = createMenu(shell, SWT.DROP_DOWN, helpMenuItem, true);

MenuItem aboutMenuItem = createMenuItem(helpMenu, SWT.PUSH, "&About\tCtrl+A", 

                                        null, SWT.CTRL + 'A', true, "doAbout");



// add popup menu

Menu popup = createPopupMenu(shell, body);

MenuItem popupMenuItem1 = createMenuItem(popup, SWT.PUSH, "&About", 

                                         null, -1, true, "doAbout");

MenuItem popupMenuItem2 = createMenuItem(popup, SWT.PUSH, "&Noop", 

                                         null, -1, true, "doNothing");

The code sequence creates the following menu bar, with sub-menus and a pop-up menu (see Figure 1Figure 2Figure 3, and Figure 4). The body value is a label control with the text "Sample body." The pop-up menu is contextually associated with this control.

Figure 1. Menu bar with File and Help menus
Menu bar with File and Help menus 

Figure 2. File menu drop-down
File menu drop-down 

Figure 3. Help menu drop-down
Help menu drop-down 

Figure 4. Pop-up menu
Pop-up menu 
As you can see, menu items can have accelerators (Ctrl+?) and mnemonics (underlined characters identified by &) to help the user select items with the keyboard.
I created these menus with a set of helper methods, shown in Listing 2. Best practice is to create methods like this to use in creating repetitive GUI parts, such as menus. Over time, you can add more support functions to these helper methods and apply them to all use points. These methods also help prompt you for all the required values.

Listing 2. Menu creation helper routines
protected Menu createMenu(Menu parent, boolean enabled) {

    Menu m = new Menu(parent);

    m.setEnabled(enabled);

    return m;

}

protected Menu createMenu(MenuItem parent, boolean enabled) {

    Menu m = new Menu(parent);

    m.setEnabled(enabled);

    return m;

}

protected Menu createMenu(Shell parent, int style) {

    Menu m = new Menu(parent, style);

    return m;

}

protected Menu createMenu(Shell parent, int style, 

                          MenuItem container, boolean enabled) {

    Menu m = createMenu(parent, style);

    m.setEnabled(enabled);

    container.setMenu(m);

    return m;

}

protected Menu createPopupMenu(Shell shell) {

    Menu m = new Menu(shell, SWT.POP_UP);

    shell.setMenu(m);

    return m;

}

protected Menu createPopupMenu(Shell shell, Control owner) {

    Menu m = createPopupMenu(shell);

    owner.setMenu(m);

    return m;

}

protected MenuItem createMenuItem(Menu parent, int style, String text, 

                                  Image icon, int accel, boolean enabled, 

                                  String callback) {

    MenuItem mi = new MenuItem(parent, style);

    if (text != null) {

        mi.setText(text);

    }

    if (icon != null) {

        mi.setImage(icon);

    }

    if (accel != -1) {

        mi.setAccelerator(accel);

    }

    mi.setEnabled(enabled);

    if (callback != null) {

        registerCallback(mi, this, callback);

    }

    return mi;

}

Listing 3 shows how to use the Java reflection feature to link the menu items with the code that processes them. This feature creates an easy-to-use method where you simply add a public method (such as doExitdoAbout, or doNothing) to your application class to process the command.

Listing 3. Callback routines to process menu command
protected void registerCallback(final MenuItem mi, 

                                final Object handler, 

                                final String handlerName) {

    mi.addSelectionListener(new SelectionAdapter() {

        public void widgetSelected(SelectionEvent e) {

            try {

                Method m = handler.getClass().getMethod(handlerName, null);

                m.invoke(handler, null);

            }

            catch (Exception ex) {

                ex.printStackTrace();

            }

        }

    });

}

I described the details of using a SelectionListener in the first installment of this series, "How to create a simple SWT application."
Please note that menu items (and items in the list, combo, table, and tree controls discussed later) support only string values; other types must be converted to string values before you add them.

Combos and lists

Often, you want your GUI's user to select from a predetermined list of values. The list control is the easiest way to do this. A list shows a predetermined set of string values that the user can select from. Lists typically require a large amount of screen real estate. If you want to save space, you can use a combo control that allows the list to drop down when needed. Combos also optionally allow the user to enter the desired value in a text-like field.
You must define combos as one of two mutually exclusive styles:
  1. SIMPLE shows the list of values
  2. DROP_DOWN drops down the list of values
Combos support an optional style:
  • READ_ONLY prevents users from editing the text field of this combo
All the controls I discuss (list, combo, table, and tree) support one of two mutually exclusive styles:
  1. SINGLE users can select only one item
  2. MULTI users can select multiple items
These controls also support additional styles:
  • H_SCROLL shows a horizontal scroll bar when needed
  • V_SCROLL shows a vertical scroll bar when needed
Creating combos and lists is fairly easy. Create the controls and add the desired string values as shown in Listing 4.

Listing 4. Creating a combo and a list using FormLayout
import org.eclipse.swt.SWT;

import org.eclipse.swt.widgets.*;

import org.eclipse.swt.events.*;

import org.eclipse.swt.layout.*;

  :

setLayout(new FormLayout());



String[] data = { "Item 1", "Item 2", "Item 3", "Item 4", "Item 5",

                  "Item 6", "Item 7", "Item 8", "Item 9", "Item 10" };



Combo c = createCombo(this, data);

configureLayout(c, new FormAttachment(0, 5), new FormAttachment(0, 5), 

                   new FormAttachment(100, -5), null);



List l = createList(this, data);

configureLayout(l, new FormAttachment(0, 5), new FormAttachment(c, 5), 

                   new FormAttachment(100, -5), new FormAttachment(100, -5));



// Create a Combo

protected Combo createCombo(Composite parent, String[] data) {

    Combo combo = new Combo(parent, 

                            SWT.DROP_DOWN | SWT.MULTI | 

                            SWT.V_SCROLL | SWT.H_SCROLL);

    combo.addSelectionListener(new SelectionListener() {

        :

    });

    setComboContents(data);



    return combo;

}

// Create a List

protected List createList(Composite parent, String[] data) {

    List list = new List(parent, SWT.MULTI | 

                         SWT.V_SCROLL | SWT.H_SCROLL);

    list.addSelectionListener(new SelectionListener() {

        :

    });

    setListContents(data);



    return list;

}



public void setComboContents(String[] data) {

    combo.removeAll();

    for (int i = 0; i < data.length; i++) {

        combo.add(data[i]);

    }

}

public void setListContents(String[] data) {

    list.removeAll();

    for (int i = 0; i < data.length; i++) {

        list.add(data[i]);

    }

}

If you add the SelectionListener, it enables the application to take action when the user changes the selected items.
The flow of the main code sequence in Listing 4 assumes it is included in some composite referenced by this. It creates the combo and (partially hidden) list shown in Figure 5.

Figure 5. Combo and list example
Combo and list example 
You can use an alternate implementation to the combo control, called CCombo (located in the org.eclipse.swt.custompackage). CCombo is similar to Combo, except that it supports some additional functions, the most significant being that you can programmatically ask a CCombo to cut, copy, or paste text into or from its embedded Text control. Also, a CCombo is always in the DROP_DOWN style, so it doesn't support the type styles.
CCombos also support optional styles:
  • BORDER shows a border around the text area
  • READ_ONLY prevents users from editing the text field of this combo
The example in Listing 4 uses a FormLayout to place the combo and list. FormLayout is one of the most useful layout managers because it allows you to lay out each control relative to other controls. It allows you to attach any side (left, top, right, or bottom) of a control to the (typically opposing) side of another control or to the side of the container. Unattached sides take the natural corresponding dimension of the control. Use an instance of FormAttachment to specify the reference control or a percentage of the container size as the attachment point, and to provide a pixel offset from that point. The Listing 4 code uses the helper method from Listing 5.

Listing 5. configureLayout: FormLayout helper method
protected static void configureLayout(Control c, 

                                      FormAttachment left, 

                                      FormAttachment top, 

                                      FormAttachment right, 

                                      FormAttachment bottom) {

    FormData fd = new FormData();

    if (left != null) {

        fd.left = left;

    }

    if (top != null) {

        fd.top = top;

    }

    if (right != null) {

        fd.right = right;

    }

    if (bottom != null) {

        fd.bottom = bottom;

    }

    c.setLayoutData(fd);

}


Tables

Tables are enhanced forms of lists that support TableColumns. These columns align their data into a more readable format. They also support column names and the ability to resize the columns. To create tables, first create the table control, add its table columns, then add the string data wrapped in TableItems.
Tables support optional styles:
  • CHECK adds check boxes to the first column
  • VIRTUAL support for large tables (platform-specific)
  • FULL_SELECTION selects all columns (not just the first column)
Listing 6 creates the table shown in Figure 6.

Listing 6. Creating a table using helper methods
// Create the Table and TableColumns 

protected Table createTable(Composite parent, int mode, Object[] contents) {

    table = new Table(parent, mode | SWT.SINGLE | SWT.FULL_SELECTION | 

                      SWT.V_SCROLL | SWT.H_SCROLL);

    table.setHeaderVisible(true);

    table.setLinesVisible(true);



    createTableColumn(table, SWT.LEFT,   "Column 1", 100);

    createTableColumn(table, SWT.CENTER, "Column 2", 100);

    createTableColumn(table, SWT.RIGHT,  "Column 3", 100);



    addTableContents(contents);

    return table;

}

protected TableColumn createTableColumn\

(Table table, int style, String title, int width) {

    TableColumn tc = new TableColumn(table, style);

    tc.setText(title);

    tc.setResizable(true);

    tc.setWidth(width);

    return tc;

}

protected void addTableContents(Object[] items) {

    for (int i = 0; i < items.length; i++) {

        String[] item = (String[])items[i];

        TableItem ti = new TableItem(table, SWT.NONE);

        ti.setText(item);

    }

}

  :

// sample creation code

protected void initGui() {

    Object[] items = {

        new String[] {"A", "a", "0"}, new String[] {"B", "b", "1"},

        new String[] {"C", "c", "2"}, new String[] {"D", "d", "3"},

        new String[] {"E", "e", "4"}, new String[] {"F", "f", "5"},

        new String[] {"G", "g", "6"}, new String[] {"H", "h", "7"},

        new String[] {"I", "i", "8"}, new String[] {"J", "j", "9"}

    };

    table = createTable(this, SWT.CHECK, items);

}


Figure 6. Table example
Table example 
The check boxes in the first column are optional. Note the alignment of the columns.

Trees

tree is a list that can show hierarchical information. Trees support the application's ability to expand and collapse intermediate levels of the hierarchy.
Because trees often display hierarchical structures, you should provide a data model for them to use (I will revisit this model notion later when I talk about JFace). In my example, I use the inner class Node, shown in Listing 7, for this purpose.

Listing 7. Tree model class node
public class Node {

    protected java.util.List children;

    public java.util.List getChildren() {

        return children;

    }

    public void setChildren(java.util.List children) {

        this.children = children;

    }

    public void addChild(Node node) {

        children.add(node);

    }



    protected String name;

    public String getName() {

        return name;

    }

    public void setName(String name) {

        this.name = name;

    }



    public Node(String name) {

        this(name, new ArrayList());

    }

    public Node(String name, java.util.List children) {

        setName(name);

        setChildren(children);

    }

}

To create trees, first create the tree control, then add the string data wrapped in TreeItems. TreeItems can contain other TreeItems, thus creating the hierarchy of values. Listing 8 creates the tree shown in Figure 7.

Listing 8. Creating a tree using helper methods
// Create the Tree 

protected Tree createTree(Composite parent, int mode, Node root) {

    tree = new Tree(parent, mode | SWT.MULTI | SWT.V_SCROLL | SWT.H_SCROLL);

    tree.addSelectionListener(new SelectionListener() {

        :

     });

    setTreeContents(root);

    return tree;

}

protected void setTreeContents(Node root) {

    tree.removeAll();

    TreeItem ti = new TreeItem(tree, SWT.NONE);

    setTreeItemContents(ti, root);



}

protected void setTreeItemContents(TreeItem ti, Node root) {

    ti.setText(root.getName());

    java.util.List children = root.getChildren();

    if (children != null && children.size() > 0) {

        for (Iterator i = children.iterator(); i.hasNext();) {

            Node n = (Node)i.next();

            TreeItem tix = new TreeItem(ti, SWT.NONE);

            setTreeItemContents(tix, n);

        }

    }

}

  :

// sample creation code

protected void addChildren(Node n, int count, int depth, String prefix) {

    if (depth > 0) {

        for (int i = 0; i < count; i++) {

            String name = prefix + '.' + i;

            Node child = new Node(name);

            n.addChild(child);

            addChildren(child, count, depth - 1, name);

        }

    }

}



Node root = new Node("");

addChildren(root, 3, 3, "Child");

tree = createTree(this, SWT.CHECK, root);


Figure 7. Tree example
Tree example 
The check boxes are optional.

Building a base program

Except for the menu example, all the examples in this article use a base class, called BasicApplication, to simplify their implementation. As another best-practice example, I factored out the common features of an SWT GUI application into this base class (including the helper methods from the menu example) to make them easily available.
BasicApplication is a composite that creates its own shell. This class provides additional services, like an exit verification dialog (see Figure 8) and the ability to dump out the widget tree as a diagnostic aid (see an abridged example in Listing 9). See Download for the code to this class.

Figure 8. Confirmation message dialog
Confirmation message dialog 

Listing 9. Printout of (partial) control hierarchy
Shell {Tree1App Example}

    Tree1App {}

        Tree {}

            TreeItem {}

                TreeItem {Child.0}

                    TreeItem {Child.0.0}

                        TreeItem {Child.0.0.0}

                        TreeItem {Child.0.0.1}

                        TreeItem {Child.0.0.2}

                    TreeItem {Child.0.1}

                        TreeItem {Child.0.1.0}

                        TreeItem {Child.0.1.1}

                        TreeItem {Child.0.1.2}

                    TreeItem {Child.0.2}

                        TreeItem {Child.0.2.0}

                        TreeItem {Child.0.2.1}

                        TreeItem {Child.0.2.2}

                TreeItem {Child.1}

                        :

                TreeItem {Child.2}

                        :

Listing 10 shows the main method of each subclass (from the combo and list example in Listing 4), and provides the shell's title and size, the application composite's style, and any command-line inputs.

Listing 10. Main method for example list application
public static void main(String[] args) {

     run(List1App.class.getName(), "List1App Example", SWT.NONE, 400, 300, args);

}

Each subclass, which is loaded through Java technology reflection, must define a constructor and the completeGui method. Subclasses may optionally provide the initGui method. Again using the combo and list application in Listing 4 as an example, these methods are shown in Listing 11.

Listing 11. Required methods supplied in the application subclass
public List1App(Shell shell, int style) {

    super(shell, style);   // must always supply parent and style

}



// Allow subclasses to complete the GUI 

protected void completeGui(String[] args) {

    // create GUI here 

    :

}



// Allow subclasses to initialize the GUI 

protected void initGui() {

    // finish GUI and add dynamic contents here

    :

}

MessageBox

Before I close, I will show you how to use the MessageBox control to request the user to input choices.
You must define MessageBoxes as one of five mutually exclusive styles:
  1. ICON_ERROR presents an error message
  2. ICON_INFORMATION presents an information message
  3. ICON_QUESTION presents a question message
  4. ICON_WARNING presents a warning message
  5. ICON_WORKING presents a working message
MessageBoxes support some additional optional styles, all of which present their respective choices on buttons:
  • OK, OK | CANCEL
  • YES | NO, YES | NO | CANCEL
  • RETRY | CANCEL
  • ABORT | RETRY | IGNORE
Listing 12 shows a typical use of MessageBox, which presents the confirmation dialog as seen in Figure 8 when the user closes the application shell.

Listing 12. Using MessageBox to create an exit confirmation dialog
shell.addShellListener(new ShellAdapter() {

        public void shellClosed(ShellEvent e) {

            MessageBox mb = new MessageBox(shell, SWT.ICON_QUESTION | SWT.OK | SWT.CANCEL);

            mb.setText("Confirm Exit");

            mb.setMessage("Are you sure you want to exit?");

            int rc = mb.open();

            e.doit = rc == SWT.OK;

        }

});


Conclusion

In this second installment of "A gentle introduction to SWT and JFace", I introduced more SWT controls: combo, list, table, and tree. I also showed you how to create a base class for your SWT applications and how to use helper methods to make it easier to build GUIs.
The next installment will show you how to create more containers and input controls, and how to use the StackLayoutlayout manager.

댓글 없음:

댓글 쓰기