客户端编码

此时,您已经使用 GWT 小部件和面板构建了用户界面,并连接了事件处理程序。StockWatcher 接受输入,但它还没有将股票添加到股票表格或更新任何股票数据。

在本节中,您将完成所有 StockWatcher 的客户端功能的实现。具体来说,您将编写以下代码:

  1. 在股票表格中添加和删除股票。
  2. 刷新表格中每只股票的价格和变化字段。
  3. 实现显示最后更新时间的戳。

您对 StockWatcher 的初始实现非常简单,您可以将所有功能都编码在客户端。稍后您将添加对服务器的调用以检索股票数据。

在股票表格中添加和删除股票

您的首要任务是将股票代码和一个“删除”按钮添加到股票表格。请记住,FlexTable 会自动调整大小以容纳数据,因此您不必担心编写代码来处理此问题。

  1. 创建数据结构。
  2. 在股票表格中添加行。
  3. 添加一个按钮以从股票表格中删除股票。
  4. 在开发模式下测试。

A. 创建数据结构

您需要一个数据结构来保存用户输入的股票代码列表。使用标准 Java ArrayList 并将列表命名为 stocks。

  1. 创建数据结构。

    • 在 StockWatcher.java 中的 StockWatcher 类中,创建一个新的 Java ArrayList 实例。
        public class StockWatcher implements EntryPoint {
    
          private VerticalPanel mainPanel = new VerticalPanel();
          private FlexTable stocksFlexTable = new FlexTable();
          private HorizontalPanel addPanel = new HorizontalPanel();
          private TextBox newSymbolTextBox = new TextBox();
          private Button addStockButton = new Button("Add");
          private Label lastUpdatedLabel = new Label();
          private ArrayList<String> stocks = new ArrayList<String>();
    
  2. Eclipse 会标记 ArrayList 并建议您包含导入声明。

  3. 包含导入声明。

    import java.util.ArrayList;
    

B. 在 flex 表格中添加行

在用户输入股票代码后,首先检查它是否不是重复的。如果股票代码不存在,则在 FlexTable 中添加一行,并在第一列(列 0)中的单元格中填充用户输入的股票代码。要将文本添加到 FlexTable 的单元格中,请调用 setText 方法。

  1. 检查股票是否存在,如果存在,则不要再次添加。

    • 在 addStock 方法中,用以下代码替换 TODO 注释。
        // Don't add the stock if it's already in the table.
        if (stocks.contains(symbol))
          return;
    
  2. 如果股票不存在,则添加它。

    • 在 addStock 方法中,用以下代码替换 TODO 注释。
        // Add the stock to the table.
        int row = stocksFlexTable.getRowCount();
        stocks.add(symbol);
        stocksFlexTable.setText(row, 0, symbol);
    
    • 调用 setText 方法时,FlexTable 会根据需要自动创建新单元格;因此,您无需显式调整表格大小。

C. 添加一个按钮以从股票列表中删除股票

为了让用户可以从列表中删除特定股票,请在表格行的最后一个单元格中插入一个“删除”按钮。要将小部件添加到 FlexTable 的单元格中,请调用 setWidget 方法。使用 addClickHandler 方法订阅点击事件。如果“删除股票”按钮发布点击事件,则从 FlexTable 和 ArrayList 中删除股票。

  1. 添加用于从列表中删除股票的按钮。

    • 在 addStock 方法中,用以下代码替换 TODO 注释。
        // Add a button to remove this stock from the table.
        Button removeStockButton = new Button("x");
        removeStockButton.addClickHandler(new ClickHandler() {
          public void onClick(ClickEvent event) {
            int removedIndex = stocks.indexOf(symbol);
            stocks.remove(removedIndex);
            stocksFlexTable.removeRow(removedIndex + 1);
          }
        });
        stocksFlexTable.setWidget(row, 3, removeStockButton);
    

D. 测试添加/删除股票功能

您还有另一个 TODO 需要编码:获取股票价格。但首先在开发模式下快速检查一下添加股票和删除股票功能是否按预期工作。

此时,当您输入股票代码时,StockWatcher 应该将其添加到股票表格。试一试看看。

  1. 在开发模式下运行 StockWatcher。
    • 在已打开的浏览器中按“刷新”。
  2. 添加股票。
    • 在输入框中输入股票代码。
    • StockWatcher 应该将股票添加到表格中。表格会调整大小以容纳新数据。但是,价格和变化字段仍然为空。如果您输入的小写股票代码,它会将字母转换为大写。
  3. 验证您是否无法将重复的股票添加到表格中。
    • 添加一个表格中已存在的股票代码。
    • StockWatcher 应该清除输入框,但不再添加相同的股票代码。
  4. 删除股票。
    • 点击“删除”按钮。
    • 该股票将从表格中删除,表格会调整大小。

StockWatcher, Add/Remove Functionality

现在您将解决最后一个 TODO:获取股票价格。

刷新价格和变化字段

StockWatcher 的最重要的功能是更新用户正在关注的股票的价格。如果您使用传统的 Web 开发技术编写 StockWatcher,则每次想要更新价格时,都必须依赖于完整的页面重新加载。您可以手动完成此操作(让用户点击浏览器的“刷新”按钮),也可以自动完成此操作(例如,在 HTML 标头中使用<meta http-equiv="refresh" content="5"> 标签)。但在如今的 Web 2.0 时代,这效率实在不够高。StockWatcher 的用户想要他们的股票价格更新,而且他们现在就想要…无需等待完整的页面重新加载。

在本节中,您将

  1. 通过实现计时器并指定刷新率来自动刷新价格和变化字段。
  2. 通过创建 StockPrice 类来封装股票价格数据。
  3. 通过实现 refreshWatchList 方法生成价格和变化字段的股票数据。
  4. 通过实现 updateTable 方法,使用股票数据加载价格和变化字段。
  5. 测试股票价格和变化值的随机生成。

A. 自动刷新股票数据

GWT 使您能够轻松地动态更新应用程序的内容。对于 StockWatcher,您将使用 GWT Timer 类来自动更新股票价格。

Timer 是一个单线程的、浏览器安全的计时器类。它使您能够安排代码在未来的某个时间点运行,可以使用 schedule() 方法运行一次,也可以使用 scheduleRepeating() 方法重复运行。因为您希望 StockWatcher 每五秒钟自动更新股票价格,所以您将使用 scheduleRepeating()。

当 Timer 到期时,run 方法将执行。对于 StockWatcher,您将使用对 refreshWatchList 方法的调用来覆盖 run 方法,该方法会刷新价格和变化字段。现在,只需在 refreshWatchList 方法中放入一个存根;在本节的后面,您将实现它。

  1. 实现计时器。
  • 修改 onModuleLoad 方法以创建一个新的 Timer 实例,如下所示

        public void onModuleLoad() {
    
          ...
    
          // Move cursor focus to the input box.
          newSymbolTextBox.setFocus(true);
    
          // Setup timer to refresh list automatically.
          Timer refreshTimer = new Timer() {
            @Override
            public void run() {
              refreshWatchList();
            }
          };
          refreshTimer.scheduleRepeating(REFRESH_INTERVAL);
    
          ...
    
        }
    
    • Eclipse 会标记 Timer、REFRESH_INTERVAL 和 refreshWatchList。
  1. 声明 Timer 的导入。

    • 如果您使用的是 Eclipse 快捷键,请确保选择 GWT Timer。
        import com.google.gwt.user.client.Timer;
    
  2. 指定刷新率。

    • 如果您使用的是 Eclipse 快捷键,请选择“创建常量 'REFRESH_INTERVAL'”,然后以毫秒为单位指定刷新间隔,即 5000
    • 否则,只需从下面突出显示的代码中剪切和粘贴。
        public class StockWatcher implements EntryPoint {
    
          private static final int REFRESH_INTERVAL = 5000; // ms
          private VerticalPanel mainPanel = new VerticalPanel();
    
  3. 在添加新股票后立即填充价格和变化值。

    • 在 addStock 方法中,用以下突出显示的代码替换 TODO 注释。
        private void addStock() {
    
          ...
    
          // Add a button to remove a stock from the table.
          Button removeStockButton = new Button("x");
          removeStockButton.addClickHandler(new ClickHandler() {
            public void onClick(ClickEvent event) {
              int removedIndex = stocks.indexOf(symbol);
              stocks.remove(removedIndex);
              stocksFlexTable.removeRow(removedIndex + 1);
            }
          });
          stocksFlexTable.setWidget(row, 3, removeStockButton);
    
          // Get the stock price.
          refreshWatchList();
    
        }
    
    • Eclipse 会标记 refreshWatchList。
  4. 在 StockWatcher 类中,为 refreshWatchList 方法创建一个存根。

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

B. 封装股票价格数据

screenshot: Eclipse New Class Window

使用 Eclipse 创建 Java 类

GWT 加速 AJAX 开发的主要方式之一是允许您使用 Java 语言编写应用程序。因此,您可以利用静态类型检查和久经考验的面向对象编程模式。这些模式与现代 IDE 功能(如代码补全和自动重构)相结合,使得编写具有良好组织代码库的健壮 AJAX 应用程序比以往任何时候都更容易。

对于 StockWatcher,您将利用此功能,将股票价格数据分解为它自己的类。

  1. 创建一个名为 StockPrice 的新 Java 类。
    • 在 Eclipse 中的“包资源管理器”窗格中,选择包 com.google.gwt.sample.stockwatcher.client
    • 从 Eclipse 菜单栏中,选择“文件 > 新建 > 类”。
    • Eclipse 会打开一个“新建 Java 类”窗口。
  2. 填写“新建类”窗口。
    • 在“名称”处输入 StockPrice
    • 接受其他字段的默认值。
    • 按“完成”。
  3. Eclipse 会创建 StockPrice 类的存根代码。

    package com.google.gwt.sample.stockwatcher.client;
    
    public class StockPrice {
    
    }
    
  4. 用以下代码替换存根。

    package com.google.gwt.sample.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 10.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;
      }
    }
    

