目录
背景
开发环境
开始之前
真正的正文
1. 制作无标题栏的窗口
2. 给窗口添加关闭按钮
3. 添加鼠标拖动事件
4. 添加背景图片
本节代码
附录
1. 相关链接
2. 资源图片
背景
前些日子Java课的老师给我们留了个作业,要求写一个程序来实现点对点聊天的功能,给了我们一个半月的时间,给的示例程序是一个控制台程序。这我就很不满意了,像我这样优秀的人怎么可能花一个半月只写控制台程序(咳咳,吹大了……)于是我思来想去,决定把程序写成模仿QQ聊天界面的 GUI程序。
为了写这个程序我确实花了不少心思,各种百度找各种问题的解决方法,终于在交作业截止前三天写完了 一半的 程序。代码前后大概有两千多行吧,只模仿完了聊天界面和系统托盘图标。但是既然已经模仿了这么多,那我不妨就索性把它写成一个真正的Java版本的QQ吧,顺便在此记录下我的模仿历程,也希望我的经历能够帮助到更多的初学者少踩一些坑。
我的Java作业源程序:Java局域网聊天儿小软件儿(本文的程序和我的作业还是有一丢丢的区别的,作业毕竟是作业,总要给老师留点面子不是嘛)
在程序的开发过程中,我还借鉴了许多前辈们的代码 但忘了保存链接 ,我会 尽可能地找到这些链接,并 在相应的章节把这些链接 中能找到的部分 贴在附录中。好了,废话不多说,下面开始正文。
开发环境
- Eclipse Java Oxygen(哪个版本的我给忘了……)
- JDK-10(但在开发的时候把编译的级别设成了1.7,谁叫老师装的JDK-1.8呢)
开始之前
怎么说我们也是要给窗口添加背景,所以我们首先要做一张窗口背景图出来。我呢,就直接用截图工具截了一个聊天窗口,然后用PS抹了抹,就成了一张“固定大小”的背景图(别着急,后面我们还会再改,这里我们只介绍怎么添加背景图)。后来嫌QQ的自带皮肤有点……于是就在手机里找了一张照片放了进去。图我贴在文末的附录里了,不许吐槽我的审美,单身人士慎入!(求小仙女保佑我Java作业满分满分,考试高分过)
真正的正文
1. 制作无标题栏的窗口
标题栏嘛,就是放标题的栏。我们打开QQ的聊天界面,就可以发现,这个界面的“标题栏”好像和通常的标题栏长得不太一样。所以我们要先去掉窗口的标题栏和边框,然后把自定义的标题栏加进去。
由于我们现在做的是群聊的界面,我测量了一下,得到了窗口的尺寸为。因此可以用如下的代码创建我们的窗口:
package com.jianshu.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聊天界面的“标题栏”高度为,因此我们要设定只有当鼠标位于窗口的标题栏范围内时,拖动事件才有效。给窗口添加鼠标拖动事件的代码如下:
// 需要额外添加的属性
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;// 限定鼠标拖动事件的有效坐标范围,至于为什么不是38,请大家自行测试
int xOnScreen = e.getXOnScreen();
int yOnScreen = e.getYOnScreen();
int xx = xOnScreen - xOld;
int yy = yOnScreen - yOld;
setLocation(xx, yy);//设置拖拽后,窗口的位置
}
});
4. 添加背景图片
说了这么多,总算到这节的重点了。说到添加背景图片,我可是在网上找了种方法。这些方法不是图片添加不上,就是会覆盖控件,就算没有覆盖控件的,把窗口拖到屏幕外面(一部分)再拖回来,刚才到屏幕外面的那一部分控件甚至都不会重绘(真是个不错的方法)。
但是下面这段代码就不同了。它的原理也很简单,就是在窗口中添加一个覆盖整个窗口的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.jianshu.images.frameui包中,则图片路径为:
// /com/jianshu/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.jianshu.main;
import com.jianshu.frames.JGroupChatDlg;
public class JavaMyOICQ {
public static void main(String[] args) {
JGroupChatDlg dlg = new JGroupChatDlg();
dlg.setVisible(true);
}
}
JGroupChatDlg.java
package com.jianshu.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/jianshu/images/frameui/main_frame_background_default.png")).getImage();
private static final Image functionList = new ImageIcon(JGroupChatDlg.class.getResource("/com/jianshu/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. 相关链接
- 自定义窗口的实现 - https://blog.csdn.net/ltx06/article/details/28996839
- 处理鼠标拖动事件 - https://blog.csdn.net/ljheee/article/details/51051978
- 为窗口设置背景图片 - https://blog.csdn.net/xlh1991/article/details/16986555
2. 资源图片
图1.5-1 main_frame_background_default.png 图1.5-2 main_frame_function_list.png(注:以上图片位于com.jianshu.images.frameui文件夹中)
网友评论