JFrame
是Swing中提供的一个重要类,继承自java.awt.Frame
,它用于创建图形用户界面(GUI)应用程序中的主窗体,我们Swing程序的窗口通常都是从JFrame
写起。JFrame
是一个顶层容器,它提供了窗口的各种功能,如设置标题、设置窗口大小、设置可见性等。
注意:Swing的JFrame
和AWT的Frame
要区分开,AWT也有顶层窗口,写程序时不要引错包。Swing中的控件大多都是以J
开头的。
前一篇笔记的Demo程序中我们已经使用过了JFrame
窗体,这里我们回顾一下之前的代码。
Main.java
package com.gacfox.demo;
import javax.swing.SwingUtilities;
import javax.swing.WindowConstants;
public class Main {
public static void main(String[] args) {
SwingUtilities.invokeLater(new Runnable() {
@Override
public void run() {
MainFrame mainFrame = new MainFrame();
mainFrame
.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);
mainFrame.setVisible(true);
}
});
}
}
MainFrame.java
package com.gacfox.demo;
import javax.swing.JFrame;
public class MainFrame extends JFrame {
public MainFrame() {
setSize(300, 200);
setTitle("Hello, Swing!");
}
}
主窗体MainFrame
继承并扩展了Swing的JFrame
类,它在构造函数中设置了窗体的各种属性。在入口类Main
中,我们在事件分发线程中创建了主窗体,然后设置了其关闭行为和可见性。运行程序即可看到如下窗体,它的大小被设置为300像素 x 200像素,标题被设置为Hello, Swing!
字符串。
前面代码中我们没有设置任何窗体图标,因此显示的图标是Java的默认图标,下面代码我们从类路径中加载一个icon.png
图片作为窗体图标,该图片放置在类路径的根目录下。
package com.gacfox.demo;
import java.net.URL;
import javax.swing.ImageIcon;
import javax.swing.JFrame;
public class MainFrame extends JFrame {
public MainFrame() {
URL iconUrl = getClass().getResource("/icon.png");
ImageIcon icon = new ImageIcon(iconUrl);
setIconImage(icon.getImage());
setSize(300, 200);
setTitle("Hello, Swing!");
}
}
代码中,我们通过getClass().getResource()
方式从类路径下加载图片,然后创建了ImageIcon
对象,它是Swing中图标的封装类型,随后我们调用JFrame
的setIconImage()
方法设置图标,它接受一个Image
类型的数据。运行程序后,我们可以看到类似如下效果。
这里我们要注意,代码中我们是从类路径加载图标的,而不是文件系统上的某个位置,这是因为最终我们分发软件时通常将软件打包为可执行Jar文件,里面的图片等资源文件是打包在Jar内的,它无法通过简单的文件系统路径来访问,这种情况下,使用getClass().getResource()
方式从类路径下加载资源是一种最佳实践。在Eclipse中,建议单独创建一个源代码文件夹命名为resources
来保存这些资源文件,注意要将该资源文件夹和src
文件夹同时配置到Build Path
。
设置窗体位置和大小最简单的方式是直接使用数值指定,下面例子我们设置窗体大小为300像素 x 200像素,屏幕坐标位置为(100, 100)
。
package com.gacfox.demo;
import javax.swing.JFrame;
public class MainFrame extends JFrame {
public MainFrame() {
setLocation(100, 100);
setSize(300, 200);
setTitle("Hello, Swing!");
}
}
不过实际上,像上面这样指定窗体大小和位置不太友好,用户的计算机可能使用各种各样的屏幕分辨率,有些使用4:3
的普屏,有些则使用新潮的16:9
宽屏,有些使用768p的高清显示器,有些则使用更古老的低分辨率显示器,如果窗体的大小和位置都是固定值,那么一些用户可能会发现窗体的大小不合适,或者窗体出现在了意外的位置。此时我们需要一种机制来自动计算窗体的位置和大小,而这个机制的输入参数其实就是用户屏幕的宽度和高度。我们看下面例子代码。
package com.gacfox.demo;
import java.awt.Dimension;
import java.awt.Toolkit;
import javax.swing.JFrame;
public class MainFrame extends JFrame {
public MainFrame() {
// 获取屏幕大小
Toolkit toolkit = Toolkit.getDefaultToolkit();
Dimension dimension = toolkit.getScreenSize();
// 计算屏幕宽度和高度的80%
int width = (int) (dimension.width * 0.6);
int height = (int) (dimension.height * 0.6);
setSize(width > 640 ? 640 : width, height > 480 ? 480 : height);
// 自动设置窗体出现位置
setLocationByPlatform(true);
setTitle("Hello, Swing!");
}
}
代码中,对于窗体的宽度和高度,我们采用这样的策略,计算屏幕宽度和高度的60%,如果这个值过大则采用640 * 480的窗体大小,否则就使用该值作为窗体大小。当然,实际开发中,你可以使用自己的策略来设置合适的窗体大小。对于窗体的位置,我们“投机取巧”的使用了setLocationByPlatform(true)
这个方法,它是平台相关的,通常会使得我们的窗体出现在上一个打开的窗体稍靠右下角的某个位置,这和Windows操作系统本身管理窗体的策略相同,当然,你也可以基于屏幕大小实现自动计算窗体位置,实现让窗体出现在屏幕中心等。
除此之外,我们也可以禁止窗体的大小被修改,此时Windows的窗体最大化按钮将被禁用,窗体也无法被拖动修改大小。
setResizable(false);
除了上述介绍的内容,我们有时还需要基于布局来自动设置窗体的大小,这需要调用JFrame
的pack()
方法,这部分内容在布局管理器章节介绍。
下面方法设置窗体为最大化状态。
setExtendedState(JFrame.MAXIMIZED_BOTH);
下面方法设置窗体为最小化状态。
setExtendedState(JFrame.ICONIFIED);
Swing中创建无边框窗体非常简单,调用下面的JFrame
方法即可。
setUndecorated(true);
不过你会发现如果仅仅是隐藏了窗体边框,我们的窗体既没有最大化、最小化和关闭按钮,而且还不能在桌面上拖动,简直太难用了,实际上,一旦创建无边框窗体,所有的这一切都需要我们手动实现,下面是一个稍复杂的例子,实现了无边框窗体的拖动和关闭按钮。
MainFrame.java
package com.gacfox.demo;
import java.awt.BorderLayout;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import javax.swing.JFrame;
public class MainFrame extends JFrame {
private int xMouse;
private int yMouse;
public MainFrame() {
setSize(300, 200);
setUndecorated(true);
setTitle("Hello, Swing!");
setLayout(new BorderLayout());
TopPanel topPanel = new TopPanel();
topPanel.addMouseListener(new MouseAdapter() {
@Override
public void mousePressed(MouseEvent e) {
super.mousePressed(e);
xMouse = e.getX();
yMouse = e.getY();
}
});
topPanel.addMouseMotionListener(new MouseAdapter() {
@Override
public void mouseDragged(MouseEvent e) {
super.mouseDragged(e);
setLocation(e.getXOnScreen() - xMouse, e.getYOnScreen()
- yMouse);
}
});
add(topPanel, BorderLayout.NORTH);
}
}
TopPanel.java
package com.gacfox.demo;
import java.awt.FlowLayout;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import javax.swing.JButton;
import javax.swing.JPanel;
public class TopPanel extends JPanel {
public TopPanel() {
setLayout(new FlowLayout(FlowLayout.RIGHT));
JButton closeButton = new JButton("x");
closeButton.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
System.exit(0);
}
});
add(closeButton);
}
}
代码中,MainFrame
是主窗体,TopPanel
是窗体上方的一个条状区域,它的作用类似有边框窗体的标题栏,其中包含了一个关闭按钮且该区域可以被拖动。关闭按钮比较简单,它被设置为点击时直接调用System.exit(0)
退出程序,而拖动的实现则稍微复杂,这涉及到AWT事件的使用。我们为TopPanel
设置了两个事件监听器,用户点击鼠标,MouseListener
的mousePressed
被触发,xMouse
和yMouse
会被设置为当前鼠标相对窗体的位置,而用户鼠标按下并拖动时,MouseMotionListener
中的mouseDragged
被触发,e.getXOnScreen()
和e.getYOnScreen()
是当前鼠标相对屏幕的坐标,因此我们将窗体的位置设置为(e.getXOnScreen() - xMouse, e.getYOnScreen() - yMouse)
,它便可精确的符合用户拖动的预期位置了。
该窗体的运行效果如下。
我们这里只是演示基本的无边框窗体使用,因此样式可能比较简陋,但实际上,无边框窗体常用于创建非常精美的富客户端软件,它们通常会与操作系统的通用窗体有着截然不同的华丽样式,这还可能涉及到较为复杂的自定义LookAndFeel。有关布局管理器和事件机制的内容将在后续章节详细介绍。