2025-12-18

原始直接的做法

这是一道典型的 Qt 绘图题(paintEvent 的应用)。在 Qt 中,所有的自定义绘图逻辑都必须在 paintEvent 函数中实现。

为了拿到这 10 分,我们需要解决三个问题:

  1. 计算格子大小:根据窗口大小和地图行列数 (),动态计算每个格子应该多大。
  2. 坐标转换:把矩阵坐标 转换成屏幕像素坐标
  3. 绘制矩形:使用 QPainter 填充颜色。

以下是分步实现指南:

1. 核心逻辑分析

  • 绘图区域:你的窗口大小是 (或者动态变化),但上面还有个按钮。为了不遮挡按钮,建议让地图稍微往下移一点(比如 offsetY = 50)。
  • 自适应大小:为了让地图填满屏幕,格子宽度 ,格子高度 。通常为了保持格子是正方形,我们可以取 中较小的那个作为边长,或者直接拉伸(题目没说必须正方形,拉伸最简单)。

2. 代码实现 (mainwindow.cpp)

请找到 paintEvent 函数,填入以下代码。

#include <QPainter> // 必须引入这个头文件
 
void MainWindow::paintEvent(QPaintEvent *event)
{
    // 0. 如果还没读入文件,或者地图数据为空,直接返回,不画
    if (!isLoaded || M == 0 || N == 0) {
        return; 
    }
 
    // 1. 准备画笔
    QPainter painter(this);
    
    // 开启抗锯齿,让线条更平滑(可选,写了显得专业)
    painter.setRenderHint(QPainter::Antialiasing);
 
    // 2. 计算绘图参数
    // 留出顶部空间给按钮,比如 50 像素
    int topMargin = 50; 
    int leftMargin = 20; 
    
    // 计算可用区域的宽高
    int availableWidth = this->width() - 2 * leftMargin;
    int availableHeight = this->height() - topMargin - 20; // 底部留点边距
 
    // 计算每个格子的宽和高
    // 注意:要强转 double 避免整数除法丢失精度,然后再转回 int
    // 或者直接用浮点数坐标画,这里为了简单用整数
    double cellWidth = (double)availableWidth / N;
    double cellHeight = (double)availableHeight / M;
 
    // 3. 遍历地图矩阵进行绘制
    for (int i = 0; i < M; ++i) {      // i 是行号 (对应 y 轴)
        for (int j = 0; j < N; ++j) {  // j 是列号 (对应 x 轴)
            
            // --- 计算当前格子的屏幕坐标 ---
            // x = 左边距 + 列号 * 格子宽
            // y = 顶边距 + 行号 * 格子高
            int x = leftMargin + j * cellWidth;
            int y = topMargin + i * cellHeight;
 
            // --- 决定颜色 ---
            // rawMap[i][j] == 1 是火堆 (红色)
            // rawMap[i][j] == 0 是空地 (绿色)
            QColor cellColor;
            if (rawMap[i][j] == 1) {
                cellColor = Qt::red;
            } else {
                cellColor = Qt::green;
            }
 
            // --- 开始绘制 ---
            
            // 设置填充颜色
            painter.setBrush(QBrush(cellColor));
            // 设置边框颜色 (黑色边框能让格子区分更明显)
            painter.setPen(QPen(Qt::black));
 
            // 画矩形 (参数:x, y, 宽, 高)
            // 使用 QRectF 可以接受浮点数,绘制更精确,防止格子之间有缝隙
            painter.drawRect(QRectF(x, y, cellWidth, cellHeight));
 
            // --- (进阶预告) 如果按下了T键,在这里画文字 ---
            /*
            if (showSafetyValues) {
                painter.setPen(Qt::black); // 文字用黑色
                // 在矩形中央画文字
                painter.drawText(QRectF(x, y, cellWidth, cellHeight), 
                                 Qt::AlignCenter, 
                                 QString::number(safetyMap[i][j]));
            }
            */
        }
    }
}

3. 需要注意的细节 (拿分关键)

  1. 偏移量 (topMargin): 一定要留出顶部的空间(代码中设为 50)。你的按钮在 (10, 10) 位置,如果你的地图从 (0, 0) 开始画,地图的第一行就会把按钮盖住,或者被按钮盖住,这在现场检查时会被扣印象分。

  2. 行列对应的坐标轴

    • 行 (i, M) 对应 Y 轴(垂直方向)。
    • 列 (j, N) 对应 X 轴(水平方向)。
    • 画图公式一定是 x = ... + j * widthy = ... + i * height。搞反了地图会旋转 90 度。
  3. 浮点数精度 (double): 计算 cellWidth 时最好用 double,画图时用 QRectF。 如果全是整数运算(如 800 / 3 = 266),3 个格子加起来只有 798 像素,屏幕右边和下边会有几像素的白边。用 QRectF 会自动处理这些亚像素偏差,让格子铺满界面。

  4. 数据保护: 开头那句 if (!isLoaded ...) 非常重要。程序刚启动时,MN 都是 0,如果没有这个判断,除法 availableWidth / N 会导致 除以零错误 (Divide By Zero),程序直接崩溃。

4. 验证方式

  1. 运行程序。
  2. 点击左上角的 Load Map 按钮。
  3. 选择样例文件 testcase1.txt
  4. 如果你看到了一个红绿相间的网格矩阵,且红色位置对应文件里的 1,绿色对应 0,恭喜你,这 10 分到手了。

你可以把上面的代码复制到你的 MainWindow::paintEvent 中,然后编译运行试试。

优化

QRect

 QRectF cellRect(x, y, cellWidth, cellHeight); // 把区域存下来,方便后面复用

弄一个QRect,这个f表示支持浮点数绘图

color判定

这个东西应该和绘制ui,尤其是paintevent解耦合,paintevent只管读取一个颜色去画得了,至于颜色哪里来,这个应该是逻辑方面的事

std::vector<std::vector<QColor>> colorMap;

  • 于是有了这个颜色容器 void MainWindow::updateColor()
  • 于是就有了这个函数
  • 用于更新颜色,基于已经存储的信息

那些数组的初始化

在openFile函数里面进行,因为需要读取到一些东西才能初始化 比如这样

    safetyMap.clear();
    safetyMap.resize(M, std::vector<int>(N));

不要忘记std::

尤其是vector,还有一些函数,比如

int dis = std::abs(xxxx)

作用范围

            QString text;
            if(showSafetyValues == false)
                text = QString::number(rawMap[i][j]);
            else
                text = QString::number(safetyMap[i][j]);
        

这样子是可以的,但是不能!

        if(showSafetyValues == false)
            QString text = QString::number(rawMap[i][j]);
        else
            QString text = QString::number(safetyMap[i][j]);

但它的作用域只在各自的 if/else 语句块内部。出了 if/else 语句块后,text 变量就不存在了

update()

界面绘制不会自动更新,需要手动调用update函数。所以keypressevent后改变了某些值,需要update以下来让界面更新

数组初始化

ispath.clear();
ispath.resize(M, std::vector<bool>(N, false));

奇怪的点总结

妈的

  • 如果一个容器初始化过了,然后又clear,会出现很诡异的问题!
  • 行列xy的处理
    • 反正就是要记清楚,自己用的是行列还是xy,不要这里用这个,那里用那个!