2011년 11월 30일 수요일

SWT Part 3 - How to use TabFolder, Canvas, and StyledText


In the previous two installments of A gentle introduction to SWT and JFace, I introduced you to Eclipse, the Eclipse Standard Widget Toolkit (SWT) and the JFace GUI tool kits used to construct Eclipse and stand-alone rich GUIs. I also introduced the several basic GUI controls and container types. Then I showed how to combine these controls into simple working applications. I detailed how to provide those applications with a menu system. Finally, I demonstrated how you can follow best practice by creating a library of methods and classes to ease GUI development.
Here, I continue my survey of the various widgets in the org.eclipse.swt.custom and org.eclipse.swt.widgets packages. Unless otherwise noted, the controls I discuss are in the widgets package.

TableTree

In the previous installment, "How to use combo, list, table, and tree controls," I described the Tree and Table controls. SWT supports a hybrid version of these controls, in the custom package, called TableTree. In Eclipse V3.1, the Tree control was enhanced to be a functional replacement for TableTree, and TableTree was deprecated. Figure 1 shows an example Tree in tabular format (TableTree emulation mode). As you can see, each item in the tree is divided into columns. As the creation of both tables and trees were shown previously, and creating a tabular Tree is basically a combination of these two tasks, I will not repeat them here. The Tree sample included with this article demonstrates the creation of a tabular Tree. The code for creating a TableTree is very similar to the code I include for Tree, so if you need support for older versions of Eclipse, you can use the TableTree control.

Figure 1. Tabular Tree Example
Tabular Tree showing four members of a family and another person
The rest of this article will demonstrate the use of many new SWT controls. I will do this in the context of a single application called TabFolder1App.




TabFolder (and CTabFolder)

TabFolders are a convenient way to create complex GUIs that use limited amount of space. A tab folder is divided into one or more tabs, where each tab is a complete GUI of its own. Only one tab at a time is showing. A CTabFolder, in the custompackage, is an enhanced version of a TabFolder that looks better and can support closure of tabs.
TabFolders and CTabFolders must be defined as one of two mutually exclusive styles:
  1. TOP -- Places tabs on the top.
  2. BOTTOM -- Places tabs on the bottom.
CTabFolder supports some additional optional styles:
  • FLAT -- Gives the folder a flat look.
  • BORDER -- Shows a border around the control.
  • CLOSE -- Allows the tab to close (shows a Close button).
Similar to Trees and Tables that contain items, TabFolders contain TabItems (or CTabItems) that define the tabs. TabFolders also contain multiple controls (typically, Composites), each one defining the contents of a tab. TheTabItem.setControl method links the control to the associated tab.
Figure 2 shows a sample TabFolder, while Figure 3 shows a similar GUI using CTabFolder. Notice that the selected Canvas tab has a Close (X) button on the CTabFolder.

Figure 2. TabFolder with four tabs
Tab Folder showing four tabs with a drawing in the selected tab

Figure 3. CTabFolder with four tabs
Custom Tab Folder showing four tabs with a drawing in the selected tab
In keeping with the approach introduced in "How to use combo, list, table, and tree controls," I will use protected service methods (located in the super class BasicApplication) for creating controls. Listing 1 details the first of these methods and shows how TabFolders are created; similar code exists for CTabFolders.

Listing 1. Methods to create a TabFolder and TabItem
protected TabFolder createTabFolder(Composite parent, int style) {

    return new TabFolder(parent, style);

}

protected TabItem createTabItem(TabFolder parent, int style, 

                      String text, Image icon, Control ctl) {

    TabItem ti = new TabItem(parent, style);

    if (text != null) {

        ti.setText(text);

    }

    if (icon != null) {

        ti.setImage(icon);

    }

    if (ctl != null) {

        ti.setControl(ctl);

    }

    return ti;

}

protected TabItem createTabItem(TabFolder parent, 

                      String text, Image icon, Control ctl) {

    return createTabItem(parent, SWT.NONE, text, icon, ctl);

}


Canvas

One of the most basic control types is Canvas, which is used to either create custom controls or to draw pictures. Figure 2and Figure 3 show examples of using a Canvas to draw a picture composed of overlapping rectangles and ovals. In this drawing, some figures are filled, and others are not. Colors, sizes, and positions are assigned randomly.
Listing 2 shows the code to create a Canvas. To actually draw on a Canvas, you must add a PaintListener to the Canvas. Its paintControl method is called whenever the Canvas needs to redraw any part of its client area. There are two styles of painting:
  1. Draw directly -- Simple, but the content is not stable across redraws.
  2. Build a model before any painting, then redraw from that model -- More complex, but stable. This is generally the preferred method.