C. 生成股票数据

现在您已经有了 StockPrice 类来封装股票价格数据,您可以生成实际数据。为此,您将实现 refreshWatchList 方法。请记住,refreshWatchList 方法在用户将股票添加到股票表时以及每 5 秒定时器触发时都会被调用。

随机生成数据

为了替代从在线数据源检索实时股票价格,您将创建伪随机价格和变化值。为此,请使用 GWT Random 类。然后使用这些值填充 StockPrice 对象数组,并将它们传递给 updateTable 方法。

  1. 生成随机股票价格。

    • 在 StockWatcher 类中,用以下代码替换占位符 refreshWatchList 方法。
        /**
         * Generate random stock prices.
         */
        private 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(stocks.get(i), price, change);
         }
    
         updateTable(prices);
        }
    
    • Eclipse 会标记 Random 和 updateTable。
  2. 包含导入声明。

    import com.google.gwt.user.client.Random;
    
  3. 为 updateTable(StockPrice[]) 方法创建占位符。

    private void updateTable(StockPrice[] prices) {
      // TODO Auto-generated method stub
    }
    

D. 填充 Price 和 Change 字段

最后,将随机生成的 price 和 change 数据加载到 StockWatcher 表中。对于每只股票,格式化 Price 和 Change 列,然后加载数据。为此,您将在 StockWatcher 类中实现两个方法。

  • 将 Price 字段中的值格式化为两位小数 (1,956.00)。
  • 在 Change 字段中的值前面加上符号 (+/-)。
  1. 实现方法 updateTable(StockPrices[])。

    • 用以下代码替换占位符。
        /**
         * Update the Price and Change fields all the rows in the stock table.
         *
         * @param prices
         *          Stock data for all rows.
         */
        private void updateTable(StockPrice[] prices) {
          for (int i = 0; i < prices.length; i++) {
            updateTable(prices[i]);
          }
        }
    
    • Eclipse 会标记 updateTable。
    • 为 updateTable(StockPrice) 方法创建占位符。
  2. 实现方法 updateTable(StockPrice)。

    • 用以下代码替换占位符。
        /**
         * Update a single row in the stock table.
         *
         * @param price Stock data for a single row.
         */
        private void updateTable(StockPrice price) {
         // Make sure the stock is still in the stock table.
         if (!stocks.contains(price.getSymbol())) {
           return;
         }
    
         int row = stocks.indexOf(price.getSymbol()) + 1;
    
         // Format the data in the Price and Change fields.
         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());
    
         // Populate the Price and Change fields with new data.
         stocksFlexTable.setText(row, 1, priceText);
         stocksFlexTable.setText(row, 2, changeText + " (" + changePercentText
             + "%)");
        }
    
    • Eclipse 会标记 NumberFormat。
  3. 包含导入声明。

    import com.google.gwt.i18n.client.NumberFormat;
    

