环境
jdk17(jdk14 以后自带将jar 打成安装包工具,版本从1.8调整到17)
Maven:3.2.5
pom.xml:在上篇文章中《Java Swing-2.环境搭建及窗口背景》
效果

下一章:如何通过jdk 自带的工具 打包成 exe ,msi ,或者面安装版的exe 文件(自带jre环境),无需安装这安装jdk
- exe4j: 免费版会有弹框 提示用户使用的免费版exe4j打包工具
- Launch4j: 开源,但目前没找到如何把jre 环境直接放到exe 中,需要把exe和jre 打成一个压缩包形式使用
说明
Swing 控制样式总体来说分为两种
- 通过布局管理器
- 组件提供的修改样式方法(部分组件,有限调整)
- 继承组件后通过画布 重写绘制该组件(按钮变圆,背景色等)
1.布局管理器
布局管理器之前要先了解Swing AWT 的容器(窗口)概念,因为布局管理器是针对容器的,每个容器可以设置一个属于自己的布局管理器。基本概念在这;同时容器可以添加其他容器,组件。
一个容器的整体分布、布局方式。
FlowLayout
流式布局,依次排序,遇到边界挪至下一行
BorderLayout
边界布局(区域布局),分为北、南、西、东、中(上、下、左、右、中) 五个区域
GridLayout
纵横风格的网络,每个网格所占区域大小相同。默认从左向右
GridBagLayout
功能最强大的布局管理器,纵横网格,可以跨越一个或多个网格,设置网格的大小(类似Excel中的合并单元格 ,上下 左右合并)比较难控制
CardLayout
将加入容器的租价看成一叠卡片,只有最上层的才可见(类似轮播图)
绝对定位
setLayout(null)
设置后不会再自动填充这个容器,
BoxLayout
GridBagLayout布局管理器的简化版,配合 Box 一起使用,如果想要简单控制组件间距,可以使用该组件
组件方法
不是所有组件都有以下方法。
.setBounds();
java
// x起点,y起点 宽,高
button.setBounds(5, 5, 80, 25);
.setFont();
java
// 设置字体 形状,大小
zlabel.setFont(new Font("Serif", Font.BOLD, 36));
.setSize();
java
// 宽,高
newLabel.setSize(10,20);
.setIcon();
java
// 设置图标(按钮图标,树状图图标)
newLabel.setIcon(new ImageIcon("/path/xxx/ccc"));
// 设置背景色(边框)
jPanel.setBackground(Color.black);
继承重绘组件
主要依赖于以下两种画布
- Graphics
继承组件 重新 paintComponent(Graphics g) 方法 - Canvase
主要代码
项目目录

