> For the complete documentation index, see [llms.txt](https://lewiszlw.gitbook.io/notebooks/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://lewiszlw.gitbook.io/notebooks/blogs/history/tu-xing-hua-yong-hu-jie-mian.md).

# 图形化用户界面

## Swing基础

大多数Swing应用都被构建在基础的JFrame内部，JFrame在你使用的任何操作系统中都可以创建视窗应用。

视窗的标题可以像下面这样使用JFrame的构造器来设置：

```java
import javax.swing.*;

public class HelloSwing{
	public static void main(String[] args){
		JFrame frame = new JFrame("Hello Swing");
		frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
		frame.setSize(300,100);   //以像素为单位设置视窗的尺寸
		frame.setVisible(true);   //设置视窗是否可见
	}
}
```

setDefaultCloseOperation()告诉JFrame当用户执行关闭操作时应该做些什么。EXIT\_ON\_CLOSE常量告诉它要退出程序。如果没有这个调用，默认的行为是什么也不做，因此应用不会关闭，默认情况下，该值被设置为HIDE\_ON\_CLOSE，关闭时只是隐藏窗体。

在JFrame中添加一个JLabel：

```java
import javax.swing.*;
import java.util.concurrent.*;

public class HelloLabel {
  public static void main(String[] args) throws Exception {
    JFrame frame = new JFrame("Hello Swing");
    JLabel label = new JLabel("A Label");
    frame.add(label);
    frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
    frame.setSize(300, 100);
    frame.setVisible(true);
    TimeUnit.SECONDS.sleep(1);
    label.setText("Hey! This is Different!");
  }
}
```

在一秒钟之后，JLabel的文本发生了变化。 对于main()线程来说，直接对GUI组件编写代码并非是一种好的想法。Swing有它自己的专用线程来接受UI事件并更新屏幕，如果你从其他线程着手对屏幕进行操作，那么就可能会产生冲突和死锁。 其他线程，例如这里是想main()这样的线程，应该通过Swing事件分发线程提交要执行的任务。你可以通过将任务提交给SwingUtilities.invokeLater()来实现这种方式。这个方法会通过事件分发线程将任务放置到（最终将得到执行的）待执行事件队列中。例如：

```java
import javax.swing.*;
import java.util.concurrent.*;

public class SubmitLabelManipulationTask {
	public static void main(String[] args) throws Exception{
		JFrame frame = new JFrame("Hello Swing");
		final JLabel label=new JLabel("A Label");
		frame.add(label);
		frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
		frame.setSize(300,100);   
		frame.setVisible(true);  
		TimeUnit.SECONDS.sleep(1);
		SwingUtilities.invokeLater(new Runnable(){
			public void run(){
				label.setText("Hey!This is Different!");
			}
		});
	}
}
```

现在不用直接操作JLabel了。取而代之的是，提交一个Runnable，当事件分发线程在事件队列中获取这项任务时，它将执行实际的操作，并且在执行这个Runnable时，不会做其他任何事情，因此也就不会产生任何冲突，当然，前提是程序中的所有代码都遵循这种通过SwingUtilities.invokeLater()来提交操作的方式。这包括启动程序自身，即main()也不应该调用Swing的方法，就像上面的程序一样，它应该向事件队列提交任务。

### 一个显示框架

```java
import javax.swing.*;

public class SwingConsole {
	public static void run(final JFrame f,final int width,final int height){
		SwingUtilities.invokeLater(new Runnable(){
			public void run(){
				f.setTitle(f.getClass().getSimpleName());
				f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
				f.setSize(width, height);
				f.setVisible(true);
			}
		});
	}
}
```

要想使用它，应用必须位于一个JFrame中。静态的run()方法可以将视窗的标题设置为类的简单名。

## 创建按钮

一般来说，要在类中为按钮创建一个字段，以便以后可以引用这个按钮。 JButton是一个组件，它有自己的小窗口，能作为整个更新过程的一部分而自动被重绘。也就是说，不必显式绘制一个按钮或者别的类型的控件；只要把它们放在窗体上，它们可以自动绘制自己。通常会在构造器内部把按钮加入窗体：

```java
import javax.swing.*;
import java.awt.*;
import static 一个显示框架.SwingConsole.*;

public class Button1 extends JFrame {
	private JButton
		b1=new JButton("Button 1"),
		b2=new JButton("Button 2");
	public Button1(){
		setLayout(new FlowLayout());
		add(b1);
		add(b2);
	}
	public static void main(String[] args){
		run(new Button1(),200,100);
	}
}
```

新内容：再向JFrame添加任何组件之前，先给出一个新的FlowLayout类型的“布局管理器”。布局管理器是面板用来隐式地决定控件在窗体上的位置的工具。JFrame通常使用BorderLayout管理布局，但这里不能使用，因为它的默认行为是每加入一个控件，将完全覆盖其他控件。FlowLayout使得控件可以在窗体上从左到右、从上到下连续均匀分布。

## 捕获事件

事件驱动编程（包含了许多关于GUI的内容）的基础，就是把事件同处理时间的代码连接起来。 在Swing中，这种关联的方式就是通过清楚地分离接口（图形组件）和实现（当和组件相关的时间发生时，你要执行的代码）而做到的。每个Swing组件都能够报告其上所有可能发生的事件，并且它能单独报告每种事件。 首先，对所使用的组件，我们只把重点放在它感兴趣的主要事件上。对于JButton，“感兴趣的事件”就是按钮被按下。可以调用JButton的addActionListener()方法。这个方法接受一个实现ActionListener接口的对象作为参数，ActionListener接口只包含一个actonPerformed()方法。所以要想把事件处理代码和JButton关联，需要在一个类中实现ActionListener接口，然后把这个类的对象通过addActionListener()方法注册给JButton。这样按钮按下的时候就会调用actionPerformed()方法（通常这也称为回调）。

```java
import javax.swing.*;
import java.awt.*;
import java.awt.event.*;
import static 一个显示框架.SwingConsole.*;

public class Button2 extends JFrame{
	private JButton
		b1= new JButton("Button 1"),
		b2= new JButton("Button 2");
	private JTextField txt= new JTextField(10);
	class ButtonListener implements ActionListener{
		public void actionPerformed(ActionEvent e){
			String name=((JButton)e.getSource()).getText();  //强制转换
			txt.setText(name);
		}
	}
	private ButtonListener bl=new ButtonListener();
	public Button2(){
		b1.addActionListener(bl);
		b2.addActionListener(bl);
		setLayout(new FlowLayout());
		add(b1);
		add(b2);
		add(txt);
	}
	public static void main(String[] args){
		run(new Button2(),200,150);
	}
}
```

actionPerformed()方法的参数是ActionEvent类型，它包含事件和事件源的所有信息。getSource()方法产生的对象表明了事件的来源。getText()方法返回按钮上的文本，这个文本被放进JTextField。 通常，把ActionListener实现成匿名内部类会更方便，尤其是对每个监听器类只使用一个实例时候更是如此：

```java
import javax.swing.*;
import java.awt.*;
import java.awt.event.*;
import static 一个显示框架.SwingConsole.*;

public class Button2b extends JFrame {
	private JButton
		b1=new JButton("Button 1"),
		b2=new JButton("Button 2");
	private JTextField txt=new JTextField(10);
	private ActionListener bl=new ActionListener(){
		public void actionPerformed(ActionEvent e){
			String name=((JButton)e.getSource()).getText();
			txt.setText(name);
		}
	};
	public Button2b(){
		b1.addActionListener(bl);
		b2.addActionListener(bl);
		setLayout(new FlowLayout());
		add(b1);
		add(b2);
		add(txt);
	}
	public static void main(String[] args){
		run(new Button2b(),200,150);
	}
}
```

## 文本区域

> 《Java编程思想（第4版）》
