Java Swing学习笔记(2)

Java Swing学习笔记(2)

本文主要记录了使用IntelliJ IDEA Community Edition + Java Swing编写UDP接收工具的过程中遇到的问题。

程序运行效果如下:

监听端口文本框只能输入数字:

开始监听后,输入文本框变灰色,退出时弹出提示框,确认退出后,关闭端口。

接收到数据后进行显示:

在完成本程序的过程中,遇到的问题有:

  1. 一个Thread结束运行后,再次运行会出现异常IllegalThreadStateException
  2. 端口文本框需要只能输入数字
  3. 点击开始后,检查端口号是否符合要求(0~65535)。不符合要求或打开出错则弹出对话框进行提醒。
  4. 在监听时,点击关闭窗口按钮,能够弹出对话框进行提示。
  5. 监听过程中对耗时操作更新UI操作的处理

1. 两次启动同一个线程带来的异常IllegalThreadStateException

在对同一个Thread对象执行两次start()方法时,会出现该异常。

例如下面的代码:

Thread thr = new Thread(new Runnable() {
    @Override
    public void run() {
        ...
    }
});

thr.start();
...
thr.start();  //同一个Thread对象两次start会出现异常IllegalThreadStateException

1.1 问题分析

查看Thread类的start()方法的源码,可以看到如下代码:

if (threadStatus != 0)
    throw new IllegalThreadStateException();

这里的0是一个代表线程状态的常量,查看Thread类的Enum类State:

public enum State {
    /**
     * Thread state for a thread which has not yet started.
     */
    NEW,

    /**
     * Thread state for a runnable thread.  A thread in the runnable
     * state is executing in the Java virtual machine but it may
     * be waiting for other resources from the operating system
     * such as processor.
     */
    RUNNABLE,

    /**
     * Thread state for a thread blocked waiting for a monitor lock.
     * A thread in the blocked state is waiting for a monitor lock
     * to enter a synchronized block/method or
     * reenter a synchronized block/method after calling
     * {@link Object#wait() Object.wait}.
     */
    BLOCKED,

    /**
     * Thread state for a waiting thread.
     * A thread is in the waiting state due to calling one of the
     * following methods:
     * <ul>
     *   <li>{@link Object#wait() Object.wait} with no timeout</li>
     *   <li>{@link #join() Thread.join} with no timeout</li>
     *   <li>{@link LockSupport#park() LockSupport.park}</li>
     * </ul>
     *
     * <p>A thread in the waiting state is waiting for another thread to
     * perform a particular action.
     *
     * For example, a thread that has called {@code Object.wait()}
     * on an object is waiting for another thread to call
     * {@code Object.notify()} or {@code Object.notifyAll()} on
     * that object. A thread that has called {@code Thread.join()}
     * is waiting for a specified thread to terminate.
     */
    WAITING,

    /**
     * Thread state for a waiting thread with a specified waiting time.
     * A thread is in the timed waiting state due to calling one of
     * the following methods with a specified positive waiting time:
     * <ul>
     *   <li>{@link #sleep Thread.sleep}</li>
     *   <li>{@link Object#wait(long) Object.wait} with timeout</li>
     *   <li>{@link #join(long) Thread.join} with timeout</li>
     *   <li>{@link LockSupport#parkNanos LockSupport.parkNanos}</li>
     *   <li>{@link LockSupport#parkUntil LockSupport.parkUntil}</li>
     * </ul>
     */
    TIMED_WAITING,

    /**
     * Thread state for a terminated thread.
     * The thread has completed execution.
     */
    TERMINATED;
}

可以看到,只有线程状态为NEW的线程,才允许使用start()方法。

否则会抛出异常。

1.2 解决方法

每次执行start方法之前,才new一个Thread对象。

例如,在类里定义了一个Thread对象mRecThr,并且该线程需要多次启动,则应该如下定义:

public class UDP {
    ...
    private Thread mRecThr;
    ...
    public void startReceive() {
        mRecFlag = true;
        mRecThr = new Thread(new Runnable() {

            public void run() {
                ...
            }
        });
        mRecThr.start();  //WARNING:同一个线程启动两次,会出现异常IllegalThreadStateException
    }
    ...
}

即保证每次start时,threadStatus都是0。

2. 让文本框JTextField只能输入数字

实现这个效果的方法有很多,这里使用重写PlainDocument类的insertString()方法的方式来实现。

首先,先自定义一个继承PlainDocument类的子类NumberTextField并重写insertString()方法:

class NumberTextField extends PlainDocument {
    public NumberTextField() {
        super();
    }

    public void insertString(int offset, String str, AttributeSet attr)
            throws javax.swing.text.BadLocationException {
        if (str == null) {
            return;
        }

        char[] s = str.toCharArray();
        int length = 0;
        // 过滤非数字
        for (int i = 0; i < s.length; i++) {
            if ((s[i] >= '0') && (s[i] <= '9')) {
                s[length++] = s[i];
            }
            // 插入内容
            super.insertString(offset, new String(s, 0, length), attr);
        }
    }
}

再使用要设置的JTextField对象的setDocument方法应用修改:

textField1.setDocument(new NumberTextField());   //使用该方法让文本框只能接受数字

3. 弹出提示对话框

使用JOptionPaneshowMessageDialog方法来实现这个效果。