控制样式部分都在 首页及编辑页面,主要使用了布局管理器,组件方法来控制按钮的位置
首页代码
java
package com.tiger.four;
import com.tiger.four.table.GoodsJTable;
import com.tiger.four.table.LableJTable;
import com.tiger.four.util.DataUtil;
import com.tiger.four.util.FileTUtil;
import javax.swing.*;
import javax.swing.border.AbstractBorder;
import java.awt.*;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.awt.geom.RoundRectangle2D;
import java.util.Random;
import java.util.Set;
public class InitWindow {
public static JComboBox<String> comboBox;
private static Timer timer;
/**
* 创建窗口
*/
public static void createWindow() {
JFrame jFrame = new JFrame("随便");
//jFrame.setIconImage(new ImageIcon("src/main/resources/static/imgs/yu.png").getImage());
jFrame.setIconImage(FileTUtil.readImage("static/imgs/yu.png").getImage());
jFrame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
// 设置窗口宽高
jFrame.setSize(600, 500);
// 添加菜单
jFrame.setJMenuBar(createMenus(jFrame));
// 添加开始按钮
JPanel jPanel = new JPanel();
jPanel.setBackground(Color.black);
// 添加标签
jFrame.add(createLable(jFrame,jPanel), BorderLayout.NORTH);
startFace(jFrame, jPanel);
jFrame.add(jPanel, BorderLayout.CENTER);
jFrame.setLocationRelativeTo(null);
jFrame.setVisible(true);
}
/**
* 开始界面
* @param jFrame
* @param jPanel
*/
private static void startFace(JFrame jFrame, JPanel jPanel) {
// 清空当前容器所有组件
jPanel.removeAll();
if(timer!=null&&timer.isRunning()){
timer.stop();
}
// 加载开始图片
JLabel imageLabel = new JLabel();
ImageIcon secondImage = FileTUtil.readImage("static/imgs/startb1.png");//new ImageIcon("src/main/resources/static/imgs/startb1.png");
imageLabel.setIcon(secondImage);
jPanel.add(imageLabel);
// 增加图片点击时间
imageLabel.addMouseListener(new MouseAdapter() {
@Override
public void mouseClicked(MouseEvent e) {
// 跳转抽签页面
drawLotsFace(jFrame, jPanel);
}
});
jFrame.revalidate();
jFrame.repaint();
}
/**
* 抽签界面
*/
private static void drawLotsFace(JFrame jFrame, JPanel jPanel) {
// 清空当前容器所有组件
jPanel.removeAll();
// 加载抽签界面
JLabel imageLabel = new JLabel();
ImageIcon secondImage = FileTUtil.readImage("static/imgs/move/chou2.gif"); //new ImageIcon("src/main/resources/static/imgs/move/chou2.gif");
imageLabel.setIcon(secondImage);
jPanel.add(imageLabel);
// 重新渲染窗口
jFrame.revalidate();
jFrame.repaint();
// 创建一个Timer,在2秒后触发ActionEvent
timer = new Timer(2000, new ActionListener() {
@Override
public void actionPerformed(ActionEvent evt) {
// 跳转结束画面
endFace(jFrame, jPanel, (Timer) evt.getSource());
}
});
timer.start();
}
/**
* 结束界面
* @param jFrame
* @param jPanel
* @param timer1
*/
private static void endFace(JFrame jFrame, JPanel jPanel, Timer timer1) {
// 清空容器中所有组件
jPanel.removeAll();
// 绘制结束画面
// 首级容器 让文字和按钮换行展示
JPanel firstPanel = new JPanel();
firstPanel.setLayout(new BoxLayout(firstPanel, BoxLayout.Y_AXIS)); // 设置垂直布局
// 获取当前选择的下来框
String selectedItem = comboBox.getSelectedItem() + "";
Set<String> strings = DataUtil.getGoods().get(selectedItem);
// 生成随机数,随机生成结果
Object[] array = strings.toArray();
int i = new Random().nextInt(array.length);
String good = array[i] + "";
// 二级容器(为了让文字 和 按钮都居中展示,不加会错位)
JPanel secondPanel = new JPanel();
// 创建物品标签
JLabel newLabel = new JLabel(good);
newLabel.setFont(new Font("Serif", Font.BOLD, 24));
newLabel.setForeground(Color.GREEN);
secondPanel.add(newLabel);
// 创建菜单容器,保证两个菜单在一行上,且有一些间距
JPanel buttonPanel = new JPanel();
buttonPanel.setLayout(new BoxLayout(buttonPanel, BoxLayout.X_AXIS));
JButton again = new JButton("再来一次");
JButton ok = new JButton("就这个");
// 按钮点击事件
again.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
// 当按钮被点击时执行的代码
drawLotsFace(jFrame, jPanel); // 确保这个方法在你的类中已经定义
}
});
ok.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
// 当按钮被点击时执行的代码
startFace(jFrame, jPanel); // 确保这个方法在你的类中已经定义
}
});
// 为了在垂直布局中保持组件之间的间距,可以使用 Box.createVerticalStrut() 或添加一些空白的组件
buttonPanel.add(again);
buttonPanel.add(Box.createHorizontalStrut(10)); // 可选:添加水平间距
buttonPanel.add(ok);
firstPanel.add(secondPanel);
firstPanel.add(Box.createVerticalStrut(249)); // 可选:添加垂直间距
firstPanel.add(buttonPanel);
jPanel.add(firstPanel);
// 重新渲染
jFrame.revalidate();
jFrame.repaint();
timer.stop();
}
/**
* 创建菜单
* 1.编辑物品
* 2.编辑物品标签
*
* @return
*/
public static JMenuBar createMenus(JFrame jFrame) {
JMenuBar jMenuBar = new JMenuBar();
JMenu setting = new JMenu("设置");
JMenuItem editGoods = new JMenuItem("编辑物品");
// 点击菜单时触发
editGoods.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
// 绘制编辑标签界面
LableJTable.showLableJDialog(jFrame);
}
});
JMenuItem editGoodsType = new JMenuItem("编辑物品标签");
editGoodsType.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
// 编辑物品界面
GoodsJTable.showLableJDialog(jFrame);
}
});
setting.add(editGoods);
setting.add(editGoodsType);
jMenuBar.add(setting);
return jMenuBar;
}
/**
* 创建主页标签下拉框
* 1.读取标签信息
* 2.加载标签页面
* @return
*/
public static JPanel createLable(JFrame jFrame, JPanel jPanel) {
JPanel nPanel = new JPanel(new GridBagLayout());
// 创建一个字符串数组作为下拉框的选项
Object[][] tableData = DataUtil.getLable();
String[] options = new String[tableData.length];
for (int i = 0; i < tableData.length; i++) {
options[i] = tableData[i][0] + "";
}
JPanel son = new JPanel();
JLabel label = new JLabel("标签:");
// 创建一个 JComboBox 并添加选项
InitWindow.comboBox = new JComboBox<>(options);
InitWindow.comboBox.setSize(1, comboBox.getHeight());
// 创建个首页按钮
JPanel pagePanel = new JPanel();
JButton page = new JButton("首页");
/*page.setContentAreaFilled(false); // 去除按钮的默认填充,以便边框更明显
page.setBorderPainted(true); // 确保边框被绘制
page.setBorder(new RoundedBorder(10)); // 应用圆角边框*/
page.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
startFace( jFrame, jPanel);
}
});
pagePanel.add(page);
son.add(label);
son.add(comboBox);
// 设置GridBag约束
GridBagConstraints gbc = new GridBagConstraints();
//
gbc.gridx = 0;
gbc.gridy = 0;
gbc.weightx = 0.1; // 左侧占90%
gbc.anchor = GridBagConstraints.LINE_START; // 右对齐
gbc.fill = GridBagConstraints.HORIZONTAL;
nPanel.add(pagePanel, gbc);
// 添加空面板占位左侧90%宽度
gbc.gridx = 1;
gbc.gridy = 0;
gbc.weightx = 0.8; // 左侧占90%
gbc.fill = GridBagConstraints.BOTH;
nPanel.add(new JPanel(), gbc);
// 设置单选框约束
gbc.gridx = 2; // 第二列
gbc.gridy = 0;
gbc.weightx = 0.1; // 右侧占10%
gbc.anchor = GridBagConstraints.LINE_END; // 右对齐
gbc.fill = GridBagConstraints.HORIZONTAL; // 水平填充
nPanel.add(son, gbc);
return nPanel;
}
static class RoundedBorder extends AbstractBorder {
private int radius;
public RoundedBorder(int radius) {
this.radius = radius;
}
@Override
public void paintBorder(Component c, Graphics g, int x, int y, int width, int height) {
Graphics2D g2d = (Graphics2D) g.create();
g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
g2d.setStroke(new BasicStroke(2));
g2d.setPaint(Color.BLACK);
// 绘制圆角矩形边框
g2d.draw(new RoundRectangle2D.Double(x, y, width - 1, height - 1, radius, radius));
g2d.dispose();
}
@Override
public Insets getBorderInsets(Component c) {
return new Insets(radius, radius, radius, radius);
}
@Override
public boolean isBorderOpaque() {
return false;
}
}
}
编辑页面代码-标签编辑页
java
package com.tiger.four.table;
import com.tiger.four.InitWindow;
import com.tiger.four.util.DataUtil;
import javax.swing.*;
import javax.swing.event.ChangeEvent;
import javax.swing.event.TableModelEvent;
import javax.swing.event.TableModelListener;
import javax.swing.table.DefaultTableModel;
import javax.swing.table.TableCellRenderer;
import javax.swing.table.TableModel;
import java.awt.*;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
/**
* 标签列表
*/
public class LableJTable {
public static void showLableJDialog(JFrame jFrame) {
JDialog dialog = new JDialog(jFrame, "物品列表", true);
dialog.setSize(500, 300);
dialog.setLayout(new BorderLayout());
dialog.setLocationRelativeTo(null);
JPanel jPanel = new JPanel(new BorderLayout());
JButton add = new JButton("新增");
// 表格
Object[] columnTitle = {"标签", "操作"};
DefaultTableModel model = new DefaultTableModel(DataUtil.getLable(), columnTitle);
JTable jTable = new JTable(model);
// 新增点击时触发
add.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
model.addRow(new Object[]{""});
dialog.pack();
}
});
// 添加 TableModelListener
model.addTableModelListener(new TableModelListener() {
@Override
public void tableChanged(TableModelEvent e) {
String[][] tableData = DataUtil.getLable();
int firstRow = e.getFirstRow();
int column = e.getColumn();
TableModel source = (TableModel) e.getSource();
if (column < 0) return;
Object valueAt = source.getValueAt(firstRow, column);
if (("删除").equals(valueAt)) return;
// 同步首页标签下拉框
int itemCount = InitWindow.comboBox.getItemCount();
if (itemCount - 1 < firstRow) {
InitWindow.comboBox.addItem(valueAt + "");
} else {
Object o = tableData[firstRow][column];
InitWindow.comboBox.removeItem(o + "");
InitWindow.comboBox.insertItemAt(valueAt + "", firstRow);
}
DataUtil.changeLable(firstRow, column, valueAt + "");
}
});
jTable.setRowHeight(35);
jTable.getColumnModel().getColumn(1).setCellEditor(new DeleteLableEditor(jTable));
jTable.getColumnModel().getColumn(1).setCellRenderer(new DelectButtonRender());
jPanel.add(add, BorderLayout.NORTH);
jPanel.add(new JScrollPane(jTable), BorderLayout.CENTER);
dialog.add(jPanel);
dialog.pack();
dialog.setVisible(true);
}
}
/**
* 将指定列渲染成按钮
* 举一反三
* 也可以时图片 单选框 多选框 等非文字组件
*
*/
class DelectButtonRender implements TableCellRenderer {
private JPanel panel;
private JButton button;
public DelectButtonRender() {
this.initButton();
this.initPanel();
// 添加按钮。
this.panel.add(this.button);
}
private void initButton() {
this.button = new JButton();
// 设置按钮的大小及位置。
this.button.setBounds(5, 5, 80, 25);
}
private void initPanel() {
this.panel = new JPanel();
this.panel.setLayout(null);
}
@Override
public Component getTableCellRendererComponent(JTable table, Object value, boolean isSelected, boolean hasFocus, int row, int column) {
this.button.setText("删除");
return this.panel;
}
}
/**
* 指定列中 的单元格触发修改时调用
* 触发修改时 可以将其位置的文字或组件 改成其他下拉框,按钮等
*/
class DeleteLableEditor extends DefaultCellEditor {
private static final long serialVersionUID = -6546334664166791132L;
private JPanel deletePanel;
private JButton deleteButton;
private JTable lableTable;
public DeleteLableEditor(JTable table) {
super(new JTextField());
lableTable = table;
this.setClickCountToStart(1);
this.init();
this.deletePanel.add(this.deleteButton);
}
/**
* 初始化按钮
* 当列表开始渲染时触发
*/
private void init() {
this.deletePanel = new JPanel();
this.deletePanel.setLayout(null);
this.deleteButton = new JButton();
this.deleteButton.setBounds(0, 0, 80, 25);
this.deleteButton.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e) {
JButton btn = (JButton) (e.getSource());
ChangeEvent ce = new ChangeEvent(btn);
lableTable.editingStopped(ce);
int selectedRow = lableTable.getSelectedRow();
DefaultTableModel model = (DefaultTableModel) (lableTable.getModel());
String value = model.getValueAt(selectedRow, 0) + "";
model.removeRow(selectedRow);
InitWindow.comboBox.removeItem(value);
DataUtil.deleteLable(selectedRow, 0, value);
}
});
}
@Override
public Component getTableCellEditorComponent(JTable table, Object value, boolean isSelected, int row, int column) {
this.deleteButton.setText("删除");
return this.deletePanel;
}
@Override
public Object getCellEditorValue() {
return this.deleteButton.getText();
}
}
编辑页面代码-物品编辑页
java
package com.tiger.four.table;
import com.tiger.four.util.DataUtil;
import javax.swing.*;
import javax.swing.event.TableModelEvent;
import javax.swing.event.TableModelListener;
import javax.swing.table.DefaultTableModel;
import javax.swing.table.TableCellRenderer;
import javax.swing.table.TableModel;
import java.awt.*;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
/**
* 物品列表
*/
public class GoodsJTable {
public static void showLableJDialog(JFrame jFrame) {
JDialog dialog = new JDialog(jFrame, "物品列表", true);
dialog.setSize(500, 300);
dialog.setLayout(new BorderLayout());
dialog.setLocationRelativeTo(null);
JPanel jPanel = new JPanel(new BorderLayout());
JButton add = new JButton("新增");
// 表格
Object[] columnTitle = {"物品", "标签", "操作"};
DefaultTableModel model = new DefaultTableModel(DataUtil.getGoodsArray(), columnTitle);
JTable jTable = new JTable(model);
add.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
model.addRow(new Object[]{""});
dialog.pack();
}
});
// 添加 TableModelListener
model.addTableModelListener(new TableModelListener() {
@Override
public void tableChanged(TableModelEvent e) {
int firstRow = e.getFirstRow();
int column = e.getColumn();
if (column < 0) return;
TableModel source = (TableModel) e.getSource();
Object valueAt = source.getValueAt(firstRow, column);
if (("删除").equals(valueAt)) {
return;
}
DataUtil.changeGoods(firstRow, column, valueAt + "");
}
});
jTable.setRowHeight(35);
jTable.getColumnModel().getColumn(2).setCellEditor(new DeleteEditor(jTable));
jTable.getColumnModel().getColumn(1).setCellEditor(new CheckBoxEditor(jTable));
jTable.getColumnModel().getColumn(2).setCellRenderer(new DeleteRenderer());
jPanel.add(add, BorderLayout.NORTH);
jPanel.add(new JScrollPane(jTable), BorderLayout.CENTER);
dialog.add(jPanel);
dialog.pack();
dialog.setVisible(true);
}
}
/**
* 单元格渲染时 会按照指定格式渲染
*/
class DeleteRenderer implements TableCellRenderer {
private JPanel panel;
private JButton button;
public DeleteRenderer() {
this.initButton();
this.initPanel();
this.panel.add(this.button);
}
private void initButton() {
this.button = new JButton();
// 设置按钮的大小及位置。
this.button.setBounds(5, 5, 80, 25);
}
private void initPanel() {
this.panel = new JPanel();
// panel使用绝对定位,这样button就不会充满整个单元格。
this.panel.setLayout(null);
}
@Override
public Component getTableCellRendererComponent(JTable table, Object value, boolean isSelected, boolean hasFocus, int row, int column) {
this.button.setText("删除");
return this.panel;
}
}
/**
* 单元格触发编辑时 会按照指定格式渲染
*/
class DeleteEditor extends DefaultCellEditor {
private static final long serialVersionUID = -6546334664166791132L;
private JPanel buttonPanel;
private JButton deleteButton;
private JTable goodsTable;
public DeleteEditor(JTable table) {
super(new JTextField());
goodsTable = table;
this.setClickCountToStart(1);
this.init();
}
private void init() {
this.buttonPanel = new JPanel();
this.buttonPanel.setLayout(null);
this.deleteButton = new JButton();
this.deleteButton.setBounds(0, 0, 80, 25);
this.deleteButton.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e) {
if (goodsTable.getCellEditor() != null) {
goodsTable.getCellEditor().stopCellEditing();
}
/*JButton btn = (JButton) (e.getSource());
ChangeEvent ce = new ChangeEvent(btn);
goodsTable.editingStopped(ce);*/
int selectedRow = goodsTable.getSelectedRow();
DefaultTableModel model = (DefaultTableModel) (goodsTable.getModel());
String valueAt = model.getValueAt(selectedRow, 0) + "";
model.removeRow(selectedRow);
DataUtil.deleteGoods(selectedRow, 0, valueAt);
}
});
this.buttonPanel.add(this.deleteButton);
}
@Override
public Component getTableCellEditorComponent(JTable table, Object value, boolean isSelected, int row, int column) {
this.deleteButton.setText("删除");
return this.buttonPanel;
}
@Override
public Object getCellEditorValue() {
return this.deleteButton.getText();
}
}
/**
* 单元格触发编辑时 会按照指定格式渲染
*/
class CheckBoxEditor extends DefaultCellEditor {
private JPanel boxPanel;
private JComboBox<String> comboBox;
private JTable goodsTable;
public CheckBoxEditor(JTable table) {
super(new JTextField());
goodsTable = table;
this.setClickCountToStart(2);
this.init();
}
private void init() {
this.boxPanel = new JPanel();
this.boxPanel.setLayout(null);
String[][] tableData = DataUtil.getLable();
int goodsLength = tableData.length;
String[] option = new String[goodsLength];
for (int i = 0; i < goodsLength; i++) {
option[i] = tableData[i][0] + "";
}
this.comboBox = new JComboBox<String>(option);
this.comboBox.setBounds(0, 0, 80, 25);
this.boxPanel.add(this.comboBox);
}
@Override
public Component getTableCellEditorComponent(JTable table, Object value, boolean isSelected, int row, int column) {
this.comboBox.setSelectedItem(value);
return this.boxPanel;
}
@Override
public Object getCellEditorValue() {
return this.comboBox.getSelectedItem();
}
}
异常
无法读取静态文件
开发阶段一切正常,打成jar 以后静态文件无法读取
原因
常规方法无法从jar(压缩包) 文件中读取文件
代码
java
// 用此方法读取
InputStream inputStream = FileTUtil.class.getClassLoader().getResourceAsStream(path);
ImageIcon 加载动图 不动
原因
通过源码中观察 ,流的的方式 会导致缺少以下渲染加载动作,导致无法将动图完整加载到Icon 中,从而只显示动图的第一张图
Toolkit.getDefaultToolkit().createImage(imageData);
代码
java
public static ImageIcon readImage(String path){
try (InputStream inputStream = FileTUtil.class.getClassLoader().getResourceAsStream(path)) {
if (inputStream == null) throw new IllegalArgumentException("资源未找到: " + path);
// 将流转换为字节数组
ByteArrayOutputStream buffer = new ByteArrayOutputStream();
byte[] data = new byte[1024];
int bytesRead;
while ((bytesRead = inputStream.read(data, 0, data.length)) != -1) {
buffer.write(data, 0, bytesRead);
}
byte[] imageBytes = buffer.toByteArray();
// 此行是关键,直接new ImageIcon(inputStream);会让动图失效
return new ImageIcon(imageBytes);
} catch (IOException e) {
e.printStackTrace();
throw new RuntimeException("资源加载失败", e);
}
}