Tutorial: Creating a Stock Watcher with GWT Designer

This tutorial demonstrates how to use the GUI builder, GWT Designer, to create and design a Stock Watcher application based on the GWT tutorial. To get the most out of this tutorial make sure to at least skim over the GWT Tutorial first. Also, they intentionally introduced a bug that they later fixed. We're going to skip that and just have the code already fixed in this tutorial.

Prerequisites: 

Note: This tutorial was created using Eclipse 3.4, Java 1.6, GWT v1.5 and GWT Designer v5.1. 
Special thanks to Chad Lung for creating this tutorial.

Assuming everything is installed lets go ahead and begin. 

Set the path to the GWT installation directory

First, we need to set the path to the GWT installation directory. Launch Eclipse and open the preferences window by selecting Window > Preferences from the menu. Select the Designer > GWT preference and browse to your GWT installation directory.

Create a new GWT Java Project

Next, we need to create a new GWT Java project. Select File -> New Project from the main menu. From the new project window expand Designer > GWT and select GWT Java Project.

Press the "Next" button. In the New Project screen enter "StockWatcher" for the project name and press the "Next" button In the following screen click on the "Create GWT Module" checkbox and enter "StockWatcher" for the Module name and com.google.gwt.sample.stockwatcher.client.StockWatcher for the Package name.

At this point you can press the "Finish" button. You should see some code that looks like the following in the StockWatcher.java file:

package com.google.gwt.sample.stockwatcher.client.StockWatcher.client;

import com.google.gwt.core.client.EntryPoint;
import com.google.gwt.user.client.Window;
import com.google.gwt.user.client.ui.Button;
import com.google.gwt.user.client.ui.ClickListener;
import com.google.gwt.user.client.ui.RootPanel;
import com.google.gwt.user.client.ui.Widget;

/**
 * Entry point classes define <code>onModuleLoad()</code>.
 */

public class StockWatcher implements EntryPoint
{
    private Button clickMeButton;

    public void onModuleLoad()
    {
        RootPanel rootPanel = RootPanel.get();

        clickMeButton = new Button();
        rootPanel.add(clickMeButton);
        clickMeButton.setText("Click me!");
        clickMeButton.addClickListener(new ClickListener() {

            public void onClick(Widget sender)
            {
                Window.alert("Hello, GWT World!");
            }
        });
    }
}

We are going to get rid of the "Hello World" boilerplate code and button. Click on the "Design" tab at the bottom of the source file editor and GWT Designer will render a visual layout of our page. If you don't see the Design tab, right click on the Java class and select Open With > Designer Editor.

When you're in Design mode, you should see the Designer's Palette that lists all the supported Panels and Widgets. Let's remove the button so click on the "Click me!" button and press "delete".

Save your project and then click on the "Source" tab (down where the Design tab was) and you should have code that now looks like this:

package com.google.gwt.sample.stockwatcher.client.StockWatcher.client;

import com.google.gwt.core.client.EntryPoint;
import com.google.gwt.user.client.Window;
import com.google.gwt.user.client.ui.Button;
import com.google.gwt.user.client.ui.ClickListener;
import com.google.gwt.user.client.ui.RootPanel;
import com.google.gwt.user.client.ui.Widget;

/**
 * Entry point classes define <code>onModuleLoad()</code>.
 */

public class StockWatcher implements EntryPoint
{

    public void onModuleLoad()
    {
        RootPanel rootPanel = RootPanel.get();
    }
}

According to the GWT tutorial our end product will look like this:

Design the UI

Lets start designing the UI now. The first thing we add is a vertical panel. The documentation states that a vertical panel is: "A panel that lays all of its widgets out in a single vertical column". Go back to the GWT Designer by pressing the "Design" tab at the bottom of the source screen and then locate the "Vertical Panel" from the "Panels" tab of the GWT Designer's Palette and click on it.

Then simply click inside the root panel to place it. Adjust the Vertical Panel to coordinates 0,0. You can adjust the size later, for now just stretch it out to a comfortable size.

Change the vertical panel's "variable" property to "mainPanel".