打开JOptionPane.java,我们可以看到该方法存在着多个重载。

以下面的方法为例,说明各参数的含义:

public static void showMessageDialog(Component parentComponent,
    Object message, String title, int messageType)
    throws HeadlessException {
    ...
}

Component parentComponent:指定弹出对话框的位置,若传入一个Frame对象,则弹出对话框的位置会在该Frame的中心。若置为null,则弹出对话框在屏幕中心。

Object message:要显示的信息。

String title:对话框标题。

int messageType:对话框的提示图标,在JOptionPane类中有如下常量定义:

//
// Message types. Used by the UI to determine what icon to display,
// and possibly what behavior to give based on the type.
//
/** Used for error messages. */
public static final int  ERROR_MESSAGE = 0;
/** Used for information messages. */
public static final int  INFORMATION_MESSAGE = 1;
/** Used for warning messages. */
public static final int  WARNING_MESSAGE = 2;
/** Used for questions. */
public static final int  QUESTION_MESSAGE = 3;
/** No icon is used. */
public static final int   PLAIN_MESSAGE = -1;

综上所述,下列代码的执行结果如图:

JOptionPane.showMessageDialog(null, "端口号错误,请输入0~65535之间的端口号。", "Error", JOptionPane.ERROR_MESSAGE);

4. 点击“关闭窗口”按钮时的操作和确认对话框

要自定义点击“关闭窗口”按钮时的操作,首先需要取消关闭按钮的功能,然后自己完成点击关闭窗口按钮的事件。

关键代码如下:

frame.setDefaultCloseOperation(JFrame.DO_NOTHING_ON_CLOSE);  //自定义关闭窗口动作
frame.addWindowListener(new WindowAdapter() {
    @Override
    public void windowClosing(WindowEvent e) {
        if(udpService.isRunning()) {
            int flag = 
                JOptionPane.showConfirmDialog(null,"正在监听,是否退出?",
                                              "退出",JOptionPane.YES_NO_OPTION, 
                                              JOptionPane.INFORMATION_MESSAGE);
            if(flag == JOptionPane.NO_OPTION) {
                return;
            } else {
                udpService.stopUDPListen();
                System.exit(0);
            }
        }
        System.exit(0);
    }
});

4.1 自定义点击“关闭窗口”按钮时的操作

如上述代码所示。

首先设置关闭窗口动作为DO_NOTHING_ON_CLOSE(即点击时系统不执行任何操作)

之后在该窗口上添加自定义的WindowAdapter对象,重写windowClosing方法。

在该方法中添加自己需要的逻辑即可。

若要关闭程序,请使用System.exit(0)

4.2 显示确认对话框

JOptionPane.showConfirmDialog(null,"正在监听,是否退出?",
                                              "退出",JOptionPane.YES_NO_OPTION, 
                                              JOptionPane.INFORMATION_MESSAGE);

使用如上代码即可显示确认对话框。

其中的JOptionPane.YES_NO_OPTION常量用于控制确认对话框上显示的按钮。

其他常量如下:

//
// Option types
//

/**
 * Type meaning Look and Feel should not supply any options -- only
 * use the options from the <code>JOptionPane</code>.
 */
public static final int         DEFAULT_OPTION = -1;
/** Type used for <code>showConfirmDialog</code>. */
public static final int         YES_NO_OPTION = 0;
/** Type used for <code>showConfirmDialog</code>. */
public static final int         YES_NO_CANCEL_OPTION = 1;
/** Type used for <code>showConfirmDialog</code>. */
public static final int         OK_CANCEL_OPTION = 2;

可通过监听返回值获得用户按下的按钮是什么,可能的返回值如下所示:

//
// Return values.
//
/** Return value from class method if YES is chosen. */
public static final int         YES_OPTION = 0;
/** Return value from class method if NO is chosen. */
public static final int         NO_OPTION = 1;
/** Return value from class method if CANCEL is chosen. */
public static final int         CANCEL_OPTION = 2;
/** Return value form class method if OK is chosen. */
public static final int         OK_OPTION = 0;
/** Return value from class method if user closes window without selecting
 * anything, more than likely this should be treated as either a
 * <code>CANCEL_OPTION</code> or <code>NO_OPTION</code>. */
public static final int         CLOSED_OPTION = -1;

5. 耗时操作与更新UI

耗时操作请使用子线程完成。

更新UI的操作请用Runnable包装,并使用invokeLater执行。

下面是一个用子线程从阻塞队列中获取UDP传来的数据并刷新UI的例子。

thrGetData = new Thread(new Runnable() {
    @Override
    public void run() {
        while (MainClass.udpService.isRunning()) {
            DatagramPacket dp = MainClass.udpService.getDataFromQueue(); 
            //耗时操作,从阻塞队列中获取数据,队列为空时线程阻塞

            if (dp == null)
                return;
            byte[] recv = new byte[dp.getLength()];
            System.arraycopy(dp.getData(), 0, recv, 0, dp.getLength());
            final String data = new String(recv);
            javax.swing.SwingUtilities.invokeLater(new Runnable() {
                //使用invokeLater来更新UI
                public void run() {
                    recData.setText(data);
                }
            });
        }
    }
});
thrGetData.start();

猜你喜欢

转载自blog.csdn.net/u010034969/article/details/80306717
今日推荐