E. 测试随机生成的股票价格和变化值

此时,Price 和 Change 字段应该填充了您随机生成的股票数据。试一试看看。

  1. 在开发模式下运行 StockWatcher。
  2. 添加股票。
    • Price 和 Change 字段应该包含数据。
    • 每 5 秒,数据都会刷新。

添加时间戳

您需要实现的最后一个功能是时间戳。您使用了一个 Label 小部件 lastUpdatedLabel 在 UI 中创建了时间戳。现在设置 Label 小部件的文本。将此代码添加到 updateTable(StockPrice[]) 方法中。

  1. 实现时间戳。

    • 在 updateTable(StockPrice[]) 方法中,复制并粘贴突出显示的代码。
        /**
         * Update the Price and Change fields all the rows in the stock table.
         *
         * @param prices Stock data for all rows.
         */
        private void updateTable(StockPrice[] prices) {
          for (int i = 0; i < prices.length; i++) {
            updateTable(prices[i]);
          }
    
          // Display timestamp showing last refresh.
          DateTimeFormat dateFormat = DateTimeFormat.getFormat(
            DateTimeFormat.PredefinedFormat.DATE_TIME_MEDIUM);
          lastUpdatedLabel.setText("Last update : " 
            + dateFormat.format(new Date()));
        }
    
    • Eclipse 会标记 DateTimeFormat 和 Date。
  2. 包含导入。

    import com.google.gwt.i18n.client.DateTimeFormat;
    import java.util.Date;
    
  3. 测试时间戳。

    • 保存您的更改。在浏览器中,按刷新以加载更改。
    • 时间戳标签应该显示在股票表格下方。当 Price 和 Change 字段刷新时,时间戳应该显示上次更新的日期和时间。

实现说明:您可能已经注意到 DateTimeFormat 和 NumberFormat 类位于 com.google.gwt.i18n 的子包中,这表明它们以某种方式处理国际化。事实上它们确实如此:这两个类在格式化数字和日期时会自动使用您应用程序的区域设置。您将在本教程中详细了解如何将您的 GWT 应用程序本地化和翻译成其他语言。 国际化 GWT 应用程序.

下一步

此时,您已经构建了接口组件,并编写了 StockWatcher 应用程序所有底层客户端功能的代码。用户可以添加和删除股票。Price 和 Change 字段每 5 秒更新一次。时间戳显示上次刷新发生的时间。

screenshot StockWatcher Bug

尽管您尚未编译 StockWatcher,但您可以在生产模式下在此处进行测试: 运行 StockWatcher

一个错误

为了本教程的需要,我们在代码中引入了错误。你能发现它吗?

看看变化百分比。它们看起来是不是有点小?如果您进行计算,您会发现它们似乎恰好比它们应该的规模小一个数量级。StockWatcher 代码中隐藏着一个算术错误。使用 GWT 和您的 Java IDE 提供的工具,您的下一步是查找并修复错误。

步骤 6:调试 GWT 应用程序