Listing 2. Methods to create a Canvas
protected Canvas createCanvas(Composite parent, int style, 

                              PaintListener pl) {

    Canvas c = new Canvas(parent, style);

    if (pl != null) {

        c.addPaintListener(pl);

    }

    return c;

}

protected Canvas createCanvas(Composite parent, PaintListener pl) {

    return createCanvas(parent, SWT.NONE, pl);

}

As an example of painting style 2, consider the simple model defined in Listing 3:

Listing 3. Hierarchy of PaintItems
abstract protected class PaintItem {

    public Color color;

    public void paint(GC gc) {

        gc.setForeground(color);

        gc.setBackground(color);

    }

}

abstract protected class BaseRectItem extends PaintItem {

    public boolean fill;

    public Rectangle extent;

}

protected class ElipseItem extends BaseRectItem {

    public void paint(GC gc) {

        super.paint(gc);

        if (fill) {

            gc.fillOval(extent.x, extent.y, 

                        extent.width, extent.height);

        }

        else {

            gc.drawOval(extent.x, extent.y, 

                        extent.width, extent.height);

        }

    }

}

protected class RectangleItem extends BaseRectItem {

    public void paint(GC gc) {

        super.paint(gc);

        if (fill) {

            gc.fillRectangle(extent.x, extent.y, 

                             extent.width, extent.height);

        }

        else {

            gc.drawRectangle(extent.x, extent.y, 

                             extent.width, extent.height);

        }

    }

}

These paint items are drawn by the PaintListener shown in Listing 4. The paintControl method is provided with a graphic context (GC in the org.eclipse.swt.graphics package) on which to draw. You may draw text and many shapes using the GC. This code reuses standard system colors available through the Display class. It is up to the Canvas to fill its extent with a background color. The gcObjects collection includes all the PaintItem instances that need to be drawn. Array colorIds is a mapping to selected system colors.

Listing 4. Methods to create a TabFolder and TabItem
… new PaintListener() {

    public void paintControl(PaintEvent e) {

        GC gc = e.gc;

        gc.setBackground(canvas.getDisplay().

             getSystemColor(colorIds[0]));

        Point cext = canvas.getSize();

        gc.fillRectangle(0, 0, cext.x, cext.y);

        for (Iterator i = gcObjects.iterator(); 

             i.hasNext();) {

             PaintItem pi = (PaintItem)i.next();

             pi.paint(gc);

        }

   }

}…



protected static int[] colorIds = {

    SWT.COLOR_WHITE, SWT.COLOR_BLUE, SWT.COLOR_CYAN, 

    SWT.COLOR_GRAY, SWT.COLOR_GREEN, SWT.COLOR_MAGENTA, 

    SWT.COLOR_RED, SWT.COLOR_YELLOW, SWT.COLOR_BLACK

};

The code to clear the drawing, then create the drawing as a set of rectangles and ellipses is shown in Listing 5. This code is activated by buttons on the GUI.

Listing 5. Methods to process drawing events
public void doClear() {

    gcObjects.clear();

    canvas.redraw();

}



public void doDraw() {

    gcObjects.clear();

    Display display = drawButton.getDisplay();

    // create a bunch of objects

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

        if (i % 2 == 0) {

            RectangleItem ri = new RectangleItem();

            ri.extent = new Rectangle(nextInt(500), nextInt(250), 

                                      nextInt(500), nextInt(250));

            ri.color = display.

                getSystemColor(colorIds[nextInt(colorIds.length)]);

            ri.fill = i % 3 == 0;

            gcObjects.add(ri);

        }

        else {

            ElipseItem ei = new ElipseItem();

            ei.extent = new Rectangle(nextInt(500), nextInt(250), 

                                      nextInt(500), nextInt(250));

            ei.color = display.

                getSystemColor(colorIds[nextInt(colorIds.length)]);

            ei.fill = i % 5 == 0;

            gcObjects.add(ei);

        }

    }

    canvas.redraw();

}


Spinner, Slider, Scale, and ProgressBar

