在HOW2J上看到的一个小例子,打开程序显示一个窗口,关闭后再启动程序,窗口出现在上次的相同位置,并且窗口中有一个label显示当前位置,如下图所示。
思路:用文件记录窗口位置,启动程序时首先读取存储的位置信息,再设置窗口位置,显示窗口。
方法一:多线程,启动一个线程记录位置信息。这个不是正常的思路,但是可以练习一下多线程。
方法二:监听器,监听窗口关闭,记录位置信息。
一、多线程方法
使用一个线程,每隔一段时间就读取当前位置信息并保存在文件中。程序中设置一个默认位置,如果是第一次启动,文件中没有信息,就采用默认位置;如果读取到信息,就设置为窗口位置。
JFrame的getX() getY()方法分别可以得到横纵坐标。
创建一个Record类,继承Thread类,实现读取坐标,存储位置,更新label功能。
package gui;
import java.io.DataOutputStream;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import javax.swing.JFrame;
import javax.swing.JLabel;
public class Record extends Thread{
private JFrame frame;
private JLabel label;
private int xLocation;
private int yLocation;
static File file = new File("record.txt");
public Record(JFrame frame,JLabel label) {
this.frame = frame;
this.label = label;
}
public void run() {
while(true) {
updateLocation();
writeRecord();
try {
Thread.sleep(100);
}catch (InterruptedException e){
e.printStackTrace();
}
}
}
//读取横纵坐标
void getLocation() {
xLocation = frame.getX();
yLocation = frame.getY();
}
//更新位置
void updateLocation() {
getLocation();
label.setText("X: " + xLocation + " Y: " + yLocation);
}
//记录坐标到文件
void writeRecord() {
getLocation();
try (
FileOutputStream fos = new FileOutputStream(file);
DataOutputStream dos =new DataOutputStream(fos);
){
dos.writeInt(xLocation);
dos.writeInt(yLocation);
} catch (IOException e) {
e.printStackTrace();
}
}
}
主程序中获取文件中位置信息,设置label显示位置信息,启动Record线程。
package gui;
import java.io.DataInputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import javax.swing.JFrame;
import javax.swing.JLabel;
public class TestGUI {
static File file = new File("record.txt");
private static int xLocation = 300;
private static int yLocation = 300;
//从文件读取坐标信息
private static void getLocation() {
if(file.length() == 0) return;
try(
FileInputStream fis = new FileInputStream(file);
DataInputStream dis =new DataInputStream(fis);
){
xLocation = dis.readInt();
yLocation = dis.readInt();
} catch (IOException e) {
e.printStackTrace();
}
}
public static void main(String[] args) {
// 主窗体
JFrame frame = new JFrame("TestGUI");
// 主窗体设置大小
frame.setSize(400, 300);
// 主窗体设置位置
getLocation();
frame.setLocation(xLocation,yLocation);
// 主窗体中的组件设置为绝对定位
frame.setLayout(null);
// label显示窗体位置
JLabel label = new JLabel("X: " + xLocation + " Y: " + yLocation);
// 同时设置组件的大小和位置
label.setBounds(150, 100, 280, 30);
// 把按钮加入到主窗体中
frame.add(label);
//退出窗体的时候退出程序
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
// 让窗体变得可见
frame.setVisible(true);
//启动线程更新位置信息
Record record = new Record(frame,label);
record.start();
}
}
二、监听器方法
使用监听器应该是更常规的做法,使用多线程的话就要不停的记录位置信息,而监听器可以监听到窗口关闭的动作,记录一次信息即可。
实现一个Listener类,实现ComponentListener和WindowListener两个接口。
ComponentListener有一个componentMoved方法,监视组件移动,可以用来更新label显示的信息。
WindowListener有一个windowClosing方法,监视窗口正在关闭(用户试图通过关闭按钮关闭窗口时),可以用来存储位置到文件中。注意不能用windowClosed窗口关闭 (dispose方法被显式调用用来释放资源时响应)。
由于在Record类中都已实现了这些功能,所以在构造器中new一个Record,调用其相关函数。
package gui;
import java.awt.event.ComponentEvent;
import java.awt.event.ComponentListener;
import java.awt.event.WindowEvent;
import java.awt.event.WindowListener;
import javax.swing.JFrame;
import javax.swing.JLabel;
public class Listener implements ComponentListener, WindowListener {
private Record record;
public Listener(JFrame frame,JLabel label){
record = new Record(frame,label);
}
@Override
public void windowOpened(WindowEvent e) {
// TODO Auto-generated method stub
}
@Override
//窗口关闭前记录位置
public void windowClosing(WindowEvent e) {
// TODO Auto-generated method stub
record.writeRecord();
}
@Override
public void windowClosed(WindowEvent e) {
// TODO Auto-generated method stub
}
@Override
public void windowIconified(WindowEvent e) {
// TODO Auto-generated method stub
}
@Override
public void windowDeiconified(WindowEvent e) {
// TODO Auto-generated method stub
}
@Override
public void windowActivated(WindowEvent e) {
// TODO Auto-generated method stub
}
@Override
public void windowDeactivated(WindowEvent e) {
// TODO Auto-generated method stub
}
@Override
public void componentResized(ComponentEvent e) {
// TODO Auto-generated method stub
}
@Override
//窗口移动时就更新label上显示的位置信息
public void componentMoved(ComponentEvent e) {
// TODO Auto-generated method stub
record.updateLocation();
}
@Override
public void componentShown(ComponentEvent e) {
// TODO Auto-generated method stub
}
@Override
public void componentHidden(ComponentEvent e) {
// TODO Auto-generated method stub
}
}
主程序中,new一个Listener,调用addComponentListener和addWindowListener即可。
package gui;
import java.io.DataInputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import javax.swing.JFrame;
import javax.swing.JLabel;
public class TestGUI {
static File file = new File("record.txt");
private static int xLocation = 300;
private static int yLocation = 300;
//从文件读取坐标信息
private static void getLocation() {
if(file.length() == 0) return;
try(
FileInputStream fis = new FileInputStream(file);
DataInputStream dis =new DataInputStream(fis);
){
xLocation = dis.readInt();
yLocation = dis.readInt();
} catch (IOException e) {
e.printStackTrace();
}
}
public static void main(String[] args) {
// 主窗体
JFrame frame = new JFrame("TestGUI");
// 主窗体设置大小
frame.setSize(400, 300);
// 主窗体设置位置
getLocation();
frame.setLocation(xLocation,yLocation);
// 主窗体中的组件设置为绝对定位
frame.setLayout(null);
// label显示窗体位置
JLabel label = new JLabel("X: " + xLocation + " Y: " + yLocation);
// 同时设置组件的大小和位置
label.setBounds(150, 100, 280, 30);
// 把按钮加入到主窗体中
frame.add(label);
//退出窗体的时候退出程序
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
//使用监听器
Listener listener = new Listener(frame, label);
frame.addComponentListener(listener);
frame.addWindowListener(listener);
// 让窗体变得可见
frame.setVisible(true);
}
}
三、 one more
在Listener类中用到了Record中的方法,这两个类的功能本来就相似,可以建立一个基类,实现基本功能,再分别扩展,实现线程的方法或监听器的方法。需要注意一点,上面多线程方法中,是继承线程类的方式,Java不支持多重继承,所以使用实现Runnable的方式。
1.基类 baseRecord
和原来的Record类似,不再继承Thread类,没有了run方法
package gui;
import java.io.DataOutputStream;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import javax.swing.JFrame;
import javax.swing.JLabel;
public abstract class BaseRecord {
private JFrame frame;
private JLabel label;
private int xLocation;
private int yLocation;
static File file = new File("record.txt");
public BaseRecord(JFrame frame,JLabel label) {
this.frame = frame;
this.label = label;
}
//读取横纵坐标
private void getLocation() {
xLocation = frame.getX();
yLocation = frame.getY();
}
//更新位置
void updateLocation() {
getLocation();
label.setText("X: " + xLocation + " Y: " + yLocation);
}
//记录坐标到文件
void writeRecord() {
getLocation();
try (
FileOutputStream fos = new FileOutputStream(file);
DataOutputStream dos =new DataOutputStream(fos);
){
dos.writeInt(xLocation);
dos.writeInt(yLocation);
} catch (IOException e) {
e.printStackTrace();
}
}
}
2. NewRecord类
继承BaseRecord类,实现Runnable接口
package gui;
import javax.swing.JFrame;
import javax.swing.JLabel;
public class NewRecord extends BaseRecord implements Runnable {
public NewRecord(JFrame frame, JLabel label) {
super(frame, label);
// TODO Auto-generated constructor stub
}
public void run() {
while(true) {
updateLocation();
writeRecord();
try {
Thread.sleep(100);
}catch (InterruptedException e){
e.printStackTrace();
}
}
}
}
3. NewListener类
继承BaseRecord类,实现ComponentListener, WindowListener接口。这样一来也不需要新建一个Record对象,通过它来使用各个方法了。
package gui;
import java.awt.event.ComponentEvent;
import java.awt.event.ComponentListener;
import java.awt.event.WindowEvent;
import java.awt.event.WindowListener;
import javax.swing.JFrame;
import javax.swing.JLabel;
public class NewListener extends BaseRecord implements ComponentListener, WindowListener {
public NewListener(JFrame frame, JLabel label) {
super(frame, label);
// TODO Auto-generated constructor stub
}
@Override
public void windowOpened(WindowEvent e) {
// TODO Auto-generated method stub
}
@Override
public void windowClosing(WindowEvent e) {
// TODO Auto-generated method stub
writeRecord();
}
@Override
public void windowClosed(WindowEvent e) {
// TODO Auto-generated method stub
}
@Override
public void windowIconified(WindowEvent e) {
// TODO Auto-generated method stub
}
@Override
public void windowDeiconified(WindowEvent e) {
// TODO Auto-generated method stub
}
@Override
public void windowActivated(WindowEvent e) {
// TODO Auto-generated method stub
}
@Override
public void windowDeactivated(WindowEvent e) {
// TODO Auto-generated method stub
}
@Override
public void componentResized(ComponentEvent e) {
// TODO Auto-generated method stub
}
@Override
public void componentMoved(ComponentEvent e) {
// TODO Auto-generated method stub
updateLocation();
}
@Override
public void componentShown(ComponentEvent e) {
// TODO Auto-generated method stub
}
@Override
public void componentHidden(ComponentEvent e) {
// TODO Auto-generated method stub
}
}
4. 主程序
四个类的实现都写在了一起,只有使用NewRecord有些不同,在创建Thread对象的时候,把newRecord作为构造方法的参数传递进去,这个线程启动的时候,就会去执行newRecord.run()方法了。
package gui;
import java.io.DataInputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import javax.swing.JFrame;
import javax.swing.JLabel;
public class TestGUI {
static File file = new File("record.txt");
private static int xLocation = 300;
private static int yLocation = 300;
//从文件读取坐标信息
private static void getLocation() {
if(file.length() == 0) return;
try(
FileInputStream fis = new FileInputStream(file);
DataInputStream dis =new DataInputStream(fis);
){
xLocation = dis.readInt();
yLocation = dis.readInt();
} catch (IOException e) {
e.printStackTrace();
}
}
public static void main(String[] args) {
// 主窗体
JFrame frame = new JFrame("TestGUI");
// 主窗体设置大小
frame.setSize(400, 300);
// 主窗体设置位置
getLocation();
frame.setLocation(xLocation,yLocation);
// 主窗体中的组件设置为绝对定位
frame.setLayout(null);
// label显示窗体位置
JLabel label = new JLabel("X: " + xLocation + " Y: " + yLocation);
// 同时设置组件的大小和位置
label.setBounds(150, 100, 280, 30);
// 把按钮加入到主窗体中
frame.add(label);
//退出窗体的时候退出程序
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
// 让窗体变得可见
// //方法1 使用监听器
// Listener listener = new Listener(frame, label);
// frame.addComponentListener(listener);
// frame.addWindowListener(listener);
//方法1.2
NewListener newListener = new NewListener(frame, label);
frame.addComponentListener(newListener);
frame.addWindowListener(newListener);
frame.setVisible(true);
//方法2 启动线程更新位置信息
// Record record = new Record(frame,label);
// record.start();
//方法2.2
// NewRecord newRecord = new NewRecord(frame,label);
// new Thread(newRecord).start();
}
}
四 总结
1.
最开始写完Record类之后,开始写Listener类时发现好多代码可以复用,想到可以创建一个Record的对象,写着写着发现这两个类其实就可以继承一个类,只是实现多线程的时候先想到用继承Thread类,没有考虑两种方法通过继承共用代码。这样既可以减少复制粘贴,修改起来也更方便,而且相对于在Listener类中创建Record的对象,逻辑上更说得过去。
2.
在实现读取横纵坐标,更新位置,记录坐标到文件时,究竟一个方法实现多少功能,也需要好好考虑。
private void getLocation() {
xLocation = frame.getX();
yLocation = frame.getY();
}
//更新位置
void updateLocation() {
getLocation();
label.setText("X: " + xLocation + " Y: " + yLocation);
}
//记录坐标到文件
void writeRecord() {
getLocation();
try (
FileOutputStream fos = new FileOutputStream(file);
DataOutputStream dos =new DataOutputStream(fos);
){
dos.writeInt(xLocation);
dos.writeInt(yLocation);
} catch (IOException e) {
e.printStackTrace();
}
}
之前没有在updateLocation(),writeRecord()中调用getLocation(),结果在一个地方没有先调用getLocation(),造成输出的坐标并不是最新的。现在把它变为私有方法,放在另外两个方法内,这样外部调用时不需要知道细节,不容易出现问题。一个方法应该是职责单一的,但是获取最新的位置和使用位置信息应该是捆绑在一起的。
3.
实现Listener时,考虑要不要把addComponentListener和addWindowListener都写到一个函数里,在主程序里只要调用这个新的函数就行了。但是既然实现了ComponentListener, WindowListener这两个接口,就应该按照正常的方式使用,否则就感觉不伦不类了。