// 
//     Licensed to the Apache Software Foundation (ASF) under one
//     or more contributor license agreements.  See the NOTICE file
//     distributed with this work for additional information
//     regarding copyright ownership.  The ASF licenses this file
//     to you under the Apache License, Version 2.0 (the
//     "License"); you may not use this file except in compliance
//     with the License.  You may obtain a copy of the License at
// 
//       http://www.apache.org/licenses/LICENSE-2.0
// 
//     Unless required by applicable law or agreed to in writing,
//     software distributed under the License is distributed on an
//     "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
//     KIND, either express or implied.  See the License for the
//     specific language governing permissions and limitations
//     under the License.
//

= 通过 Web 服务传递二进制数据，第 5 部分：创建 Swing 客户端
:jbake-type: tutorial
:jbake-tags: tutorials 
:jbake-status: published
:icons: font
:syntax: true
:source-highlighter: pygments
:toc: left
:toc-title:
:description: 通过 Web 服务传递二进制数据，第 5 部分：创建 Swing 客户端 - Apache NetBeans
:keywords: Apache NetBeans, Tutorials, 通过 Web 服务传递二进制数据，第 5 部分：创建 Swing 客户端

本练习的目的在于为您以前创建和部署的 Web 服务创建一个客户端，然后给该客户端添加一个 GUI 界面。该界面显示 Web 服务以二进制数据传递的图像。


*本教程中的课程*

image::images/netbeans-stamp-74-73-72.png[title="此页上的内容适用于 NetBeans IDE 7.2、7.3 和 7.4"]

1. link:./flower_overview.html[+概述+]
2. link:./flower_ws.html[+创建 Web 服务+]
3. link:./flower-code-ws.html[+对 Web 服务进行编码和测试+]
4. link:./flower_wsdl_schema.html[+修改方案文件和 WSDL 文件以传递二进制数据+]
5. => 创建 Swing 客户端

*本课内容*

[start=1]
1. <<create-client-app,创建客户端应用程序>>


[start=2]
. <<design-jframe,设计 JFrame>>

[start=3]
. 
<<bind-jframe,绑定 JFrame 组件>>


[start=4]
. <<code-main-class,对 Main 类进行编码>>


== 创建客户端应用程序

在此部分，将创建一个 Web 应用程序。在此应用程序内，将创建一个客户端，该客户端使用您在以前的教程中创建并修改的 Web 服务。

*创建客户端应用程序：*

1. 选择 "File"（文件）> "New Project"（新建项目）（在 Linux/Windows 上为 Ctrl-Shift-N 组合键，在 MacOS 上为 ⌘-Shift-N 组合键）。此时将打开新建项目向导。
2. 选择 "Java" 类别中的 "Java Application"（Java 应用程序）选项。单击 "Next"（下一步）。此时将显示新建 Java 应用程序向导。在 "Project Name"（项目名称）中键入  ``FlowerClient`` 。选择项目的位置，然后单击 "Finish"（完成）。IDE 创建一个新的 Java 应用程序项目。
3. 右键单击  ``FlowerClient``  项目节点，然后从上下文菜单选择 "New"（新建）> "Web Service Client"（Web 服务客户端）。此时将打开新建 Web 服务客户端向导。
4. 选择 WSDL URL 单选按钮，并将 WSDL 文件的 URL 粘贴到该字段。（默认情况下，该 URL 为  ``http://localhost:8080/FlowerAlbumService/FlowerServiceService?wsdl`` 。在浏览器中查找 URL，方法是测试 Web 服务，然后将 URL 末尾的  ``?Tester``  替换为  ``?wsdl`` 。）接受其他所有默认值，包括空的包名称。 
image::images/ws-client-wiz.png[]

[start=5]
. 单击 "Finish"（完成）。IDE 将下载该 WSDL 文件，添加与 Web 服务交互的客户端桩模块，并在 "Projects"（项目）窗口的 Java 应用程序项目中添加节点。 
image::images/client-generated-sources.png[title="显示新建 Web 服务客户端的项目视图"]


[[design-jframe]]
== 设计 JFrame 窗体