SWT supports several ways to enter discrete numeric values. A Scale allows you to pick a value in a (typically small) integer range. A Slider allows you to pick a value in a (possibly large) integer range using a scroll bar-like approach. ASpinner allows you to pick (via advance/retreat buttons) or type in a (possibly fractional) number. Note that the Spinner is new in Eclipse V3.1. A ProgressBar is like an output-only Slider in that it can be used to show incremental activity (progress).
In general, these controls allow you to supply a minimum, maximum, and initial value. Except for ProgressBars, they also support increment and page increment values and, for Sliders, thumb width. Figure 4 shows a GUI with a Slider, a Spinner, and a Scale within the group and a ProgressBar below them. Immediately above the progress bar is a (centered) Label that shows the value of the progress bar.

Figure 4. Control examples
Custom Tab Folder showing Progress Bar tab containing Slider, Spinner, Scale, ProgressBar, and other controls
All these controls must be defined as one of two mutually exclusive styles:
  1. HORIZONTAL -- Layout the control horizontally.
  2. VERTICAL -- Layout the control vertically.
Spinners support some additional optional styles:
  • WRAP -- Wrap from high to low value.
  • READ_ONLY -- Do not allow typed input values.
ProgressBars support some additional optional styles:
  • SMOOTH -- Update is not in distinct steps.
  • INDETERMINATE -- There is no predetermined bound on the number of steps; the bar just keeps repeating over time.
To create these controls, use the code shown in listings 6-9. As described in "How to use combo, list, table, and tree controls," Java reflection is used, via the registerCallback method, to add SelectionListeners to these controls. This listener is invoked whenever the value of the control changes.

Listing 6. Methods to create a Slider
protected Slider createSlider(Composite parent, int style, 

                              int min, int current, int max, 

                              int inc, int pageinc, int thumb, 

                              String callback) {

    Slider s = new Slider(parent, style);

    if (min >= 0) {

        s.setMinimum(min);

    }

    if (max >= 0) {

        s.setMaximum(max);

    }

    if (current >= 0) {

        s.setSelection(current);

    }

    if (inc >= 1) {

        s.setIncrement(inc);

    }

    if (pageinc >= 1) {

        s.setPageIncrement(pageinc);

    }

    if (thumb >= 1) {

        s.setThumb(thumb);

    }

    if (callback != null) {

        registerCallback(s, this, callback);

    }

    return s;

}

protected Slider createVSlider(Composite parent, 

                               int min, int current, int max, 

                               int inc, int pageinc, int thumb, 

                               String callback) {

    return createSlider(parent, SWT.VERTICAL, min, current, max, 

                        inc, pageinc, thumb, callback);

}

protected Slider createHSlider(Composite parent, 

                               int min, int current, int max, 

                               int inc, int pageinc, int thumb, 

                               String callback) {

    return createSlider(parent, SWT.HORIZONTAL, min, current, max, 

                        inc, pageinc, thumb, callback);

}


Listing 7. Method to create a Spinner
protected Spinner createSpinner(Composite parent, int style, 

                                int min, int current, int max, 

                                int inc, int pageinc, String callback) {

    Spinner s = new Spinner(parent, style);

    if (min >= 0) {

        s.setMinimum(min);

    }

    if (max >= 0) {

        s.setMaximum(max);

    }

    if (current >= 0) {

        s.setSelection(current);

    }

    if (inc >= 1) {

        s.setIncrement(inc);

    }

    if (pageinc >= 1) {

        s.setPageIncrement(pageinc);

    }

    if (callback != null) {

        registerCallback(s, this, callback);

    }

    return s;

}


Listing 8. Method to create a Scale
protected Scale createScale(Composite parent, int style, 

                            int min, int current, int max, 

                            int inc, int pageinc) {

    Scale s = new Scale(parent, style);

    if (min >= 0) {

        s.setMinimum(min);

    }

    if (max >= 0) {

        s.setMaximum(max);

    }

    if (current >= 0) {

        s.setSelection(current);

    }

    if (inc >= 1) {

        s.setIncrement(inc);

    }

    if (pageinc >= 1) {

        s.setPageIncrement(pageinc);

    }

    return s;

}

protected Scale createVScale(Composite parent, 

                             int min, int current, int max, 

                             int inc, int pageinc) {

    return createScale(parent, SWT.VERTICAL, min, current, max, 

                       inc, pageinc);

}

protected Scale createHScale(Composite parent, 

                             int min, int current, int max, 

                             int inc, int pageinc) {

    return createScale(parent, SWT.HORIZONTAL, min, current, max, 

                       inc, pageinc);

}


