简介:直接双击就能玩的Java五子棋桌面程序(Five.jar),界面用Weblaf库美化,操作流畅。每落一子,自动在横向、纵向、主对角线(左上→右下)、副对角线(右上→左下)四个方向分别扫描,实时判断是否达成连续五子胜出。源码结构清晰,src/com/下存放全部Java类,含完整游戏逻辑与UI交互代码;out/production/为编译输出目录;weblaf-complete-1.28.jar已内置,无需额外配置依赖。配套1.jpg、2.jpg、3.jpg三张实际运行截图,直观展示开局、中盘和胜负提示界面。附LICENSE开源协议、README.md使用说明、setup.dat构建参数及.idea相关配置文件,适合Java初学者理解Swing事件驱动机制,也适合作为高校Java课程设计交付材料或Swing图形界面开发入门参考。
1. 这不是玩具,是能上讲台的五子棋——一个真正“可交付”的Swing实战项目
你有没有试过在Java课设答辩前最后一晚,对着IDEA里一堆报红的Swing组件发呆?有没有下载过网上标着“完整源码”的五子棋项目,解压打开却发现:没有main方法、缺jar包、界面丑得像2003年网页、胜负判断只检测横线还漏判斜线?我带过六届Java实训,每年都有学生拿着这类“半成品”来找我救场。而这个项目——Java五子棋(Five.jar),是我去年给大三学生做课程设计时亲手打磨出来的交付级案例。它不追求炫酷动画或网络对战,但每一步都踩在教学关键点上:Swing事件驱动模型如何落地、二维数组状态管理怎么不写死、四向连五检测算法如何拆解为可验证的原子逻辑、Weblaf库怎样真正替代原生LookAndFeel而不引入新坑。双击Five.jar就能玩,不是口号——它内置了weblaf-complete-1.28.jar,打包时已用jar -cfm指定了Main-Class和Class-Path;截图里的胜负弹窗不是PS做的,是JOptionPane.showMessageDialog在真实落子后触发的;src/com/下的每个类名都带着明确职责:BoardPanel只管画格子和棋子,GameEngine只管算胜负,GameController只管串联两者——这种分层不是教科书概念,是我在调试第17次ArrayIndexOutOfBoundsException后,把逻辑硬生生从ActionListener里抠出来重构的结果。关键词里“四向胜负检测”四个字背后,是4个独立方向的扫描循环,每个循环都带边界防护和计数重置逻辑;“Weblaf界面”不是简单换肤,而是用WebLookAndFeel.install()后,手动适配了按钮圆角、面板阴影和鼠标悬停反馈。如果你正卡在Swing布局管理器的选择上(GridBagLayout太难?FlowLayout太松?),或者纠结“胜负检测该放在线程里还是事件里”,这个项目就是给你准备的实操沙盒——它不教你理论,它让你看见代码跑起来时,每一帧背后发生了什么。
2. 整体架构设计:为什么放弃“单类巨无霸”,坚持三层分离?
2.1 核心矛盾:Swing的事件驱动特性 vs 初学者的线性思维惯性
几乎所有初学者写的第一个Swing程序,都会把所有逻辑塞进JFrame子类里:initComponents()初始化界面,actionPerformed()里写落子、画图、判断胜负、弹窗提示……这看似省事,但一旦要加悔棋、计时、音效,代码立刻变成意大利面条。这个项目的第一刀,就砍向了这种惯性。我们强制拆成三层:
- View层(视图):
BoardPanel继承JPanel,只做两件事——绘制15×15棋盘网格(用Graphics2D画虚线)、根据gameState数组绘制黑白棋子(用Ellipse2D.Double画圆)。它不持有任何游戏规则,甚至不知道“黑先白后”,只认int[][] board里0=空、1=黑、2=白。 - Model层(模型):
GameEngine是纯逻辑核心,不依赖任何Swing类。它暴露三个方法:makeMove(int row, int col)执行落子并返回是否合法;checkWin(int row, int col)传入最新落子坐标,返回WinResult对象(含获胜方、五子坐标列表);reset()重置棋盘。最关键的是,checkWin内部不遍历全盘,只扫描以(row,col)为中心的四个方向——这是性能与可读性的平衡点:15×15棋盘最多扫描4×10=40个点,比全盘225点快5倍以上,且错误定位精准。 - Controller层(控制器):
GameController作为粘合剂,监听BoardPanel的鼠标点击事件,调用GameEngine.makeMove(),若成功则更新BoardPanel.board数组并触发重绘,再调用checkWin(),根据结果调用JOptionPane或切换玩家状态。它像交通警察,只指挥,不干活。
提示:这种分层不是为了炫技。当你发现
checkWin方法里突然要加“禁手规则”(如三三禁手),只需修改GameEngine,View和Controller完全不动;想把Weblaf换成FlatLaf?只改MainApp里的UIManager.setLookAndFeel()一行代码。我在指导学生时反复强调:Swing的复杂性不在组件本身,而在状态同步的时机——谁负责更新数据?谁负责刷新界面?谁负责响应用户?三层分离的本质,是把“什么时候更新”这个最难的问题,变成清晰的调用链条。
2.2 Weblaf集成策略:美化≠堆控件,而是重写渲染逻辑
网上很多Swing项目说“用了Weblaf”,实际只是加了WebLookAndFeel.install(),然后用原生JButton、JLabel。这会导致两个问题:一是Weblaf的圆角、阴影效果在JPanel上失效(因为JPanel默认不启用Opaque);二是自定义绘制(如棋盘网格)会被Weblaf的默认背景色覆盖。本项目的Weblaf实践有三处硬核处理:
- 面板透明化改造:
BoardPanel构造函数中显式调用setOpaque(false),并在paintComponent(Graphics g)开头添加Graphics2D g2d = (Graphics2D) g.create(); g2d.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_OVER, 0.95f));——这行代码让棋盘网格在Weblaf深色主题下依然清晰可见,避免了原生setBackground(Color.WHITE)导致的色块冲突。 - 按钮行为定制:重写
ResetButton类(继承JButton),在setBorder(BorderFactory.createEmptyBorder(8, 16, 8, 16))基础上,用putClientProperty("JButton.buttonType", "roundRect")激活Weblaf的圆角渲染,并通过addMouseListener()实现按压态缩放动画(setScale(0.95f)),让重置按钮有真实反馈。 - 字体全局统管:在
MainApp.main()中,WebLookAndFeel.install()后立即执行:
UIManager.put("Label.font", new Font("Microsoft YaHei", Font.PLAIN, 14));
UIManager.put("Button.font", new Font("Microsoft YaHei", Font.BOLD, 14));
UIManager.put("OptionPane.messageFont", new Font("Microsoft YaHei", Font.PLAIN, 13));
这确保了中文显示不糊、字号统一,避免Weblaf默认字体在不同系统上渲染差异。
注意:Weblaf的
weblookandfeel-1.28.jar必须放在项目根目录,且MANIFEST.MF中Class-Path: weblaf-complete-1.28.jar路径要与实际一致。我见过太多学生把jar包放错位置,导致打包后运行报NoClassDefFoundError——这不是代码问题,是构建流程的细节失控。
2.3 四向连五检测:算法拆解与边界防护的实战细节
“检测四个方向”听起来简单,但实际编码时,90%的失败源于边界越界和计数逻辑混乱。本项目的checkWin(int row, int col)方法,将四个方向拆解为独立的scanDirection私有方法,每个方法接收起始坐标、行步长(dr)、列步长(dc),返回连续棋子长度。以水平方向(左→右)为例:
private int scanHorizontal(int row, int col) {
int count = 1; // 当前落子本身
// 向左扫描
for (int c = col - 1; c >= 0 && board[row][c] == currentPlayer; c--) {
count++;
}
// 向右扫描
for (int c = col + 1; c < BOARD_SIZE && board[row][c] == currentPlayer; c++) {
count++;
}
return count;
}
关键细节在于:
- 双重防护:c >= 0防止负索引,c < BOARD_SIZE防止超15;
- 即时终止:一旦board[row][c] != currentPlayer立即跳出,不浪费CPU;
- 计数起点:count = 1从当前子开始,避免重复计算。
其他三个方向同理:
- 垂直方向:dr = 1, dc = 0(上→下),扫描for (int r = row - 1; r >= 0...和for (int r = row + 1; r < BOARD_SIZE...
- 主对角线(左上→右下):dr = 1, dc = 1,扫描需同时检查r和c边界;
- 副对角线(右上→左下):dr = -1, dc = 1,注意r从row + 1开始向下,c从col - 1开始向左。
实操心得:初学者常犯的错误是写成“先扫左再扫右,最后加总”,这会导致
count变量被覆盖。正确做法是每个方向独立计算,最终取最大值。我在代码里加了assert count <= 5 : "连五检测异常:count=" + count;断言,编译时加-ea参数,能在开发阶段捕获逻辑漏洞。另外,检测阈值设为>= 5而非== 5,是为了兼容未来可能的“六子胜”规则扩展。
3. 核心模块详解:从落子到胜负,每一步都在解决真实痛点
3.1 BoardPanel:用Graphics2D绘制可缩放的矢量棋盘
Swing绘图常被初学者误解为“用JLabel贴图”,但本项目坚持用Graphics2D动态绘制,原因有三:一是支持窗口缩放时棋盘自动重绘(getPreferredSize()返回固定尺寸,但paintComponent内根据getWidth()/getHeight()动态计算格距);二是便于高亮获胜连线(后续扩展);三是规避图片资源路径问题。核心代码如下:
@Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
Graphics2D g2d = (Graphics2D) g.create();
g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
g2d.setRenderingHint(RenderingHints.KEY_TEXT_ANTIALIASING, RenderingHints.VALUE_TEXT_ANTIALIAS_ON);
int width = getWidth();
int height = getHeight();
int cellSize = Math.min(width, height) / BOARD_SIZE; // 动态格距
int offsetX = (width - cellSize * BOARD_SIZE) / 2;
int offsetY = (height - cellSize * BOARD_SIZE) / 2;
// 绘制网格线
g2d.setColor(new Color(180, 180, 180));
for (int i = 0; i <= BOARD_SIZE; i++) {
// 横线
g2d.drawLine(offsetX, offsetY + i * cellSize,
offsetX + BOARD_SIZE * cellSize, offsetY + i * cellSize);
// 竖线
g2d.drawLine(offsetX + i * cellSize, offsetY,
offsetX + i * cellSize, offsetY + BOARD_SIZE * cellSize);
}
// 绘制星位(天元及四角)
int[] starPoints = {3, 7, 11}; // 0-indexed,对应第4、8、12线
g2d.setColor(Color.BLACK);
for (int r : starPoints) {
for (int c : starPoints) {
if (r == 7 && c == 7) continue; // 天元单独处理
int x = offsetX + c * cellSize - 3;
int y = offsetY + r * cellSize - 3;
g2d.fillOval(x, y, 6, 6);
}
}
// 天元(中心点)
int centerX = offsetX + 7 * cellSize - 4;
int centerY = offsetY + 7 * cellSize - 4;
g2d.fillOval(centerX, centerY, 8, 8);
// 绘制棋子
for (int r = 0; r < BOARD_SIZE; r++) {
for (int c = 0; c < BOARD_SIZE; c++) {
if (board[r][c] != EMPTY) {
int x = offsetX + c * cellSize - 12;
int y = offsetY + r * cellSize - 12;
Color stoneColor = (board[r][c] == BLACK) ? Color.BLACK : Color.WHITE;
g2d.setColor(stoneColor);
g2d.fillOval(x, y, 24, 24);
// 白子描边增强对比
if (board[r][c] == WHITE) {
g2d.setColor(Color.GRAY);
g2d.drawOval(x, y, 24, 24);
}
}
}
}
g2d.dispose();
}
关键技巧:
getPreferredSize()返回new Dimension(600, 600)确保初始窗口大小合理;offsetX/Y计算居中偏移,避免棋盘贴边;星位坐标用int[] starPoints数组集中管理,方便后期调整;白子描边用drawOval而非setColor,避免覆盖内部填充色。这些细节让界面在不同分辨率下都保持专业感,而不是“能用就行”。
3.2 GameEngine:状态机驱动的游戏逻辑核心
GameEngine类采用状态机模式管理游戏生命周期,避免用布尔变量isGameOver引发的竞态条件。其核心字段:
private enum GameState { RUNNING, PLAYER_BLACK_WINS, PLAYER_WHITE_WINS, DRAW }
private GameState currentState = GameState.RUNNING;
private int[][] board = new int[BOARD_SIZE][BOARD_SIZE]; // 0=空,1=黑,2=白
private int currentPlayer = BLACK; // 黑方先行
private List<int[]> winPositions = new ArrayList<>(); // 存储获胜五子坐标,用于高亮
makeMove(int row, int col)方法严格校验:
public boolean makeMove(int row, int col) {
if (currentState != GameState.RUNNING) return false;
if (row < 0 || row >= BOARD_SIZE || col < 0 || col >= BOARD_SIZE) return false;
if (board[row][col] != EMPTY) return false;
board[row][col] = currentPlayer;
WinResult result = checkWin(row, col);
if (result.isWin()) {
currentState = (currentPlayer == BLACK) ? GameState.PLAYER_BLACK_WINS : GameState.PLAYER_WHITE_WINS;
winPositions = result.getPositions();
} else if (isBoardFull()) {
currentState = GameState.DRAW;
} else {
currentPlayer = (currentPlayer == BLACK) ? WHITE : BLACK;
}
return true;
}
这里的关键设计是:
- 前置校验链:坐标越界→位置非空→游戏未结束,层层拦截;
- 状态驱动:currentState决定后续行为,checkWin只负责计算,不改变状态;
- 赢局坐标缓存:winPositions存储五子坐标,为后续BoardPanel高亮获胜连线提供数据支撑(虽当前版本未实现,但架构已预留)。
踩过的坑:早期版本用
boolean gameOver标志位,结果在快速连点时出现“黑方赢了但白方还能落子”的bug。根源是actionPerformed事件未排队,多线程竞争同一变量。改成枚举状态机后,makeMove返回false即丢弃无效点击,彻底解决。
3.3 GameController:事件绑定与用户体验闭环
GameController是Swing事件流的中枢,其构造函数完成三件事:
public GameController(BoardPanel boardPanel, JLabel statusLabel, JButton resetButton) {
this.boardPanel = boardPanel;
this.statusLabel = statusLabel;
this.resetButton = resetButton;
// 绑定棋盘点击事件
boardPanel.addMouseListener(new MouseAdapter() {
@Override
public void mousePressed(MouseEvent e) {
if (gameEngine.getGameState() != GameState.RUNNING) return;
Point p = e.getPoint();
int col = (p.x - boardPanel.getOffsetX()) / boardPanel.getCellSize();
int row = (p.y - boardPanel.getOffsetY()) / boardPanel.getCellSize();
if (gameEngine.makeMove(row, col)) {
boardPanel.repaint();
updateStatus();
playSound("move.wav"); // 音效预留接口
}
}
});
// 绑定重置按钮
resetButton.addActionListener(e -> {
gameEngine.reset();
boardPanel.repaint();
updateStatus();
statusLabel.setText("游戏已重置,黑方先行");
});
}
updateStatus()方法动态更新状态栏:
private void updateStatus() {
switch (gameEngine.getGameState()) {
case RUNNING:
statusLabel.setText("轮到" + (gameEngine.getCurrentPlayer() == BLACK ? "黑方" : "白方") + "落子");
break;
case PLAYER_BLACK_WINS:
statusLabel.setText("黑方获胜!点击重置继续");
JOptionPane.showMessageDialog(null, "黑方获胜!", "游戏结束", JOptionPane.INFORMATION_MESSAGE);
break;
case PLAYER_WHITE_WINS:
statusLabel.setText("白方获胜!点击重置继续");
JOptionPane.showMessageDialog(null, "白方获胜!", "游戏结束", JOptionPane.INFORMATION_MESSAGE);
break;
case DRAW:
statusLabel.setText("平局!点击重置继续");
JOptionPane.showMessageDialog(null, "平局!", "游戏结束", JOptionPane.INFORMATION_MESSAGE);
break;
}
}
实操心得:
mousePressed而非mouseClicked,因为后者在拖拽时可能不触发;getOffsetX/Y和getCellSize从BoardPanel获取,确保坐标计算与绘制逻辑一致;JOptionPane使用INFORMATION_MESSAGE而非PLAIN_MESSAGE,保证图标可见。这些细节让交互“丝滑”,而不是“卡顿”。
4. 构建与部署:从源码到Five.jar,绕不开的五个关键步骤
4.1 依赖管理:Weblaf jar包的嵌入式打包方案
Maven项目通常用<scope>compile</scope>管理依赖,但本项目为纯IDEA环境,采用手动嵌入式打包。关键步骤:
1. 将weblaf-complete-1.28.jar复制到项目根目录;
2. 在IDEA中,File → Project Structure → Libraries,点击+号添加该jar包;
3. File → Project Structure → Artifacts,点击+ → JAR → From modules with dependencies;
4. 在Output Layout中,展开WEB-INF/lib,确认weblaf-complete-1.28.jar已包含;
5. 最关键的一步:在MANIFEST.MF编辑框中,手动添加:
Main-Class: com.MainApp
Class-Path: weblaf-complete-1.28.jar
注意:Class-Path值必须与jar包文件名完全一致,且路径为相对路径(根目录下)。
常见问题:生成的jar双击无反应。排查顺序:①用
jar -tf Five.jar | grep MANIFEST确认MANIFEST.MF存在;②用more META-INF/MANIFEST.MF检查Main-Class拼写;③用java -jar Five.jar命令行运行,看报错信息——90%是Class-Path路径错误或Main-Class类名未用完整包名。
4.2 资源文件打包:截图与配置文件的路径陷阱
项目中的1.jpg、2.jpg、3.jpg和setup.dat不是放在src下,而是放在resources目录(与src同级)。这样做的原因是:getClass().getResource("/1.jpg")会从classpath根目录查找,而IDEA默认将resources目录标记为“Resources Root”。若误放src下,则路径需写为/com/1.jpg,极易出错。
setup.dat内容示例:
# 五子棋配置文件
board.size=15
win.length=5
sound.enabled=true
theme=weblaf
读取代码:
Properties props = new Properties();
try (InputStream is = MainApp.class.getClassLoader().getResourceAsStream("setup.dat")) {
props.load(is);
BOARD_SIZE = Integer.parseInt(props.getProperty("board.size", "15"));
} catch (Exception e) {
BOARD_SIZE = 15; // 默认值兜底
}
注意:
getResourceAsStream返回null时不要抛异常,用默认值降级,保证程序健壮性。我在测试时故意删掉setup.dat,程序仍能启动,只是用默认配置——这才是生产级思维。
4.3 编译输出目录:out/production/的真相与替代方案
out/production/是IDEA默认编译输出路径,但它的存在常被误解为“必须保留”。实际上,只要Project Structure → Modules → Sources中正确设置了src为源码根目录,IDEA会自动将.class文件输出到out下。对于交付,我们更推荐:
- 清理out目录(Build → Clean Project);
- 执行Build → Build Artifacts → Build生成Five.jar;
- 删除整个out目录——因为jar包已包含全部class,out只是中间产物。
实操建议:在
README.md中明确写出构建命令:
1. 确保weblaf-complete-1.28.jar在项目根目录
2. IDEA中:Build → Build Artifacts → Build
3. 生成的Five.jar位于out/artifacts/Five_jar/
避免学生在out/production/里翻找class文件,徒增困惑。
5. 常见问题与排查技巧实录:那些让我熬夜调试的“幽灵Bug”
5.1 问题速查表:高频故障与一招解决
| 问题现象 | 根本原因 | 解决方案 | 验证方式 |
|---|---|---|---|
| 双击Five.jar闪退,无任何提示 | MANIFEST.MF中Main-Class类名错误(如写成MainApp而非com.MainApp) | 用jar -xf Five.jar解压,检查META-INF/MANIFEST.MF内容 | java -cp Five.jar com.MainApp命令行运行 |
| 界面显示空白,只有灰色背景 | BoardPanel未重写getPreferredSize(),导致pack()计算尺寸为0 | 在BoardPanel构造函数末尾添加setPreferredSize(new Dimension(600, 600)) | 运行后查看窗口尺寸是否正常 |
| 落子后棋子不显示 | boardPanel.repaint()未触发,或paintComponent中未调用super.paintComponent(g) | 检查GameController中repaint()调用位置;确认paintComponent第一行是super.paintComponent(g) | 在paintComponent开头加System.out.println("repaint called") |
| 胜负判断总是失败 | checkWin扫描方向步长错误(如主对角线写成dr=1,dc=1但循环未同步检查r和c边界) | 用System.out.printf("scan at %d,%d: count=%d\n", row, col, count)打印各方向计数 | 在makeMove后立即打印board二维数组状态 |
| Weblaf主题未生效,仍是Windows原生样式 | WebLookAndFeel.install()未在SwingUtilities.invokeLater中调用 | 将WebLookAndFeel.install()移到main方法最开头,且确保在new JFrame()之前 | 运行后观察按钮是否圆角、是否有阴影 |
5.2 独家避坑技巧:来自六届实训的血泪总结
技巧1:用“坐标可视化”代替盲猜
在BoardPanel.mousePressed中加入:
System.out.printf("Mouse clicked at (%d,%d), grid pos=(%d,%d)\n",
e.getX(), e.getY(), row, col);
当发现点击位置与落子位置偏差时,立刻知道是offsetX/Y计算错误,而非逻辑问题。
技巧2:胜负检测的“断点黄金法则”
在checkWin方法开头加断点,手动在board数组中设置5颗黑子(如board[7][3]=board[7][4]=...=BLACK),然后单步执行,观察scanHorizontal返回值是否为5。这比“等AI对手下出五子”高效100倍。
技巧3:Weblaf的“主题热加载”调试法
在MainApp.main()中,WebLookAndFeel.install()后加:
System.out.println("Current L&F: " + UIManager.getLookAndFeel().getClass().getName());
确认输出为com.alee.laf.WebLookAndFeel,而非javax.swing.plaf.metal.MetalLookAndFeel。
技巧4:Jar包依赖的“反向验证”
用jar -tf Five.jar | grep weblaf确认jar包内确实包含weblaf-complete-1.28.jar;再用jar -tf Five.jar | grep MainApp确认com/MainApp.class存在。两手验证,杜绝打包遗漏。
最后分享一个小技巧:如果学生交来的作业jar包无法运行,我第一件事不是看代码,而是用
file Five.jar命令检查文件类型——曾遇到过学生把.zip文件重命名为.jar,导致java -jar直接报错。真正的交付,始于对每一个文件字节的敬畏。
6. 扩展可能性:从课堂作业到真实产品的演进路径
这个五子棋项目不是终点,而是Swing能力的起点。基于现有架构,你可以用不到200行代码实现以下扩展:
- 悔棋功能:在
GameEngine中增加Stack<MoveRecord>记录每次落子坐标和玩家,undo()方法弹出栈顶并恢复board状态; - 计时器:在
GameController中添加Timer,每秒更新JLabel,超时自动切换玩家; - AI对战:实现
SimpleAI类,makeMove()方法随机选择空位,或用极小化极大算法(Minimax)评估落子价值; - 战绩统计:用
Properties类读写score.dat,记录黑胜/白胜/平局次数,重启不丢失; - 皮肤切换:在
MainApp中添加JComboBox,选项对应不同UIManager.setLookAndFeel()调用,实现Weblaf/FlatLaf/Nimbus一键切换。
我个人在实际使用中发现,学生最容易卡在“如何让AI不作弊”上。解决方案是:
SimpleAI.makeMove()先调用gameEngine.getValidMoves()获取所有空位,再用Random.nextInt(validMoves.size())随机选一个——这样既简单,又符合真实博弈逻辑。记住,好的教学项目,不是功能越多越好,而是每个功能都能成为理解Swing核心机制的钥匙:事件、绘图、状态、布局、资源管理。当你能从这个五子棋出发,独立写出一个记事本、计算器或简易画图工具时,Swing对你而言,就不再是API文档里的冰冷条目,而是手中可塑的 clay。
简介:直接双击就能玩的Java五子棋桌面程序(Five.jar),界面用Weblaf库美化,操作流畅。每落一子,自动在横向、纵向、主对角线(左上→右下)、副对角线(右上→左下)四个方向分别扫描,实时判断是否达成连续五子胜出。源码结构清晰,src/com/下存放全部Java类,含完整游戏逻辑与UI交互代码;out/production/为编译输出目录;weblaf-complete-1.28.jar已内置,无需额外配置依赖。配套1.jpg、2.jpg、3.jpg三张实际运行截图,直观展示开局、中盘和胜负提示界面。附LICENSE开源协议、README.md使用说明、setup.dat构建参数及.idea相关配置文件,适合Java初学者理解Swing事件驱动机制,也适合作为高校Java课程设计交付材料或Swing图形界面开发入门参考。
589



被折叠的 条评论
为什么被折叠?