在此部分，向该 Web 应用程序添加一个  ``JFrame`` ，并在其中使用 Swing 组件设计一个 GUI 界面。最后，将该 Swing 组件绑定到该 Web 服务客户端代码。

如果不想亲自设计 JFrame 窗体，可以从link:https://netbeans.org/projects/www/downloads/download/webservices%252FFlowerFrame.java[+此处+]下载预先设计的 JFrame Java 文件。

1. 右键单击  ``FlowerClient``  节点，然后选择 "New"（新建）> "JFrame Form"（JFrame 窗体）。将该窗体命名为  ``FlowerFrame`` 。并且，将其置于  ``flowerclient``  包中。
2.  ``FlowerFrame``  在编辑器中打开。如果 "Palette"（组件面板）尚未打开，请将其打开。扩展框架的底边框大约三分之一。
image::images/opened-flowerform.png[]

[start=3]
. 将 JPanel 从 "Palette"（组件面板）的 "Swing Container"（Swing 容器）部分拖至  ``FlowerFrame`` 。扩展该而板以填充整个  ``FlowerFrame`` 。 
image::images/add-panel.png[]

[start=4]
. 右键单击 "Design"（设计）视图中的 "Panel"（面板）。从上下文菜单中选择 "Change Variable Name... "（更改变量名称...）。将该面板命名为  ``gardenFlowersPanel`` 。

[start=5]
. 将 JLabel 从 "Palette"（组件面板）拖至  ``gardenFlowersPanel``  的顶部。右键单击标签，然后将标签的变量名称更改为  ``titleLabel`` 。再次右键单击  ``titleLabel``  然后选择 "Edit Text"（编辑文本）。将文本更改为 "Garden Flowers"。可能希望浏览  ``titleLabel``  的属性，并为其设置一个醒目的字体。

[start=6]
. 将 "Button Group"（按钮组）拖至设计视图中。接受按钮组的默认变量名称  ``buttonGroup1`` 。

[start=7]
. 将四个单选按钮拖至  ``titleLabel``  下面的水平行中。在每个按钮的属性中，将其设置为  ``buttonGroup1``  的成员。按钮的其他属性如下：

|===
|buttonGroup1 中的单选按钮

|变量名称 |已选定 |文本 

|asterRadioButton |true |紫苑 

|honeysuckleRadioButton |false |金银花 

|roseRadioButton |false |玫瑰 

|sunflowerRadioButton |false |向日葵 
|===

[start=8]
. 将 "Scroll Pane"（滚动窗格）拖至单选按钮的下方。扩展该窗格以填充全部水平空间以及大约三分之二的空余垂直空间。将滚动窗格的变量名称更改为  ``mainScrollPane`` 。

[start=9]
. 将 "Panel"（面板）拖至  ``mainScrollPane``  中。将面板的变量名称更改为  ``mainPanel`` 。

[start=10]
. 在 "Design"（设计）视图中，右键单击  ``mainPanel``  然后选择 "Set Layout"（设置布局）> "Border Layout"（边框式布局）。

[start=11]
. 将 "Button"（按钮）拖至  ``mainPanel``  中。因为  ``mainPanel``  具有边框式布局，所以按钮将自动填充整个面板。将按钮的变量名称更改为  ``mainPictureButton``  并将按钮的文本更改为 "Waiting for picture..."

[start=12]
. 再将一个滚动窗格拖至  ``mainScrollPane``  下面的空间中。扩展新滚动窗格以填满全部剩余空间。将新滚动窗格的变量名称更改为  ``thumbnailScrollPane`` 。

[start=13]
. 将 "Panel"（面板）拖至  ``thumbnailScrollPane``  中。将面板的变量名称更改为  ``thumbnailPanel`` 。将  ``thumbnailPanel``  的布局设置为 "Grid Layout"（网格式布局）。

[start=14]
. 将四个 "Buttons"（按钮）拖至  ``thumbnailPanel``  中。因为  ``thumbnailPanel``  具有网格式布局，所以按钮将自动变为相同大小并且完全填满面板。按钮的属性如下： thumbnailPanel 中的按钮
|===

|变量名称 |文本 