Listing 9. Method to create a ProgressBar
protected ProgressBar createProgressBar(Composite parent, int style, 

                                        int min, int current, int max) {

    ProgressBar pb = new ProgressBar(parent, style);

    if (min >= 0) {

        pb.setMinimum(min);

    }

    if (max >= 0) {

        pb.setMaximum(max);

    }

    if (current >= 0) {

        pb.setSelection(current);

    }

    return pb;

}

protected ProgressBar createVProgressBar(Composite parent, 

                                         int min, int current, int max) {

    return createProgressBar(parent, SWT.VERTICAL, min, current, max);

}

protected ProgressBar createHProgressBar(Composite parent, 

                                         int min, int current, int max) {

    return createProgressBar(parent, SWT.HORIZONTAL, min, current, max);

}

You can query or set the current value of these controls. Consider the thread defined in Listing 10, which updates the label, progress bar and the slider in Figure 4. This thread is enabled when the "Automatic Update" Button (known as modeButton in the code) is selected.

Listing 10. Thread to update controls
protected class BarUpdater extends Thread {

    protected int delay;

    protected Display display;



    public BarUpdater(Display display) {

        this.display = display;

    }



    public void run() {

        try {

            while (true) {

                try {

                    if (!display.isDisposed()) {

                        display.syncExec(new Runnable() {

                                public void run() {

                                    if (!modeButton.isDisposed() &&

                                        !scale.isDisposed()) {

                                        delay = modeButton.getSelection() 

                                               ? scale.getSelection() : -1;

                                    }

                                }

                        });

                        if (delay >= 0) {

                            Thread.sleep(delay);

                            if (!display.isDisposed()) {

                                display.syncExec(new Runnable() {

                                    public void run() {

                                        if (!bar.isDisposed()) {

                                            int v = bar.getSelection() + 1;

                                            if (v > bar.getMaximum()) {

                                                v = bar.getMinimum();

                                            }

                                                    bar.setSelection(v);

                                            if (!slider.isDisposed()) {

                                                slider.setSelection(v);

                                            }

                                            if (!valueLabel.isDisposed()) {

                                                valueLabel.setText(

                                                       Integer.toString(v));

                                            }

                                        }

                                    }

                                });

                            }

                        }

                    }

                    Thread.sleep(100);

                }

                catch (InterruptedException ie) {

                }

            }

        }

        catch (Exception e) {

             e.printStackTrace();

        }

    }

}

Note that this code is careful to check to see if the various controls have been disposed before they are used. This is critical in asynchronous GUI manipulation. Also notice that all GUI access is done inside a syncExec (or its cousin asyncExec) method. This is needed whenever a GUI is accessed on a different thread from the one the GUI was created on.

StyledText

As described in the first installment, "How to create a simple SWT application," SWT supports plain text entry and display through the Text control. For more advanced text presentation, where fonts and colors need to be defined, use theStyledText control found in the custom package. StyledText is the control used by many Eclipse editors. Consider the sample of styled text shown in Figure 5. This text includes different colors and font modification, such as underline, strikeout, bold, and italic text. Note that strikeout and underline are supported only on Eclipse V3.1.

Figure 5. StyledText examples
Custom Tab Folder showing the Styled Text tab showing text with multiple styles
StyledText must be defined as one of two mutually exclusive styles:
  1. MULTI -- Displays multiple lines.
  2. SINGLE -- Displays a single line.
StyledText supports some additional optional styles:
  • WRAP -- Wrap lines at the right edge of the control.
  • READ_ONLY -- Do not allow typed input values.
Listing 11 shows the code to create a StyledText. Listing 12 shows its use with simple XML-like language to define the ranges of text with attributes.

Listing 11. Method to create a StyledText
protected StyledText createStyledText(Composite parent, int style) {

    return new StyledText(parent, style);

}


Listing 12. StyledText Example
styledText = createStyledText(body, SWT.MULTI | SWT.WRAP | 

                                    SWT.FULL_SELECTION);

styledText.addMouseListener(new MouseAdapter() {

    public void mouseDown(MouseEvent e) {

        if (e.button == 3) {

            processPopup();

        }

    }});

TextContent tc = new TextContent(body.getDisplay());

tc.setContent(dummyContent);

styledText.setText(tc.toPlainText());

styledText.setStyleRanges(tc.getStyleRanges());

 :

protected static final String dummyContent = 

