原始直接的做法
这是一道典型的 Qt 绘图题(paintEvent 的应用)。在 Qt 中,所有的自定义绘图逻辑都必须在 paintEvent 函数中实现。
为了拿到这 10 分,我们需要解决三个问题:
- 计算格子大小:根据窗口大小和地图行列数 (),动态计算每个格子应该多大。
- 坐标转换:把矩阵坐标 转换成屏幕像素坐标 。
- 绘制矩形:使用
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. 需要注意的细节 (拿分关键)
-
偏移量 (
topMargin): 一定要留出顶部的空间(代码中设为 50)。你的按钮在(10, 10)位置,如果你的地图从(0, 0)开始画,地图的第一行就会把按钮盖住,或者被按钮盖住,这在现场检查时会被扣印象分。 -
行列对应的坐标轴:
- 行 (i, M) 对应 Y 轴(垂直方向)。
- 列 (j, N) 对应 X 轴(水平方向)。
- 画图公式一定是
x = ... + j * width,y = ... + i * height。搞反了地图会旋转 90 度。
-
浮点数精度 (
double): 计算cellWidth时最好用double,画图时用QRectF。 如果全是整数运算(如800 / 3 = 266),3 个格子加起来只有798像素,屏幕右边和下边会有几像素的白边。用QRectF会自动处理这些亚像素偏差,让格子铺满界面。 -
数据保护: 开头那句
if (!isLoaded ...)非常重要。程序刚启动时,M和N都是 0,如果没有这个判断,除法availableWidth / N会导致 除以零错误 (Divide By Zero),程序直接崩溃。
4. 验证方式
- 运行程序。
- 点击左上角的
Load Map按钮。 - 选择样例文件
testcase1.txt。 - 如果你看到了一个红绿相间的网格矩阵,且红色位置对应文件里的
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,不要这里用这个,那里用那个!