|asterButton |正在等待... 

|honeysuckleButton |正在等待... 

|roseButton |等待 

|sunflowerButton |正在等待... 
|===

现在 JFrame 窗体已经完全设计好了。此时， ``FlowerFrame``  将如下所示。 
image::images/designed-form.png[title="显示按钮文本而不是图像的已完成 "Flower"（鲜花）框架"]


== 绑定 JFrame 组件

在此部分，将在构造函数中初始化组件，然后将这些组件绑定到监听程序。监听程序将调用用于显示鲜花图像的代码。


=== [[初始化组件]] 

[[在此部分，将填充  ``FlowerFrame``  构造函数

]]

1. 切换到编辑器的 "Source"（源）视图。找到  ``FlowerFrame``  类主体的开头以及  ``FlowerFrame``  构造函数。
image::images/ff-empty-constructor.png[]

[start=1]
1. 在  ``FlowerFrame``  类主体的顶部，并且在构造函数之前，创建一个用于表示每种花名称的字符串数组。

[source,java]
----

protected static final String[] FLOWERS = {"aster", "honeysuckle", "rose", "sunflower"};
----
1. 在 FLOWERS 字符串数组和构造函数之间，添加一行代码以初始化名为  ``flowers``  的  ``link:http://download.oracle.com/javase/6/docs/api/java/util/Map.html[+java.util.Map+]`` 。该映射使用一个  ``String``  并将其映射到某个  ``Image`` 。

[source,java]
----

private Map<String, Image> flowers;
----
1. 为  ``java.util.Map``  和  ``java.awt.Image``  添加导入语句。
2. 向  ``FlowerFrame``  构造函数添加代码，以将特定的  ``Image``  与  ``flowers``  映射特定实例的特定  ``String``  相关联

[source,java]
----

public FlowerFrame(Map<String, Image> flowers) {

    this.flowers = flowers;
    for (String flower:FLOWERS) {
        flowers.put(flower,null);
    }

    initComponents();    
} 
----

[start=3]
. 初始化单选按钮的  ``ItemListener``  以及四个花形按钮的  ``ActionListener`` ，然后设置默认标题。

[source,java]
----

public FlowerFrame(Map<String, Image> flowers) {

    this.flowers = flowers;
    for (String flower:FLOWERS) {
        flowers.put(flower,null);
    }

    initComponents(); 
    
    setTitle("Garden Flowers [waiting for picture]");
    
    ItemListener rbListener = new RBListener();
    asterRadioButton.addItemListener(rbListener);
    honeysuckleRadioButton.addItemListener(rbListener);
    roseRadioButton.addItemListener(rbListener);
    sunflowerRadioButton.addItemListener(rbListener);
    
    ActionListener bListener = new ButtonListener();
    asterButton.addActionListener(bListener);
    honeysuckleButton.addActionListener(bListener);
    roseButton.addActionListener(bListener);
    sunflowerButton.addActionListener(bListener);
}
----

[start=4]
. 为  ``link:http://download.oracle.com/javase/6/docs/api/java/awt/event/ItemListener.html[+java.awt.event.ItemListener+]``  和  ``link:http://download.oracle.com/javase/6/docs/api/java/awt/event/ActionListener.html[+java.awt.event.ActionListener+]``  添加导入语句。

现在已完成构造函数。代码中出现了编译错误警告，这是因为代码未包含类  ``RBListener``  和  ``ButtonListener`` 。这两个类分别是  ``ItemListener``  和  ``ActionListener``  的定制实现。将在下一部分中编写这两个类。


=== [[显示鲜花]] 

[[在此部分，将为单选按钮和花形按钮编写定制监听程序。还会编写一个方法，该方法用于确定按钮选择的是哪一种花，并通过  ``flowers``  映射获取此花的  ``Image`` 。最后，编写一个由  ``Main``  类调用的方法，该方法将获取每个缩略图的  ``Image`` 。

]]

1. 在  ``FlowerFrame``  类主体中找到  ``public static void main(String args[])``  方法。删除此方法及其文档。（应用程序将改用  ``Main``  类。）
. 为单选按钮编写定制  ``ItemListener``  以代替  ``main``  方法。当选择某个单选按钮时，该监听程序会显示新的鲜花图像。