Inside of this Vertical Panel we need several other items. The first is the FlexTable. So click on the FlexTable and then click inside of the vertical panel to place it near the top. Change the FlexTable's variable property to "stocksFlexTable". Now, click on the Horizontal Panel and place it below the FlexTable and change it's variable name to "addPanel". Inside of the Horizontal Panel we will add two items:
1. TextBox with the variable name "newSymbolTextBox"
2. Button with the variable name "addButton" and a text property value of "Add"
Finally add a Label below the Horizontal Panel and change it's variable name to "lastUpdatedLabel".

Now before we do anything else, we need to convert these panels and widgets into local fields. The easiest way to do this is to let the GWT Designer do it for you. Click on the top most panel, the Vertical Panel in the designer and then just above the properties click on the "Convert to local field" button.

Now, do this for the FlexTable, Horizontal Panel, TextBox, Button and the Label. You can avoid doing this in the future if you go into the GWT Designer's preferences settings and change this so that your variables are always declared as class level variables. Your code should look like this when your're done:


package com.google.gwt.sample.stockwatcher.client.StockWatcher.client;

import com.google.gwt.core.client.EntryPoint;
import com.google.gwt.user.client.Window;
import com.google.gwt.user.client.ui.Button;
import com.google.gwt.user.client.ui.ClickListener;
import com.google.gwt.user.client.ui.FlexTable;
import com.google.gwt.user.client.ui.HorizontalPanel;
import com.google.gwt.user.client.ui.Label;
import com.google.gwt.user.client.ui.RootPanel;
import com.google.gwt.user.client.ui.TextBox;
import com.google.gwt.user.client.ui.VerticalPanel;
import com.google.gwt.user.client.ui.Widget;

/**
 * Entry point classes define <code>onModuleLoad()</code>.
 */

public class StockWatcher implements EntryPoint
{

    private HorizontalPanel addPanel;
    private FlexTable stocksFlexTable;
    private VerticalPanel mainPanel;
    private Label lastUpdatedLabel;
    private Button addButton;
    private TextBox newSymbolTextBox;
   
    public void onModuleLoad()
    {
        RootPanel rootPanel = RootPanel.get();

        mainPanel = new VerticalPanel();
        rootPanel.add(mainPanel, 0, 0);
        mainPanel.setSize("262px", "338px");

        stocksFlexTable = new FlexTable();
        mainPanel.add(stocksFlexTable);

        addPanel = new HorizontalPanel();
        mainPanel.add(addPanel);

        newSymbolTextBox = new TextBox();
        addPanel.add(newSymbolTextBox);

        addButton = new Button();
        addPanel.add(addButton);
        addButton.setText("Add");

        lastUpdatedLabel = new Label("New Label");
        mainPanel.add(lastUpdatedLabel);
    }
}

Click on the textbox and set it's "focus" property to "true". This will make it so when the page loads in a browser the focus is set to the textbox right away.

Lets go into the source code now by clicking on the "Source" tab. Find the code that looks like this:

stocksFlexTable = new FlexTable();
mainPanel.add(stocksFlexTable);

We are going to add to this code to setup the FlexTable in order to display the stock information. Change the code to this:

stocksFlexTable = new FlexTable();
stocksFlexTable.setText(0, 0, "Symbol");
stocksFlexTable.setText(0, 1, "Price");
stocksFlexTable.setText(0, 2, "Change");
stocksFlexTable.setText(0, 3, "Remove");       
mainPanel.add(stocksFlexTable);

Run your GWT Application

At this point in the GWT tutorial they run the project to have a look at everything so far. We'll do the same. Right-click on the project in Eclipse and select: Run-as -> GWT Application

You should see something similar to this:

Add Event Handlers

Well that's kinda ugly so far, but we'll fix that. Right now we are going to move on to the event listeners. We are going to add a listener for when the button is clicked as well as when a keydown event is triggered in the textbox.

Adding event handlers with GWT Designer is very simple. To add an onClick event go switch to design mode and then double click on the "Add" button. It will automatically setup the event in the code and drop us into that spot. Now lets add the key event for the textbox. Go back to the designer and click on the textbox beside the "Add" button. Go over to the properties and click on the "Events" tab. Drill down into the Keyboard -> onKeyDown section and double click there. It will add the event listener for us and again drop us into the code.

According to the GWT Tutorial we need to call the "AddStock()" function everytime the button is clicked or the onKeydown is triggered in the textbox. So inside the code we need to add the call to "AddStock()".

