2025-12-16,主要是针对2023年题目

使用 menu bar

好的,要在 MainWindow 中实现文件读入功能,我们需要完成以下工作:

  1. 在头文件 (.h) 中声明用于存储数据的变量(M, N, 地图数据, 阈值)以及打开文件的槽函数。
  2. 在源文件 (.cpp) 中实现菜单栏/按钮的创建。
  3. 在源文件 (.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_H

2. 修改 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..."; 
}

代码要点解析

  1. 数据清理 (rawMap.clear()): 这是考试中最容易丢分的地方。如果不写这个,评测老师先打开 sample1.txt (5x5),再打开 sample2.txt (3x3),你的程序可能会崩溃或者数据残留。务必重新 resize

  2. this->setFocus(): 放在 openFile 的最后一行。正如我们之前讨论的,这是为了确保你点完“打开”按钮后,键盘焦点能回到窗口上,让你后续能按 T 和 P。

  3. QTextStream 的使用: Qt 读取文本文件非常方便,in >> var 会自动跳过空格和换行符,正好完美契合题目给出的输入格式(数字之间可能有空格,行之间有换行)。

  4. 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);
}

为什么这样写就能显示在左上角?

  1. 父子关系 (this): new QPushButton("...", this) 中的 this 告诉 Qt,这个按钮是 MainWindow 的“孩子”。在 Qt 中,子控件会自动显示在父窗口的客户区内。
  2. 坐标系统: Qt 的窗口坐标系原点 (0, 0) 就在绘图区域的左上角。X轴向右增加,Y轴向下增加。
  3. 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”的按钮,点击即可触发读入逻辑。