[source,java]
----

private class RBListener implements ItemListener {
    public void itemStateChanged(ItemEvent e) {
        showFlower();
    }
}
----

. 为  ``link:http://download.oracle.com/javase/6/docs/api/java/awt/event/ItemEvent.html[+java.awt.event.ItemEvent+]``  添加一条导入语句。
. 在定制  ``ItemListener``  的下面，为 4 个鲜花按钮编写定制  ``ActionListener`` 。当单击某个按钮时，监听程序会选择相关的单选按钮：

[source,java]
----

private class ButtonListener implements ActionListener {

    public void actionPerformed(ActionEvent e) {
        if (e.getSource() == asterButton) asterRadioButton.setSelected(true);
        else if (e.getSource() == honeysuckleButton) honeysuckleRadioButton.setSelected(true);
        else if (e.getSource() == roseButton) roseRadioButton.setSelected(true);
        else if (e.getSource() == sunflowerButton) sunflowerRadioButton.setSelected(true);
    }
}
----

[start=2]
. 为  ``link:http://download.oracle.com/javase/6/docs/api/java/awt/event/ActionEvent.html[+java.awt.event.ActionEvent+]``  添加一条导入语句。

[start=3]
. 在定制  ``ActionListener``  的下面，编写  ``showFlower``  方法。该方法用于确定哪一个单选按钮已选中并从  ``flowers``  映射中获取相应鲜花的  ``Image`` 。

[source,java]
----

void showFlower() {
    Image img = null;
    if (asterRadioButton.isSelected()) {
        img = flowers.get("aster");
        if (img != null) {
            mainPictureButton.setIcon(new ImageIcon(img));
            setTitle("Garden Flowers [Aster]");
        }
    } else if (honeysuckleRadioButton.isSelected()) {
        img = flowers.get("honeysuckle");
        if (img != null) {
            mainPictureButton.setIcon(new ImageIcon(img));
            setTitle("Garden Flowers [Honeysuckle]");
        }

    } else if (roseRadioButton.isSelected()) {
        img = flowers.get("rose");
        if (img != null) {
            mainPictureButton.setIcon(new ImageIcon(img));
            setTitle("Garden Flowers [Rose]");
        }
    } else if (sunflowerRadioButton.isSelected()) {
        img = flowers.get("sunflower");
        if (img != null) {
            mainPictureButton.setIcon(new ImageIcon(img));
            setTitle("Garden Flowers [Sunflower]");
        }
    }
    if (img == null) {
        mainPictureButton.setIcon(null);
        setTitle("Garden Flowers [waiting for picture]");            
    } else mainPictureButton.setText("");
}
----

[start=4]
. 为  ``link:http://download.oracle.com/javase/6/docs/api/javax/swing/ImageIcon.html[+javax.swing.ImageIcon+]``  添加一条导入语句。

[start=5]
. 编写  ``setThumbnails``  方法。此方法从  ``flowers``  映射为每个缩略图获取图像。 ``Main``  类将调用此方法。

[source,java]
----

void setThumbnails(Map<String, Image> thumbs) {
    Image img = thumbs.get("aster");
    if (img != null) {
        asterButton.setIcon(new ImageIcon(img));
        asterButton.setText("");
    }
    img = thumbs.get("honeysuckle");
    if (img != null) {
        honeysuckleButton.setIcon(new ImageIcon(img));
        honeysuckleButton.setText("");
    }
    img = thumbs.get("rose");
    if (img != null) {
        roseButton.setIcon(new ImageIcon(img));
        roseButton.setText("");
    }
    img = thumbs.get("sunflower");
    if (img != null) {
        sunflowerButton.setIcon(new ImageIcon(img));
        sunflowerButton.setText("");
    }
}
----

[start=6]
. 在  ``FlowerFrame``  中修复导入（如果在代码中粘贴导入内容时未对其进行修复）。通过在编辑器中单击鼠标右键，然后从上下文菜单中选择 "Fix Imports"（修复导入），可以一次性修复所有导入内容。下面是完整的导入语句集：