Notice the red squiggle below the "addStock" call. Hover your mouse over that squiggle and choose: Create Method 'addStock()' in type 'StockWatcher'

Lets modify the onKeyDown a little bit more to catch just the "Enter" key when it's pressed down:


newSymbolTextBox.addKeyboardListener(new KeyboardListenerAdapter() {
    public void onKeyDown(final Widget sender, final char keyCode, final int modifiers)
    {
        if (keyCode == KEY_ENTER)
            addStock();
        }
});

Add some Logic

The GWT Tutorial is now moving us onto implementing some client-side functionality. Basically we want to validate what a user types into the textbox to make sure its as valid as possible. For this we need to add some code to the "addStock()" function. Switch to the source tab and hand-code the following:


private void addStock()
{
  final String symbol = newSymbolTextBox.getText().toUpperCase().trim();
  newSymbolTextBox.setFocus(true);
 
  // symbol must be between 1 and 10 chars that are numbers, letters, or dots
  if (!symbol.matches("^[0-9a-zA-Z\\.]{1,10}$"))
  {
    Window.alert("'" + symbol + "' is not a valid symbol.");
    newSymbolTextBox.selectAll();
    return;
  }
 
  newSymbolTextBox.setText("");
     
  // now we need to add the stock to the list...

}

Go ahead and run the project again as we did before. Type in some invalid characters into the textbox and try the "Add" button. Now try the "Enter" key in the textbox.

Its time to actually be able to add a stock to the list. First, we are going to need to pull in some help from the standard Java ArrayList. Also we to need to add an import at the top of the source code as well as a variable for the new ArrayList.


import com.google.gwt.core.client.EntryPoint;
import com.google.gwt.user.client.Window;
import com.google.gwt.user.client.ui.Button;
import com.google.gwt.user.client.ui.ClickListener;
import com.google.gwt.user.client.ui.FlexTable;
import com.google.gwt.user.client.ui.HorizontalPanel;
import com.google.gwt.user.client.ui.KeyboardListenerAdapter;
import com.google.gwt.user.client.ui.Label;
import com.google.gwt.user.client.ui.RootPanel;
import com.google.gwt.user.client.ui.TextBox;
import com.google.gwt.user.client.ui.VerticalPanel;
import com.google.gwt.user.client.ui.Widget;

import java.util.ArrayList;

/**
 * Entry point classes define <code>onModuleLoad()</code>.
 */

public class StockWatcher implements EntryPoint
{

    private HorizontalPanel addPanel;
    private FlexTable stocksFlexTable;
    private VerticalPanel mainPanel;
    private Label lastUpdatedLabel;
    private Button addButton;
    private TextBox newSymbolTextBox;
   