"Just plain text!\n" +

"Now is the time for all good men " + 

"to come to the aid of their country\n" +

"To be or not to be? " + 

"That is the question\n" +

"That's all folks!\n";

The StyledText example uses a helper class, TextContent, to identify ranges of text with special attributes. This class, included in the sample code, has support to parse the document and get its plain text content and find the various ranges within that plain text content with different attributes or attribute combinations. It creates an array of the SWT classStyleRange that represent these ranges. StyleRange has several fields that describe the range including foreground and background color, start offset and length and the attributes to apply, such as font style (bold or italic), strikeout, and underline.
StyledText has more function than shown here. For example, when you enter text into the document, it has no attributes, even if entered at an attributed point. You can make use of the features of StyledText to correct this behavior.

PopupList

There are times when you want to have the ability to display a list of choices as a pop-up window without creating a pop-up menu. The PopupList control can be used to do this. Figure 6 shows use of this control to add select, cut, copy, and paste functions to the text editor of Figure 5.

Figure 6. PopupList example
Example Popup List showing four choices: Select All, Cut, Copy, Paste
Listing 13 shows the content of the processPopup method. Note the code to position the pop-up relative to the control it relates to (in other words, the styled text).

Listing 13. PopupList Example
PopupList popup = createPopupList(shell, 50, popupItems);

popup.select(popupItems[0]);

Point p = styledText.getLocation();

p = shell.getDisplay().map(styledText, null, p.x, p.y);

String choice = popup.open(new Rectangle(

                    p.x + 100, p.y - 100, 100, 200));

if (choice != null) {

    if      (popupItems[0].equals(choice)) {

        styledText.selectAll();

    }

    else if (popupItems[1].equals(choice)) {

        styledText.cut();

    }

    else if (popupItems[2].equals(choice)) {

        styledText.copy();

    }

    else if (popupItems[3].equals(choice)) {

        styledText.paste();

    }

}

:

protected static final String[] popupItems = {

    "Select All", "Cut", "Copy", "Paste"

};


StackLayout

In the first two installments, I discussed several of the layout managers supplied with SWT, including the FillLayout,GridLayout, and FormLayout. The custom package provides the StackLayout, which can be used to place multiple GUIs on top of each other with only one GUI showing at a time (something like a TabFolder without the tabs). Consider Figure 7and Figure 8 that show two states of a stack layout showing numbered labels. The stack is advanced by the ">>" button and retreated by the "<<" button. Figure 8 shows the layout after pressing ">>" four times.

Figure 7. StackLayout example 1
Example use of Stack Layout showing an initial label

Figure 8. StackLayout example 2
Example use of Stack Layout showing a final label
This stack was created by the code in Listing 14.

Listing 14. StackLayout Example
StackLayout stackLayout = new StackLayout();

Composite clabels = createComposite(body, SWT.BORDER, 

                                    stackLayout);

Label[] labels = new Label[5];

:

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

    Label xlabel = new Label(clabels, SWT.CENTER);

    xlabel.setText("Stack " + i);

    labels[i] = xlabel;

}

stackLayout.topControl = labels[0];

:

protected Composite createComposite(Composite parent, 

                                    int style, 

                                    Layout layout) {

    Composite c =  new Composite(parent, style);

    if (layout != null) {

        c.setLayout(layout);

    }

    return c;

}

protected Composite createComposite(Composite parent, 

                                    Layout layout) {

    return createComposite(parent, SWT.NONE, layout);

}

Listing 15 shows the code to advance to the next stack via the ">>" button. The code for the "<<" button is similar.

Listing 15. Code to advance to the next stack
protected int currentLabel;



public void doNext() {

    ++currentLabel;

    if (currentLabel >= labels.length) {

        currentLabel = 0;

    }

    stackLayout.topControl = labels[currentLabel];

    clabels.layout();

}


Conclusion

In this third installment of A gentle introduction to SWT and JFace, I have introduced many more SWT Controls, such as Tree for creating tabular trees; Canvas for drawing pictures; Slider, Scale, and Spinner for entering numeric values; ProgressBar for displaying progress; StyledText for entry of text with attributes; and PopupList for simple dynamic menus. I have also shown how to use the StackLayout to create time-wise overlapping GUIs. The next installment will show you how to use more SWT controls.

댓글 1개:

  1. There are many POE Currency projects in Path of Exile that players need to understand.

    Attached link: https://www.poecurrency.com/

    답글삭제