从零开始制作高仿QQ(一)——为窗口添加背景

目录

背景

开发环境

开始之前

真正的正文

1. 制作无标题栏的窗口

2. 给窗口添加关闭按钮

3. 添加鼠标拖动事件

4. 添加背景图片

本节代码

附录

1. 相关链接

2. 资源图片


背景

前些日子Java课的老师给我们留了个作业,要求写一个程序来实现点对点聊天的功能,给了我们一个半月的时间,给的示例程序是一个控制台程序。这我就很不满意了,像我这样优秀的人怎么可能花一个半月只写控制台程序(咳咳,吹大了……)于是我思来想去,决定把程序写成模仿QQ聊天界面的 GUI程序。

为了写这个程序我确实花了不少心思,各种百度找各种问题的解决方法,终于在交作业截止前三天写完了 一半的 程序。代码前后大概有两千多行吧,只模仿完了聊天界面和系统托盘图标。但是既然已经模仿了这么多,那我不妨就索性把它写成一个真正的Java版本的QQ吧,顺便在此记录下我的模仿历程,也希望我的经历能够帮助到更多的初学者少踩一些坑。

我的Java作业源程序:Java局域网聊天儿小软件儿(本文的程序和我的作业还是有一丢丢的区别的,作业毕竟是作业,总要给老师留点面子不是嘛

在程序的开发过程中,我还借鉴了许多前辈们的代码 但忘了保存链接 ,我会 尽可能地找到这些链接,并 在相应的章节把这些链接 中能找到的部分 贴在附录中。好了,废话不多说,下面开始正文。

开发环境

  • Eclipse Java Oxygen(哪个版本的我给忘了……)
  • JDK-10(但在开发的时候把编译的级别设成了1.7,谁叫老师装的JDK-1.8呢)
工程设置
图1.2-1 Eclipse工程设置

开始之前

怎么说我们也是要给窗口添加背景,所以我们首先要做一张窗口背景图出来。我呢,就直接用截图工具截了一个聊天窗口,然后用PS抹了抹,就成了一张“固定大小”的背景图(别着急,后面我们还会再改,这里我们只介绍怎么添加背景图)。后来嫌QQ的自带皮肤有点……于是就在手机里找了一张照片放了进去。图我贴在文末的附录里了,不许吐槽我的审美,单身人士慎入!(求小仙女保佑我Java作业满分满分,考试高分过)

真正的正文

1. 制作无标题栏的窗口

标题栏嘛,就是放标题的栏。我们打开QQ的聊天界面,就可以发现,这个界面的“标题栏”好像和通常的标题栏长得不太一样。所以我们要先去掉窗口的标题栏和边框,然后把自定义的标题栏加进去。

由于我们现在做的是群聊的界面,我测量了一下,得到了窗口的尺寸为891\times683 px^{2}。因此可以用如下的代码创建我们的窗口:

package com.csdn.main;

import java.awt.event.WindowAdapter;
import java.awt.event.WindowEvent;

import javax.swing.JDialog;
import javax.swing.JFrame;

public class JavaMyOICQ extends JFrame {
	public static void main(String[] args) {
		JavaMyOICQ dlg = new JavaMyOICQ();
		dlg.setVisible(true);
	}
	
	public JavaMyOICQ() {
		super();
		this.setSize(891, 683);	
		this.setLocationRelativeTo(null);// 设置窗口在屏幕正中间显示
		this.setResizable(false);
		this.setDefaultCloseOperation(JDialog.DISPOSE_ON_CLOSE);
		this.setUndecorated(true);// 这句话用来阻止窗体采用本机系统修饰,这样窗体就没有标题栏和边框了
	}
}

但是这个时候点击运行,会发现弹出了一个灰色的长方形,怎么关都关不掉,只能用任务管理器结束进程。仔细想想也对,Java窗体的关闭按钮是在标题栏上的,关了标题栏,关闭按钮可不就没了么!所以就有了下面的步骤。

2. 给窗口添加关闭按钮

—— 什么?!不就是加一个按钮么,至于搞一个小节出来?

话是这么说没错,但是这里有一个要注意的地方。有的同学喜欢用System.exit(0);来关闭窗口。但是这会出现一个问题,就是点了关闭按钮,窗口确实关了,但是程序也退出了。这就有点不好了吧,总不能每次想要和别人聊天都要重新登录一遍呀?所以,我们只需要把System.exit(0);改为frame.dispose();就好了。

接下来,我们可以再添加“最大化”和“最小化”按钮,它们的代码分别如下:

frame.setExtendedState(JFrame.ICONIFIED);// 使窗口最小化到任务栏图标
frame.setExtendedState(JFrame.MAXIMIZED_BOTH);// 使窗口最大化到全屏
frame.setExtendedState(JFrame.NORMAL);// 使窗口恢复正常(设置的)大小

还需要注意的是,在向窗口中添加控件时,要尽量先将控件添加到一个JPanel容器中,再一并添加到窗口中,方便控件的管理。

3. 添加鼠标拖动事件

标题栏也去掉了,关闭按钮也加了,但是现在还是有一个美中不足的地方,就是程序的窗口总是在屏幕的正中央,没办法拖动了。所以接下来,我们要给我们的窗口添加鼠标拖动事件。

“鼠标拖动事件”听起来像是一个事件,但其实它由两部分组成:一个是当鼠标按下时,要记录开始拖动时鼠标的坐标,另一个就是在鼠标拖动时记录鼠标的水平和垂直位移。这样就可以实时地计算出窗口应处的位置,并移动它。

经过测量,可以得出QQ聊天界面的“标题栏”高度为37px,因此我们要设定只有当鼠标位于窗口的标题栏范围内时,拖动事件才有效。给窗口添加鼠标拖动事件的代码如下:

// 需要额外添加的属性
private int xOld = 0;
private int yOld = 0;


// ......


// 给窗口添加鼠标拖动事件
frame.addMouseListener(new MouseAdapter() {
	    public void mousePressed(MouseEvent e) {
		xOld = e.getX();//记录鼠标按下时的坐标
		yOld = e.getY();
	}
});

frame.addMouseMotionListener(new MouseMotionAdapter() {
	public void mouseDragged(MouseEvent e) {
		if (yOld > 37) return;
		int xOnScreen = e.getXOnScreen();
		int yOnScreen = e.getYOnScreen();
		int xx = xOnScreen - xOld;
		int yy = yOnScreen - yOld;
		setLocation(xx, yy);//设置拖拽后,窗口的位置
	}
});

4. 添加背景图片

说了这么多,总算到这节的重点了。说到添加背景图片,我可是在网上找了N+1种方法。这些方法不是图片添加不上,就是会覆盖控件,就算没有覆盖控件的,把窗口拖到屏幕外面(一部分)再拖回来,刚才到屏幕外面的那一部分控件甚至都不会重绘(真是个不错的方法)。

但是下面这段代码就不同了。它的原理也很简单,就是在窗口中添加一个覆盖整个窗口的JPanel控件,并重写它的paintComponent()方法,使它在绘制时将背景图片一并绘制出来。paintComponent()是Java虚拟机在触发窗口绘制事件时自动调用的方法,不需要我们手动去调用。这个方法只一个Graphics型变量作为形参。这个参数可以理解为一个画笔,我们可以用它画出任意的自己想要的东西(有关paintComponent()方法及Graphics型变量更详细的用法我们会在之后的教程中讲到,在这里就只简单的提一下)。

还有一个问题,就是Java如何读取图片呢?其实,Java读取图片可以有两种方法:一是直接根据路径读取图片文件,二是读取资源文件中的图片文件。在这里我们选择第二种方法。

将图片添加到Java工程的资源文件中的方法其实很简单。只需要:新建包->打开工程文件夹下的src文件夹->进入新建的包->将图片复制进文件夹->回到Eclipse->右键单击工程名称->选择Refresh 即可。

若要读取资源文件中的图片,只需要如下代码:

import java.awt.Image;
import javax.swing.ImageIcon;

String FILE_PATH = "";

// FILE_PATH是图片的路径。
// 如:图片存放在com.csdn.images.frameui包中,则图片路径为:
// /com/csdn/image/frameui/main_frame_background_default.png
// 注意不要落下第一个斜杠

ImageIcon imageIcon = new ImageIcon(getClass().getResource(FILE_PATH));
Image image = imageIcon.getImage();

so,给窗口添加背景图的代码如下:

// 给窗口设置背景图片
JPanel imagePanel = new JPanel() {
	public void paintComponent(Graphics g) {
		super.paintComponent(g);
		Graphics2D g2d = (Graphics2D) g;// Graphics2D比Graphics具有更强大的功能,所以在这里我们做一下类型转换

		// 设置画笔的抗锯齿属性
		g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
		g2d.setRenderingHint(RenderingHints.KEY_TEXT_ANTIALIASING, RenderingHints.VALUE_TEXT_ANTIALIAS_ON);

		g2d.drawImage(background, 0, 0, 891, 683, this);
		g2d.drawImage(functionList, 0, 40, 364, 49, this);
	}
};
frame.add(imagePanel);

但需要注意的是,现在我们窗口上的所有控件都要先添加到imagePanel控件中,而不是直接添加到窗口里了。

本节代码

总结一下本节完成后发生更改的所有代码。同时,我将主方法单独拿出来放到一个专门的主类中,作为程序的入口,方便之后的修改和做其它的设置。

JavaMyOICQ.java

package com.csdn.main;

import com.csdn.frames.JGroupChatDlg;

public class JavaMyOICQ {
	public static void main(String[] args) {
		JGroupChatDlg dlg = new JGroupChatDlg();
		dlg.setVisible(true);
	}
}

JGroupChatDlg.java

package com.csdn.frames;

import java.awt.BorderLayout;
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.GridLayout;
import java.awt.Image;
import java.awt.RenderingHints;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.awt.event.MouseMotionAdapter;

import javax.swing.ImageIcon;
import javax.swing.JButton;
import javax.swing.JDialog;
import javax.swing.JFrame;
import javax.swing.JPanel;

public class JGroupChatDlg extends JFrame implements ActionListener {
	private static final long serialVersionUID = 2973650460422728478L;
	
	// 由于这里我们用了静态变量存储图片,所以就不能用getClass()方法了
	private static final Image background = new ImageIcon(JGroupChatDlg.class.getResource("/com/csdn/images/frameui/main_frame_background_default.png")).getImage();
	private static final Image functionList = new ImageIcon(JGroupChatDlg.class.getResource("/com/csdn/images/frameui/main_frame_function_list.png")).getImage();
	
	private boolean state = false;
	private int xOld = 0;// 辅助窗口拖动事件
	private int yOld = 0;
	
	private JPanel titleBar = new JPanel();
	
	private JPanel upButtonsPanel = new JPanel();
	private JButton minUp = new JButton();
	private JButton maxUp = new JButton();
	private JButton closeUp = new JButton();
	
	public JGroupChatDlg() {
		super();
		this.setSize(891, 683);
		this.setLocationRelativeTo(null);// 设置窗口在屏幕正中间显示
		this.setResizable(false);
		init();
		this.setDefaultCloseOperation(JDialog.DISPOSE_ON_CLOSE);
		this.setUndecorated(true);// 这句话用来阻止窗体采用本机系统修饰,这样窗体就没有标题栏和边框了
		
		this.addMouseListener(new MouseAdapter() {
			public void mousePressed(MouseEvent e) {
				xOld = e.getX();		//记录鼠标按下时的坐标
				yOld = e.getY();
			}
		});
		
		this.addMouseMotionListener(new MouseMotionAdapter() {
			public void mouseDragged(MouseEvent e) {
				if (yOld > 37) return;
				int xOnScreen = e.getXOnScreen();
				int yOnScreen = e.getYOnScreen();
				int xx = xOnScreen - xOld;
				int yy = yOnScreen - yOld;
				setLocation(xx, yy);	//设置拖拽后,窗口的位置
			}
		});
	}
	
	private void init() {
		JPanel imagePanel = new JPanel() {
			private static final long serialVersionUID = -2493397636069899072L;
			public void paintComponent(Graphics g) {
				super.paintComponent(g);
				Graphics2D g2d = (Graphics2D) g;// Graphics2D比Graphics具有更强大的功能,所以在这里我们做一下类型转换
				
				// 设置画笔的抗锯齿属性
				g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
				g2d.setRenderingHint(RenderingHints.KEY_TEXT_ANTIALIASING, RenderingHints.VALUE_TEXT_ANTIALIAS_ON);
				
				g2d.drawImage(background, 0, 0, 891, 683, this);
				g2d.drawImage(functionList, 0, 40, 364, 49, this);
			}
		};
		this.add(imagePanel);
		imagePanel.setLayout(new BorderLayout());
		
		titleBar.setPreferredSize(new Dimension(891, 32));
		titleBar.setLayout(new BorderLayout());
		titleBar.setOpaque(false);
		imagePanel.add(titleBar, BorderLayout.NORTH);
		
		upButtonsPanel.setSize(96, 32);
		upButtonsPanel.setLayout(new GridLayout(1, 3));
		upButtonsPanel.add(minUp);
		upButtonsPanel.add(maxUp);
		upButtonsPanel.add(closeUp);
		minUp.addActionListener(this);
		maxUp.addActionListener(this);
		closeUp.addActionListener(this);
		
		titleBar.add(upButtonsPanel, BorderLayout.EAST);
	}

	public void actionPerformed(ActionEvent e) {
		if (e.getSource() == minUp) { this.setExtendedState(JFrame.ICONIFIED); }
		else if (e.getSource() == maxUp) {
			if (state) {
				this.setExtendedState(JFrame.NORMAL);
				state = false;
			}
			else {
				this.setExtendedState(JFrame.MAXIMIZED_BOTH);
				state = true;
			}
		}
		else if (e.getSource() == closeUp) { this.dispose(); }
	}
}

附录

1. 相关链接

  1. 自定义窗口的实现 - https://blog.csdn.net/ltx06/article/details/28996839
  2. 处理鼠标拖动事件 - https://blog.csdn.net/ljheee/article/details/51051978
  3. 为窗口设置背景图片 - https://blog.csdn.net/xlh1991/article/details/16986555

2. 资源图片

main_frame_background_default.png
图1.5-1 main_frame_background_default.png
main_frame_function_list.png
图1.5-2 main_frame_function_list.png

(注:以上图片位于com.csdn.images.frameui文件夹中)

  • 2
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值