2025-12-16,主要是针对2023年题目
使用 menu bar
好的,要在 MainWindow 中实现文件读入功能,我们需要完成以下工作:
- 在头文件 (.h) 中声明用于存储数据的变量(M, N, 地图数据, 阈值)以及打开文件的槽函数。
- 在源文件 (.cpp) 中实现菜单栏/按钮的创建。
- 在源文件 (.cpp) 中实现具体的文件读取逻辑。
以下是完整的代码实现方案。
1. 修改 mainwindow.h (必须先加这个)
你需要添加存储地图数据的成员变量,否则读进来的数据没地方放。
#ifndef MAINWINDOW_H
#define MAINWINDOW_H
#include <QMainWindow>
#include <QKeyEvent>
#include <vector>
class MainWindow : public QMainWindow
{
Q_OBJECT
public:
MainWindow(QWidget *parent = nullptr);
~MainWindow();
protected:
void keyPressEvent(QKeyEvent *event) override;
void paintEvent(QPaintEvent *event) override;
private slots:
// 这是我们要实现的打开文件的槽函数
void openFile();
private:
void showResult();
void transferMatrix();
void findPath();
// --- 新增:用于存储题目的数据 ---
int M; // 行数
int N; // 列数
int requiredSafety; // 题目要求的安全程度阈值 S
// 存储原始地图:0是空地,1是火堆
// 使用 vector<vector<int>> 是最方便的
std::vector<std::vector<int>> rawMap;
// 标记是否已经加载了文件(防止在 paintEvent 中画空数据导致崩溃)
bool isLoaded = false;
// 这些是后续功能需要的,先占个位
bool showSafetyValues = false; // 是否显示数值(T键)
std::vector<std::vector<int>> safetyMap; // 存储计算后的安全值矩阵
};
#endif // MAINWINDOW_H2. 修改 mainwindow.cpp
请将你的 mainwindow.cpp 修改为如下内容。主要变动在 头文件引用、构造函数 和 新增的 openFile 函数。
#include "mainwindow.h"
#include <iostream>
#include <string>
#include <sstream>
// --- 新增的头文件 ---
#include <QMenuBar> // 用于创建菜单栏
#include <QMenu> // 用于创建菜单
#include <QAction> // 用于创建菜单项
#include <QFileDialog> // 用于打开文件选择框
#include <QFile> // 用于读取文件
#include <QTextStream> // 用于解析文本流
#include <QMessageBox> // 用于报错弹窗
#include <QDebug> // 用于调试打印
MainWindow::MainWindow(QWidget *parent)
: QMainWindow(parent)
{
this->resize(800, 600);
this->setWindowTitle("Find Path!");
// --- 1. 初始化变量 ---
M = 0;
N = 0;
requiredSafety = 0;
isLoaded = false;
showSafetyValues = false;
// --- 2. 创建菜单栏 (题目要求:通过按钮或Menu Bar读入) ---
// 创建 "File" 菜单
QMenu *fileMenu = menuBar()->addMenu("File");
// 创建 "Open" 动作
QAction *openAction = new QAction("Open Map File", this);
fileMenu->addAction(openAction);
// --- 3. 连接信号槽 ---
// 当点击 "Open" 时,执行 openFile 函数
connect(openAction, &QAction::triggered, this, &MainWindow::openFile);
// 设置焦点策略,确保按键能被捕获
this->setFocusPolicy(Qt::StrongFocus);
}
// --- 新增:文件读取的具体实现 ---
void MainWindow::openFile()
{
// 1. 打开文件选择对话框
// 参数:父对象, 标题, 默认路径(./表示当前目录), 过滤器
QString fileName = QFileDialog::getOpenFileName(this, "Select Map File", "./", "Text Files (*.txt);;All Files (*)");
if (fileName.isEmpty()) {
return; // 用户点击了取消
}
// 2. 尝试打开文件
QFile file(fileName);
if (!file.open(QIODevice::ReadOnly | QIODevice::Text)) {
QMessageBox::warning(this, "Error", "Cannot open file!");
return;
}
// 3. 准备读取流
QTextStream in(&file);
// --- 4. 核心读取逻辑 (对应题目格式) ---
// 4.1 读取第一行 M N
if (!in.atEnd()) {
in >> M >> N;
}
// 每次读新文件前,必须清空旧数据!(防坑点)
rawMap.clear();
rawMap.resize(M, std::vector<int>(N));
// 还需要重置其他状态
safetyMap.clear(); // 之后算出来的安全矩阵也要清空
showSafetyValues = false; // 重置显示状态
// 4.2 读取 M 行 N 列的地图数据
for (int i = 0; i < M; ++i) {
for (int j = 0; j < N; ++j) {
if (in.atEnd()) break;
int val;
in >> val;
rawMap[i][j] = val;
}
}
// 4.3 读取最后一行:安全程度阈值 S
if (!in.atEnd()) {
in >> requiredSafety;
}
file.close();
// 5. 标记已加载,并刷新界面
isLoaded = true;
// 6. 【关键】读完文件后,把焦点还给主窗口,否则键盘按 T 没反应!
this->setFocus();
// 7. 触发 paintEvent 重绘界面 (虽然现在 paintEvent 还是空的)
qDebug() << "File loaded. M:" << M << " N:" << N << " Threshold:" << requiredSafety;
update();
}
void MainWindow::keyPressEvent(QKeyEvent *event){
// 检查按下了哪个键
if (event->key() == Qt::Key_T) {
qDebug() << "T pressed";
if (isLoaded) {
showSafetyValues = !showSafetyValues;
// 这里通常会调用 transferMatrix() 来计算安全值(如果还没算过)
// transferMatrix();
update(); // 触发 paintEvent 重绘
}
}
else if (event->key() == Qt::Key_P) {
qDebug() << "P pressed";
if (isLoaded) {
// findPath();
update();
}
}
else {
// 记得调用父类处理其他按键(如 Esc 退出等)
QMainWindow::keyPressEvent(event);
}
}
void MainWindow::showResult(){
// @TODO: write your code here
}
void MainWindow::transferMatrix(){
// @TODO: subproblem one
}
void MainWindow::findPath(){
// @TODO: subproblem two
}
void MainWindow::paintEvent(QPaintEvent *event){
// 简单的测试代码,证明文件读进来了
// 实际考试时这里要画格子
if (!isLoaded) return;
// 这里只是为了让你看到效果,不需要作为最终代码
// qDebug() << "Painting...";
}代码要点解析
-
数据清理 (
rawMap.clear()): 这是考试中最容易丢分的地方。如果不写这个,评测老师先打开sample1.txt(5x5),再打开sample2.txt(3x3),你的程序可能会崩溃或者数据残留。务必重新resize。 -
this->setFocus(): 放在openFile的最后一行。正如我们之前讨论的,这是为了确保你点完“打开”按钮后,键盘焦点能回到窗口上,让你后续能按 T 和 P。 -
QTextStream的使用: Qt 读取文本文件非常方便,in >> var会自动跳过空格和换行符,正好完美契合题目给出的输入格式(数字之间可能有空格,行之间有换行)。 -
menuBar()->addMenu(...): 这是一种纯代码创建菜单栏的方式,不需要拖拽 UI 文件。对于考试代码框架来说,这种方式通用性最强。
使用按钮
好的,为了实现**“在窗口左上角显示一个按钮,点击该按钮读入文件”**,我们需要使用 Qt 的标准控件 QPushButton。
这是一个非常典型的 GUI 编程需求。由于你的程序主要是用代码写界面的(而不是拖拽 UI 文件),我们直接在 MainWindow 的构造函数中创建这个按钮即可。
以下是具体的修改步骤:
第一步:修改头文件 mainwindow.h
你需要引入按钮的头文件,并在类中添加一个按钮指针(虽然在构造函数里定义局部变量也可以,但定义为成员变量更规范,方便以后修改按钮文字或禁用它)。
#ifndef MAINWINDOW_H
#define MAINWINDOW_H
#include <QMainWindow>
#include <QKeyEvent>
#include <vector>
#include <QPushButton> // <--- 1. 引入按钮头文件
class MainWindow : public QMainWindow
{
Q_OBJECT
public:
MainWindow(QWidget *parent = nullptr);
~MainWindow();
protected:
void keyPressEvent(QKeyEvent *event) override;
void paintEvent(QPaintEvent *event) override;
private slots:
void openFile();
private:
void showResult();
void transferMatrix();
void findPath();
// --- 按钮对象 ---
QPushButton *btnLoad; // <--- 2. 声明按钮指针
// --- 数据变量 ---
int M, N, requiredSafety;
std::vector<std::vector<int>> rawMap;
bool isLoaded = false;
bool showSafetyValues = false;
};
#endif // MAINWINDOW_H第二步:修改源文件 mainwindow.cpp
在构造函数中,我们将实例化这个按钮,设定它的位置(左上角),并连接到之前写好的 openFile 函数。
#include "mainwindow.h"
// ... 其他头文件保持不变 ...
MainWindow::MainWindow(QWidget *parent)
: QMainWindow(parent)
{
this->resize(800, 600);
this->setWindowTitle("Find Path!");
// 初始化变量
M = 0; N = 0; requiredSafety = 0;
isLoaded = false; showSafetyValues = false;
// ---------------------------------------------------------
// 新增代码:创建左上角的按钮
// ---------------------------------------------------------
// 1. 创建按钮对象
// 参数1: 按钮上显示的文字
// 参数2: 父窗口 (this),这非常重要,只有设为 this,按钮才会显示在这个窗口里
btnLoad = new QPushButton("Load Map", this);
// 2. 设置位置和大小 (左上角)
// setGeometry(x, y, width, height)
// (10, 10) 表示距离左边10像素,距离上边10像素,留点边距比较好看
btnLoad->setGeometry(10, 10, 100, 30);
// 3. 连接信号与槽
// 当点击按钮(clicked)时,执行 openFile 函数
connect(btnLoad, &QPushButton::clicked, this, &MainWindow::openFile);
// ---------------------------------------------------------
// 确保主窗口能接收键盘焦点
this->setFocusPolicy(Qt::StrongFocus);
}为什么这样写就能显示在左上角?
- 父子关系 (
this):new QPushButton("...", this)中的this告诉 Qt,这个按钮是MainWindow的“孩子”。在 Qt 中,子控件会自动显示在父窗口的客户区内。 - 坐标系统: Qt 的窗口坐标系原点
(0, 0)就在绘图区域的左上角。X轴向右增加,Y轴向下增加。 setGeometry: 我设置了(10, 10),这会让按钮稍微离左上角边缘有一点空隙,看起来不那么挤。如果你想要死死贴住左上角,可以写(0, 0, 100, 30)。
重要提醒:不要忘记 setFocus
当你点击这个按钮时,焦点一定会跑到按钮身上。
请再次检查你之前写的 openFile 函数,确保最后一句包含 this->setFocus()。如果你没写这一句,当你点完按钮加载完地图后,直接按 ‘T’ 键是不会有反应的,必须重新点一下空白处才行。
// 再次确认 mainwindow.cpp 中的 openFile 结尾是这样的:
void MainWindow::openFile() {
// ... 前面读文件的代码 ...
file.close();
isLoaded = true;
update();
// !!! 这一句至关重要,特别是用了按钮之后 !!!
// 读完文件,强行把焦点从按钮身上抢回给主窗口
this->setFocus();
}按照这样修改,你的程序左上角就会出现一个“Load Map”的按钮,点击即可触发读入逻辑。