[source,java]
----

import java.awt.Image;import java.awt.event.ActionEvent;import java.awt.event.ActionListener;import java.awt.event.ItemEvent;import java.awt.event.ItemListener;import java.util.Map;import javax.swing.ImageIcon;
----

 ``FlowerFrame``  现在已完成。


== [[对 Main 类进行编码]] 

[[在此部分，将完成  ``Main``  类，以使其显示  ``FlowerFrame`` ，连接到 Web 服务，并调用 Web 服务操作。

1. 在编辑器中打开  ``Main.java``  类。
image::images/main-empty.png[]

[start=2]
. 在类主体中的  ``main``  方法之前，为已下载的图片数初始化一个  ``int``  变量。

[source,java]
----

 private static int downloadedPictures;
----

[start=3]
. 在  ``main``  方法主体中，创建四种花的  ``HashMap``  以及四张缩略图的另一个  ``HashMap`` 。

[source,java]
----

final Map<String,Image> flowers = new HashMap<String,Image>(4);
final Map<String,Image> thumbs = new HashMap<String,Image>(4);
----

[start=4]
. 为  ``java.awt.Image`` 、 ``java.util.Map``  和  ``java.util.HashMap``  添加导入语句。

[start=5]
. 在  ``main``  方法主体中，添加代码以显示  ``FlowerFrame`` 。*// Show the FlowerFrame.*

[source,java]
----

final FlowerFrame frame = new FlowerFrame(flowers);
frame.setVisible(true);  
----

[start=6]
. 在  ``main``  方法主体中，添加代码以将客户端连接到服务。*// The client connects to the service with this code.*

[source,java]
----

FlowerServiceService service = new FlowerServiceService();
final FlowerService port = service.getFlowerServicePort();
----

[start=7]
. 为  ``org.flower.service.FlowerService``  和  ``org.flower.service.FlowerServiceService``  添加导入语句。

[start=8]
. 在  ``main``  方法主体中，添加代码，创建一个具有四个  ``Runnable``  线程的数组，并在每个线程中调用一次 Web 服务的  ``getFlower``  操作。*// The web service getFlower operation
// is called 4 times, each in a separate thread.
// When the operation finishes the picture is shown in
// a specific button.*

[source,java]
----

Runnable[] tasks = new Runnable[4];

for (int i=0; i<4;i++) {
    final int index = i;
    tasks[i] = new Runnable() {
        public void run() {
            try {
            
                *// Call the getFlower operation
                // on the web service:*
                Image img = port.getFlower(FlowerFrame.FLOWERS[index]);
                System.out.println("picture downloaded: "+FlowerFrame.FLOWERS[index]);
                     
                *// Add strings to the hashmap:*
                flowers.put(FlowerFrame.FLOWERS[index],img);
                        
                *// Call the showFlower operation
                // on the FlowerFrame:*
                frame.showFlower();
                        
            } catch (IOException_Exception ex) {
                ex.printStackTrace();
            }
            downloadedPictures++;
        }
    };
    new Thread(tasks[i]).start();
}
----

[start=9]
. 为  ``org.flower.service.IOException_Exception``  添加一条导入语句。

[start=10]
. 在  ``main``  方法主体中，添加代码以在单独的线程中调用 Web 服务的  ``getThumbnails``  操作。*// The web service getThumbnails operation is called
// in a separate thread, just after the previous four threads finish.
// When the images are downloaded, the thumbnails are shown at 
// the bottom of the frame.*

[source,java]
----

Runnable thumbsTask = new Runnable() {
    public void run() {
        try {
            while (downloadedPictures < 4) {                        
                try {Thread.sleep(100);} catch (InterruptedException ex) {}
            }
                 
            *// Call the getThumbnails operation
            // on the web service:*
            List<Image> images = port.getThumbnails();
            System.out.println("thumbs downloaded");
                    
            if (images != null &amp;&amp; images.size() == 4) {
                for (int i=0;i<4;i++) {
                    thumbs.put(FlowerFrame.FLOWERS[i],images.get(i));
                }
                frame.setThumbnails(thumbs);
            }
        } catch (IOException_Exception ex) {
            ex.printStackTrace();
        }
    }            
};
new Thread(thumbsTask).start();

----

[start=11]
. 在  ``Main.java``  中修复导入（如果在代码中粘贴导入内容时未对其进行修复）。通过在编辑器中单击鼠标右键，然后从上下文菜单中选择 "Fix Imports"（修复导入），可以一次性修复所有导入内容。您可以选择要导入的 List 类；请选择  ``java.util.List`` 。下面是完整的导入语句集：

[source,java]
----

import flower.album.FlowerService;import flower.album.FlowerService_Service;import flower.album.IOException_Exception;import java.awt.Image;import java.util.HashMap;import java.util.List;import java.util.Map;
----

 ``Main``  类现在已完成。


[source,java]
----

public class Main {

     private static int downloadedPictures;
    
     public static void main(String[] args) {
    
        final Map<String,Image> flowers = new HashMap<String,Image>(4);
        final Map<String,Image> thumbs = new HashMap<String,Image>(4);
        
        *// Show the FlowerFrame.*
        final FlowerFrame frame = new FlowerFrame(flowers);
        frame.setVisible(true);
        *        // The client connects to the service with this code.*
        FlowerService_Service service = new FlowerService_Service();
        final FlowerService port = service.getFlowerServicePort();
        
        Runnable[] tasks = new Runnable[4];
        
        *// The web service getFlower operation
        // is called 4 times, each in a separate thread.
        // When the operation finishes the picture is shown in
        // a specific button.*
        for (int i=0; i<4;i++) {
            final int index = i;
            tasks[i] = new Runnable() {
                public void run() {
                    try {
                    
                        *// Call the getFlower operation
                        // on the web service:*
                        Image img = port.getFlower(FlowerFrame.FLOWERS[index]);
                        System.out.println("picture downloaded: "+FlowerFrame.FLOWERS[index]);
                        
                        *// Add strings to the hashmap:*
                        flowers.put(FlowerFrame.FLOWERS[index],img);
                        
                        *// Call the showFlower operation
                        // on the FlowerFrame:*
                        frame.showFlower();
                        
                    } catch (IOException_Exception ex) {
                        ex.printStackTrace();
                    }
                    downloadedPictures++;
                }
            };
            new Thread(tasks[i]).start();
        }
        *// The web service getThumbnails operation is called
        // in a separate thread, just after the previous four threads finish.
        // When the images are downloaded, the thumbnails are shown at 
        // the bottom of the frame.*
        Runnable thumbsTask = new Runnable() {
            public void run() {
                try {
                    while (downloadedPictures < 4) {                        
                        try {Thread.sleep(100);} catch (InterruptedException ex) {}
                    }
                    
                    *// Call the getThumbnails operation
                    // on the web service:*
                    List<Image> images = port.getThumbnails();
                    System.out.println("thumbs downloaded");
                    
                    if (images != null &amp;&amp; images.size() == 4) {
                        for (int i=0;i<4;i++) {
                            thumbs.put(FlowerFrame.FLOWERS[i],images.get(i));
                        }
                        frame.setThumbnails(thumbs);
                    }
                } catch (IOException_Exception ex) {
                    ex.printStackTrace();
                }
            }            
        };
        new Thread(thumbsTask).start();
    }

}
----

现在该客户端应用程序完整了，具有与 Web 服务交互的代码，该 Web 服务委托给了 EJB 模块，以公开其图像。右键单击客户端，然后选择 "Run"（运行）。将启动 Swing 应用程序，并且之后会填充从 Web 服务接收到的图像。如果图像没有全部出现，请清理并构建 FlowerService 项目，然后再次运行。请注意，可以通过选择单选按钮或者单击缩略图来更改主框架中显示的图像。

]]

link:/about/contact_form.html?to=3&subject=Feedback:%20Flower%20Swing%20Client%20EE6[+发送有关此教程的反馈意见+]

要发送意见和建议、获得支持以及随时了解 NetBeans IDE Java EE 开发功能的最新开发情况，请link:../../../community/lists/top.html[+加入 nbj2ee@netbeans.org 邮件列表+]。