    private ArrayList<String> stocks = new ArrayList<String>();

... shortened code listing ...

In the "addStock" function we need to add some code following this comment:


// now we need to add the stock to the list...

Here is the "addStock" function in full:


protected void addStock()
    {
        final String symbol = newSymbolTextBox.getText().toUpperCase().trim();
        newSymbolTextBox.setFocus(true);

        // symbol must be between 1 and 10 chars that are numbers, letters, or
        // dots
        if (!symbol.matches("^[0-9a-zA-Z\\.]{1,10}$"))
        {
            Window.alert("'" + symbol + "' is not a valid symbol.");
            newSymbolTextBox.selectAll();
            return;
        }

        newSymbolTextBox.setText("");

        // don't add the stock if it's already in the watch list
        if (stocks.contains(symbol))
            return;

        // add the stock to the list
        int row = stocksFlexTable.getRowCount();
        stocks.add(symbol);
        stocksFlexTable.setText(row, 0, symbol);

        // add button to remove this stock from the list
        Button removeStock = new Button("x");
        removeStock.addClickListener(new ClickListener() {
            public void onClick(Widget sender)
            {
                int removedIndex = stocks.indexOf(symbol);
                stocks.remove(removedIndex);
                stocksFlexTable.removeRow(removedIndex + 1);
            }
        });
        stocksFlexTable.setWidget(row, 3, removeStock);
    }

Run the project again (or refresh the browser if it's still running). Play around adding and removing some stock symbols.

If we want to simulate stock prices changing throughout the day for our demo we will need to add a timer class to refresh the prices. In this case we will be making up our own random prices so obviously you won't want to be using this for real stock trading!

Lets call in the timer class from GWT:


import com.google.gwt.user.client.Timer;

Now we can add this code to the bottom of the "onModuleLoad":


// setup timer to refresh list automatically
  Timer refreshTimer = new Timer() {
    public void run() {
      refreshWatchList();       
    }
  };
  refreshTimer.scheduleRepeating(REFRESH_INTERVAL);

Add this code to the top of the StockWatcher class to define the REFRESH_INTERVAL:


private static final int REFRESH_INTERVAL = 5000;

You can also add the "refreshWatchList()" the same way we added the "addStock()" function using the hover over tooltip and selecting the bottom option of the two that will be available.

Your code so far should look like this:


package com.google.gwt.sample.stockwatcher.client.StockWatcher.client;

import com.google.gwt.core.client.EntryPoint;
import com.google.gwt.user.client.Window;
import com.google.gwt.user.client.ui.Button;
import com.google.gwt.user.client.ui.ClickListener;
import com.google.gwt.user.client.ui.FlexTable;
import com.google.gwt.user.client.ui.HorizontalPanel;
import com.google.gwt.user.client.ui.KeyboardListenerAdapter;
import com.google.gwt.user.client.ui.Label;
import com.google.gwt.user.client.ui.RootPanel;
import com.google.gwt.user.client.ui.TextBox;
import com.google.gwt.user.client.ui.VerticalPanel;
import com.google.gwt.user.client.ui.Widget;
import com.google.gwt.user.client.Timer;

import java.util.ArrayList;

/**
 * Entry point classes define <code>onModuleLoad()</code>.
 */

public class StockWatcher implements EntryPoint
{

    private HorizontalPanel addPanel;
    private FlexTable stocksFlexTable;
    private VerticalPanel mainPanel;
    private Label lastUpdatedLabel;
    private Button addButton;
    private TextBox newSymbolTextBox;

    private ArrayList<String> stocks = new ArrayList<String>();
    private static final int REFRESH_INTERVAL = 5000;

    public void onModuleLoad()
    {
        RootPanel rootPanel = RootPanel.get();

        mainPanel = new VerticalPanel();
        rootPanel.add(mainPanel, 0, 0);
        mainPanel.setSize("262px", "338px");

        stocksFlexTable = new FlexTable();
        stocksFlexTable.setText(0, 0, "Symbol");
        stocksFlexTable.setText(0, 1, "Price");
        stocksFlexTable.setText(0, 2, "Change");
        stocksFlexTable.setText(0, 3, "Remove");
        mainPanel.add(stocksFlexTable);

        addPanel = new HorizontalPanel();
        mainPanel.add(addPanel);

        newSymbolTextBox = new TextBox();
        addPanel.add(newSymbolTextBox);
        newSymbolTextBox.addKeyboardListener(new KeyboardListenerAdapter() {
            public void onKeyDown(final Widget sender, final char keyCode,
                    final int modifiers)
            {
                if (keyCode == KEY_ENTER)
                    addStock();
            }
        });
        newSymbolTextBox.setFocus(true);

        addButton = new Button();
        addPanel.add(addButton);
        addButton.addClickListener(new ClickListener() {
            public void onClick(final Widget sender)
            {
                addStock();
            }
        });
        addButton.setText("Add");

        lastUpdatedLabel = new Label("New Label");
        mainPanel.add(lastUpdatedLabel);
       
        // setup timer to refresh list automatically
        Timer refreshTimer = new Timer() {
          public void run() {
            refreshWatchList();       
          }
        };
        refreshTimer.scheduleRepeating(REFRESH_INTERVAL);  
    }

    protected void refreshWatchList()
    {
        // TODO Auto-generated method stub
       
    }

    protected void addStock()
    {
        final String symbol = newSymbolTextBox.getText().toUpperCase().trim();
        newSymbolTextBox.setFocus(true);

        // symbol must be between 1 and 10 chars that are numbers, letters, or
        // dots
        if (!symbol.matches("^[0-9a-zA-Z\\.]{1,10}$"))
        {
            Window.alert("'" + symbol + "' is not a valid symbol.");
            newSymbolTextBox.selectAll();
            return;
        }

        newSymbolTextBox.setText("");

        // don't add the stock if it's already in the watch list
        if (stocks.contains(symbol))
            return;

        // add the stock to the list
        int row = stocksFlexTable.getRowCount();
        stocks.add(symbol);
        stocksFlexTable.setText(row, 0, symbol);

        // add button to remove this stock from the list
        Button removeStock = new Button("x");
        removeStock.addClickListener(new ClickListener() {
            public void onClick(Widget sender)
            {
                int removedIndex = stocks.indexOf(symbol);
                stocks.remove(removedIndex);
                stocksFlexTable.removeRow(removedIndex + 1);
            }
        });
        stocksFlexTable.setWidget(row, 3, removeStock);
    }
}

We'll now add a "StockPrice" class. Right-click on the com.google.gwt.sample.stockwatcher.client package and choose: File -> New -> Class. Give the class the name: StockPrice. The rest of the defaults should be fine. Press the "Finish" button.

Here is the StockPrice class:


package com.google.gwt.sample.stockwatcher.client.StockWatcher.client;

public class StockPrice
{

    private String symbol;
    private double price;
    private double change;

    public StockPrice()
    {
    }

    public StockPrice(String symbol, double price, double change)
    {
        this.symbol = symbol;
        this.price = price;
        this.change = change;
    }

    public String getSymbol()
    {
        return this.symbol;
    }

    public double getPrice()
    {
        return this.price;
    }

    public double getChange()
    {
        return this.change;
    }

    public double getChangePercent()
    {
        return 100.0 * this.change / this.price;
    }

    public void setSymbol(String symbol)
    {
        this.symbol = symbol;
    }

    public void setPrice(double price)
    {
        this.price = price;
    }

    public void setChange(double change)
    {
        this.change = change;
    }
}

Go back to the StockWatcher source code and add the following import:


import com.google.gwt.user.client.Random;
import com.google.gwt.i18n.client.DateTimeFormat;
import com.google.gwt.i18n.client.NumberFormat;
import java.util.Date;

What we want to do is populate an array of StockPrice objects with some values and then pass them onto a function that will update the FlexTable. We can now finish off the "refreshWatchList" function:


protected void refreshWatchList()
    {
        final double MAX_PRICE = 100.0; // $100.00
        final double MAX_PRICE_CHANGE = 0.02; // +/- 2%

        StockPrice[] prices = new StockPrice[stocks.size()];
        for (int i = 0; i <stocks.size(); i++)
        {
            double price = Random.nextDouble() * MAX_PRICE;
            double change = price * MAX_PRICE_CHANGE
                    * (Random.nextDouble() * 2.0 - 1.0);

            prices[i] = new StockPrice((String) stocks.get(i), price, change);
        }

        updateTable(prices);
    }

Go ahead and hover over the red squiggle under the "updateTable" and add the function just as we have twice before.


private void updateTable(StockPrice[] prices)
    {
        for (int i = 0; i <prices.length; i++)
        {
            updateTable(prices[i]);
        }

        // change the last update timestamp
        lastUpdatedLabel.setText("Last update : "
                + DateTimeFormat.getMediumDateTimeFormat().format(new Date()));
    }

We need to also have this method capable of taking single StockPrice objects, see the GWT Tutorial if you want more details how this all works.

Here is our code so far:


package com.google.gwt.sample.stockwatcher.client.StockWatcher.client;

import com.google.gwt.core.client.EntryPoint;
import com.google.gwt.user.client.Window;
import com.google.gwt.user.client.ui.Button;
import com.google.gwt.user.client.ui.ClickListener;
import com.google.gwt.user.client.ui.FlexTable;
import com.google.gwt.user.client.ui.HorizontalPanel;
import com.google.gwt.user.client.ui.KeyboardListenerAdapter;
import com.google.gwt.user.client.ui.Label;
import com.google.gwt.user.client.ui.RootPanel;
import com.google.gwt.user.client.ui.TextBox;
import com.google.gwt.user.client.ui.VerticalPanel;
import com.google.gwt.user.client.ui.Widget;
import com.google.gwt.user.client.Timer;
import com.google.gwt.user.client.Random;
import com.google.gwt.i18n.client.DateTimeFormat;
import com.google.gwt.i18n.client.NumberFormat;

import java.util.Date;
import java.util.ArrayList;

/**
 * Entry point classes define <code>onModuleLoad()</code>.
 */

public class StockWatcher implements EntryPoint
{

    private HorizontalPanel addPanel;
    private FlexTable stocksFlexTable;
    private VerticalPanel mainPanel;
    private Label lastUpdatedLabel;
    private Button addButton;
    private TextBox newSymbolTextBox;

    private ArrayList<String> stocks = new ArrayList<String>();
    private static final int REFRESH_INTERVAL = 5000;

    public void onModuleLoad()
    {
        RootPanel rootPanel = RootPanel.get();

        mainPanel = new VerticalPanel();
        rootPanel.add(mainPanel, 4, 4);
        mainPanel.setSize("348px", "338px");

        stocksFlexTable = new FlexTable();
        stocksFlexTable.setText(0, 0, "Symbol");
        stocksFlexTable.setText(0, 1, "Price");
        stocksFlexTable.setText(0, 2, "Change");
        stocksFlexTable.setText(0, 3, "Remove");
        mainPanel.add(stocksFlexTable);
        stocksFlexTable.setWidth("100%");

        addPanel = new HorizontalPanel();
        mainPanel.add(addPanel);

        newSymbolTextBox = new TextBox();
        addPanel.add(newSymbolTextBox);
        newSymbolTextBox.addKeyboardListener(new KeyboardListenerAdapter() {
            public void onKeyDown(final Widget sender, final char keyCode,
                    final int modifiers)
            {
                if (keyCode == KEY_ENTER)
                    addStock();
            }
        });
        newSymbolTextBox.setFocus(true);

        addButton = new Button();
        addPanel.add(addButton);
        addButton.addClickListener(new ClickListener() {
            public void onClick(final Widget sender)
            {
                addStock();
            }
        });
        addButton.setText("Add");

        lastUpdatedLabel = new Label("New Label");
        mainPanel.add(lastUpdatedLabel);
        lastUpdatedLabel.setWidth("100%");

        // setup timer to refresh list automatically
        Timer refreshTimer = new Timer() {
            public void run()
            {
                refreshWatchList();
            }
        };
        refreshTimer.scheduleRepeating(REFRESH_INTERVAL);
    }

    protected void refreshWatchList()
    {
        final double MAX_PRICE = 100.0; // $100.00
        final double MAX_PRICE_CHANGE = 0.02; // +/- 2%

        StockPrice[] prices = new StockPrice[stocks.size()];
        for (int i = 0; i <stocks.size(); i++)
        {
            double price = Random.nextDouble() * MAX_PRICE;
            double change = price * MAX_PRICE_CHANGE
                    * (Random.nextDouble() * 2.0 - 1.0);

            prices[i] = new StockPrice((String) stocks.get(i), price, change);
        }

        updateTable(prices);
    }

    private void updateTable(StockPrice[] prices)
    {
        for (int i = 0; i <prices.length; i++)
        {
            updateTable(prices[i]);
        }

        // change the last update timestamp
        lastUpdatedLabel.setText("Last update : "
                + DateTimeFormat.getMediumDateTimeFormat().format(new Date()));
    }

    private void updateTable(StockPrice price)
    {
        // make sure the stock is still in our watch list
        if (!stocks.contains(price.getSymbol()))
        {
            return;
        }

        int row = stocks.indexOf(price.getSymbol()) + 1;

        // apply nice formatting to price and change
        String priceText = NumberFormat.getFormat("#,##0.00").format(
                price.getPrice());
        NumberFormat changeFormat = NumberFormat
                .getFormat("+#,##0.00;-#,##0.00");
        String changeText = changeFormat.format(price.getChange());
        String changePercentText = changeFormat
                .format(price.getChangePercent());

        // update the watch list with the new values
        stocksFlexTable.setText(row, 1, priceText);
        stocksFlexTable.setText(row, 2, changeText + " (" + changePercentText
                + "%)");
    }

    protected void addStock()
    {
        final String symbol = newSymbolTextBox.getText().toUpperCase().trim();
        newSymbolTextBox.setFocus(true);

        // symbol must be between 1 and 10 chars that are numbers, letters, or
        // dots
        if (!symbol.matches("^[0-9a-zA-Z\\.]{1,10}$"))
        {
            Window.alert("'" + symbol + "' is not a valid symbol.");
            newSymbolTextBox.selectAll();
            return;
        }

        newSymbolTextBox.setText("");

        // don't add the stock if it's already in the watch list
        if (stocks.contains(symbol))
            return;

        // add the stock to the list
        int row = stocksFlexTable.getRowCount();
        stocks.add(symbol);
        stocksFlexTable.setText(row, 0, symbol);

        // add button to remove this stock from the list
        Button removeStock = new Button("x");
        removeStock.addClickListener(new ClickListener() {
            public void onClick(Widget sender)
            {
                int removedIndex = stocks.indexOf(symbol);
                stocks.remove(removedIndex);
                stocksFlexTable.removeRow(removedIndex + 1);
            }
        });

        stocksFlexTable.setWidget(row, 3, removeStock);
    }
}

Lets run this again and test it out.

We are getting to the end, therefor its time to make this a little more pleasing to the eye. Fortunately GWT Designer makes this downright simple from a developers perspective.

One of the first things the GWT Tutorial does is add a logo to the StockWatcher application. In particular they use this one (right-click and save it if you need a copy of it):

Right-click on the src/com.google.gwt.sample.stockwatcher.client.StockWatcher/public/ package in Eclipse and choose "import" from the menu. Then in the "Import" window choose: General -> File System

Browse to where the logo is and import it.

Go into the GWT Designer and click on the Image widget and move it above the FlexTable. Once that is in place go to the Image's properties and set it's URL to the logo. Clicking the button with the "..." will make it very easy to locate the image file.

While we're adding new widgets, lets also add a Label widget below the image and set it's text property to "Stock Watcher". The GWT Tutorial actually adds this in the beginning, I purposely put it off until now since I wanted to style it right away. Your UI should now look like this (click on the image to make it larger):

Add CSS Style

Lets style the "Stock Watcher" label now. GWT Designer's CSS support is straightforward. Go to the label's properties and select the "styleName" property and click on the "..." button that appears. The CSS Style Selection window will open now with a style name of ".gwt-Label".

Change the ".gwt-Label" name to ".gwt-Label-StockWatcher" and press the "Add" button. Another window will open which you can simply press "Ok". Now that we have done that you can press the "Edit" button. Set the size to "18" and the weight to "bold" and then press "Ok" and then press "Ok" again to get back to the GWT Designer.

Lets style the "Add" button. Go to the GWT Designer if your not there already and click on the "Add" button. Go to it's properties and click on the "..." in the "styleName" to open up the CSS Style Selection window. Change the style name to ".gwt-Button-Add" and then press "Add", "Ok" and then "Edit". Now modify the button as you wish. Here is what I did:


.gwt-Button-Add {
    font-size: 14px;
    background-color: #3399ff;
}

You can see how simple this is to quickly style your application and keep in mind we are barely scratching the surface of what is possible, again, see the GWT Tutorial on styling. I won't walk through all the controls and styling them, but keep in mind you can style dynamically too. So for instance in the GWT Tutorial they style it so that when a stock's price goes up it is a green font, when it goes down it is in a red font.


stockWatcherLabel.setStyleName("gwt-Label-StockWatcher");

...

stocksFlexTable.addStyleName("watchList");
stocksFlexTable.getRowFormatter().addStyleName(0, "watchListHeader");

...

Label changeWidget = (Label) stocksFlexTable.getWidget(row, 2);
changeWidget.setText(changeText + " (" + changePercentText + "%)");

...

String changeStyleName = "noChange";
if (price.getChangePercent() <-0.1f)
{
    changeStyleName = "negativeChange";
}
else if (price.getChangePercent()> 0.1f)
{
    changeStyleName = "positiveChange";
}
 
changeWidget.setStyleName(changeStyleName);
...

Playing around a bit I ended up with something that looks like this:

That concludes this tutorial. You can download the source file from here

Next, if you're interested in using RPC to populate the Stock Watcher, see the tutorial on Remote Services.

You can post your comments, questions, corrections in the GWT Designer forum.

Related Topics: