news 2026/7/15 4:31:37

C++与Qt实战:从零开发Windows画图工具,掌握桌面应用开发核心

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
C++与Qt实战:从零开发Windows画图工具,掌握桌面应用开发核心

1. 项目概述与核心价值

最近在整理自己的技术栈时,发现很多朋友对用C++和Qt做桌面图形应用很感兴趣,尤其是想复刻一些经典的软件来练手。Windows自带的画图工具,无疑是一个绝佳的入门项目。它界面直观,功能模块清晰,涵盖了从基本的图形绘制、颜色填充到图像编辑的完整流程。用C++和Qt来实现它,不仅能让你系统地掌握Qt图形视图框架、事件处理、资源管理等核心知识,更能深入理解桌面应用从界面布局到业务逻辑的全链路开发。这个项目不像一个简单的“Hello World”,它涉及的状态管理、绘图算法和用户交互复杂度适中,做完之后,你对如何架构一个中等规模的桌面应用会有非常扎实的体会。无论你是想巩固C++面向对象设计,还是希望将Qt从“会用”提升到“精通”,这个“绘图神器”的开发之旅都值得一试。

2. 开发环境搭建与项目初始化

2.1 Qt与C++工具链选择

工欲善其事,必先利其器。首先需要搭建开发环境。对于Windows平台,我推荐使用Qt 5.15 LTSQt 6.2及以上版本,它们在稳定性和功能上都有很好的平衡。安装时,务必勾选对应你编译器版本的组件。如果你使用MSVC,就安装MSVC2019 64-bit;如果使用MinGW,就安装MinGW 64-bit。我个人更倾向于MSVC,因为与Visual Studio生态结合更紧密,调试体验更好。IDE方面,Qt Creator是官方首选,它对Qt项目的支持是无缝的,包括.ui文件的可视化编辑、信号槽的自动连接等,能极大提升开发效率。当然,如果你习惯使用Visual Studio,安装“Qt VS Tools”插件也是完全可行的。

注意:请务必从Qt官网或清华大学等国内镜像站下载安装程序。安装路径不要包含中文或空格,避免一些不必要的路径解析问题。

2.2 创建Qt Widgets Application项目

打开Qt Creator,点击“New Project”,选择“Application” -> “Qt Widgets Application”。项目名称可以定为“Painter”或“MyPaint”。在“Kit Selection”页面,确保勾选你安装的桌面编译套件。在类信息页面,基类选择“QMainWindow”,这样我们就创建了一个带菜单栏、工具栏和状态栏的主窗口应用框架。创建完成后,你会得到一个包含main.cppmainwindow.hmainwindow.cppmainwindow.ui文件的项目。.ui文件是Qt Designer的界面文件,我们可以通过拖拽控件的方式快速构建界面。

2.3 项目目录结构规划

一个清晰的项目结构有助于后期维护。我建议在项目根目录下创建以下几个文件夹:

  • src/: 存放核心的C++源文件和头文件(如绘图工具类、图形项类)。
  • ui/: 存放自定义的.ui界面文件(如果后续有对话框等)。
  • resources/: 存放图像、图标等资源文件。我们需要在这里创建一个icons子文件夹,存放各种工具的图标。
  • docs/: 存放项目说明或设计文档。

接着,我们需要创建Qt的资源文件(.qrc)。在Qt Creator中右键项目 -> “Add New...” -> “Qt” -> “Qt Resource File”。将其命名为resources.qrc。然后,在资源文件中添加前缀(如/icons),再把我们准备好的工具图标文件(如pen.png,line.png,rect.png等)添加进来。这样,在代码中就可以通过:/icons/pen.png的路径来引用图标了,这些资源在编译后会被打包到可执行文件中。

3. 主界面设计与布局管理

3.1 菜单栏与工具栏设计

主窗口的界面是用户交互的核心。我们首先利用Qt Designer来设计mainwindow.ui。从左侧控件栏拖拽一个QMenuBar到窗口顶部,创建标准的“文件(F)”、“编辑(E)”、“查看(V)”、“帮助(H)”等菜单。

  • 文件菜单:包含“新建”、“打开”、“保存”、“另存为”、“退出”等动作(QAction)。
  • 编辑菜单:包含“撤销”、“重做”、“复制”、“粘贴”、“全选”等。
  • 查看菜单:可以控制工具栏、颜色栏的显示/隐藏。
  • 帮助菜单:放置“关于”动作。

接下来是工具栏。拖拽一个QToolBar到菜单栏下方。我们需要为不同的绘图工具创建动作并添加到工具栏。例如,选择工具、画笔、直线、矩形、椭圆、文本、橡皮擦、填充桶等。每个动作都需要设置图标(使用我们资源文件中的图标)、可选中(checkable属性设为true)以及快捷键。为了实现工具间的互斥选择(即同一时间只能选中一个绘图工具),我们将这些动作添加到一个QActionGroup中。

3.2 中央绘图区域与状态栏

绘图区域是整个软件的核心显示区。我们使用Qt Graphics View Framework来实现。在UI设计器中,从“Containers”中找到QGraphicsView,拖拽到中央区域。将其对象名设为graphicsViewQGraphicsView是一个视图组件,它负责显示QGraphicsScene场景。场景(Scene)才是真正管理所有图形项(直线、矩形等QGraphicsItem)的容器。我们将在代码中创建QGraphicsScene并设置给这个View。

在窗口底部,我们添加一个QStatusBar。状态栏可以用来实时显示一些信息,比如当前鼠标在画布上的坐标(X, Y)、当前选择的线条粗细、提示信息等。我们可以通过statusBar()->showMessage(“就绪”)来设置临时信息,或者创建永久部件(如QLabel)来显示坐标。

3.3 颜色选择与属性面板

Windows画图工具的左侧是工具箱,右侧是颜色盒。我们可以用QDockWidget(停靠窗口)来实现类似的可浮动面板。创建两个QDockWidget,一个作为工具箱,一个作为颜色/属性面板。

  • 工具箱Dock:里面放置一个QToolBar,并设置为垂直方向,将我们之前创建的绘图工具动作添加进去。也可以使用QListWidget配合图标模式来呈现,看个人喜好。
  • 属性面板Dock:这里放置颜色选择器和线条粗细选择器。对于颜色,可以使用QColorDialog的标准按钮,也可以自己用QToolButton网格排列常用色块。线条粗细可以用QComboBox(下拉选择)或QSpinBox(数字微调框)来实现。

布局的关键是确保当用户拖动或关闭这些Dock时,主绘图视图能自动调整大小以填充剩余空间。这可以通过在mainwindow.ui中设置中心部件和Dock的布局属性来实现。

4. 绘图引擎核心:Graphics View框架深度解析

4.1 Scene, View与Item的关系

Qt的Graphics View框架采用MVC(模型-视图-控制器)的变体,是开发此类绘图软件的基石,必须彻底理解。

  • QGraphicsScene(场景):它是所有图形项的容器和管理者,代表一个逻辑上的二维画布。它不负责显示,但负责管理所有QGraphicsItem的生命周期、碰撞检测、事件传播等。我们可以把它想象成一个无限大的虚拟画布。
  • QGraphicsView(视图):它是一个视口组件,用于显示Scene的某一部分。它支持缩放和旋转。我们的主窗口中央那个graphicsView就是它。一个Scene可以被多个View观察。
  • QGraphicsItem(图形项):这是所有可绘制图形的基类。直线、矩形、椭圆、路径、文本甚至自定义图形,都是它的子类。Item被添加到Scene中,由Scene管理。

MainWindow的构造函数中,我们需要初始化Scene和View:

// mainwindow.cpp MainWindow::MainWindow(QWidget *parent) : QMainWindow(parent) , ui(new Ui::MainWindow) { ui->setupUi(this); m_scene = new QGraphicsScene(this); // 设置一个初始的Scene矩形区域,例如(0, 0, 800, 600) m_scene->setSceneRect(0, 0, 800, 600); ui->graphicsView->setScene(m_scene); // 设置视图的背景色为白色,模拟画布 ui->graphicsView->setBackgroundBrush(Qt::white); // 关闭滚动条,让画布自适应 ui->graphicsView->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff); ui->graphicsView->setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOff); }

4.2 自定义图形项与绘制逻辑

Qt内置了QGraphicsLineItem,QGraphicsRectItem,QGraphicsEllipseItem,QGraphicsPathItem,QGraphicsTextItem等。对于画笔(自由曲线)和橡皮擦,我们需要使用QGraphicsPathItem。但为了更灵活地控制图形的属性(如线条颜色、填充色、线宽)以及实现序列化保存,我强烈建议为每种图形创建自定义的Item类,继承自QGraphicsPathItem(对于形状)或QAbstractGraphicsShapeItem

以自定义矩形项为例:

// myrectitem.h #ifndef MYRECTITEM_H #define MYRECTITEM_H #include <QGraphicsRectItem> class MyRectItem : public QGraphicsRectItem { public: MyRectItem(const QRectF &rect, QGraphicsItem *parent = nullptr); // 重写paint函数,以实现自定义绘制效果(如虚线边框、抗锯齿) void paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget) override; // 自定义属性 void setPenStyle(Qt::PenStyle style); Qt::PenStyle penStyle() const; // 序列化相关 QJsonObject toJson() const; static MyRectItem* fromJson(const QJsonObject &json); private: Qt::PenStyle m_penStyle; }; #endif // MYRECTITEM_H

paint函数中,我们可以调用QPainter进行更精细的绘制。对于自由曲线的画笔,我们需要创建一个MyPathItem类,在其内部维护一个QPainterPath对象。当鼠标移动时,将点连接到路径中,并更新Item的路径。

4.3 坐标系统与变换理解

Graphics View中有三个关键的坐标系统:

  1. Item坐标:每个Item自身的坐标系,原点通常是其中心或左上角(取决于Item)。绘制图形时使用的坐标是Item坐标。
  2. Scene坐标:Scene的全局坐标系。所有Item在Scene中都有一个位置(通过setPos设置),这个位置是Item的本地原点在Scene坐标中的位置。
  3. View坐标:也就是窗口部件的像素坐标。

它们之间的转换至关重要。例如,当鼠标在QGraphicsView上点击时,我们获得的是View坐标。需要通过ui->graphicsView->mapToScene(event->pos())将其转换为Scene坐标,这个坐标才是我们创建或定位图形项时使用的坐标。

实操心得:在处理鼠标事件绘制图形时,一定要分清坐标系统。一个常见的错误是直接用View坐标去设置Item的矩形,导致图形位置错乱。始终记住:在Scene中添加或修改Item,使用Scene坐标。

5. 绘图工具的实现与事件处理

5.1 工具管理器的设计

我们需要一个中心化的工具管理器来协调当前激活的绘图工具。创建一个ToolManager类,它采用单例模式或作为MainWindow的成员。它维护一个工具类型的枚举(如ToolType { Select, Pen, Line, Rectangle, Ellipse, Text, Eraser, Fill })和当前选中的工具。当用户在工具栏点击不同工具时,调用ToolManager::setCurrentTool()

更重要的是,工具管理器需要将QGraphicsView的鼠标事件(按下、移动、释放)分发给当前激活的工具对象进行处理。我们可以为每种工具设计一个独立的处理类(如PenTool,LineTool),它们有统一的接口,例如void mousePressEvent(QGraphicsSceneMouseEvent *event)

5.2 直线、矩形、椭圆的绘制实现

这些基本形状的绘制逻辑类似,遵循“按下-移动-释放”的流程。以LineTool为例:

  1. mousePressEvent:记录鼠标按下的起始点(Scene坐标),并创建一个临时的QGraphicsLineItem(或自定义的MyLineItem)添加到Scene中。此时线条的起点和终点都是按下的点,所以初始长度为0。
  2. mouseMoveEvent:随着鼠标移动,更新临时线条Item的终点坐标为当前鼠标的Scene坐标。这样就能实现拖拽绘制时的实时预览效果。
  3. mouseReleaseEvent:鼠标释放,绘制完成。此时,我们应该用最终的起点和终点创建一个正式的、永久的图形项(MyLineItem),并将其添加到Scene中,然后删除那个临时的预览Item。同时,将这个操作记录到撤销/重做栈中。

矩形和椭圆的实现完全类似,只是创建的临时Item类型换成QGraphicsRectItemQGraphicsEllipseItem。关键在于计算矩形:QRectF(startPoint, currentPoint),需要保证矩形是正的(即无论从哪个方向拖拽,宽高都为正值),这可以通过QRectF::normalized()或手动比较坐标来实现。

5.3 自由曲线(画笔)与橡皮擦的实现

自由曲线(画笔):使用MyPathItem(继承自QGraphicsPathItem)。在PenToolmousePressEvent中,创建一个新的MyPathItem,并初始化一个QPainterPath,将路径的起点移动到按下的点。在mouseMoveEvent中,不断将当前点以lineTo的方式加入到路径中,并调用item->setPath()更新。为了平滑曲线,可以使用cubicTo(三次贝塞尔曲线),但这需要更复杂的逻辑来计算控制点。一个简单的优化是,不要每个鼠标移动事件点都lineTo,可以采样,或者使用QPainterPath::quadTo

橡皮擦:橡皮擦的实现有两种思路:

  1. 覆盖绘制:将当前绘图工具切换到一种特殊的“画笔”,其颜色与画布背景色相同,线宽较粗。这本质上还是在画图,只是画的是背景色。这种方法简单,但无法真正“擦除”已存在的图形项,只是覆盖了它们。
  2. 碰撞检测擦除:这才是真正的擦除。在EraserToolmouseMoveEvent中,以当前鼠标位置为中心,创建一个小的矩形或圆形区域(QRectFQPainterPath)。然后,调用m_scene->items(eraseArea)获取所有与该区域相交的图形项。对于获取到的每一个QGraphicsItem,判断其类型。如果是MyPathItem(自由曲线),我们可以将擦除区域与路径进行布尔运算(QPainterPath::intersected),从原路径中减去相交部分,如果路径变空则删除该项。对于其他基本图形项(直线、矩形),可以直接删除。这种方法更真实,但实现复杂度高,特别是对于路径的布尔运算。

对于初学者,我建议先从第一种“覆盖绘制”方式实现橡皮擦,以快速完成功能闭环。第二种方式可以作为后续的进阶优化。

5.4 文本工具与颜色填充工具

文本工具:在TextToolmousePressEvent中,在点击位置创建一个QGraphicsTextItem,并设置其初始文本(如“Text”),同时使其进入编辑模式(setTextInteractionFlags(Qt::TextEditorInteraction)并调用setFocus())。用户可以直接输入文字。难点在于,当用户在画布其他位置点击时,需要结束当前文本项的编辑。这可以通过在ToolManagerMainWindow中监听Scene的selectionChanged信号,或者为文本项失去焦点时设置一个标志来处理。

颜色填充工具(油漆桶):这是一个经典的算法问题——泛洪填充(Flood Fill)。Qt没有直接提供此功能的API,需要自己实现。核心逻辑是:获取鼠标点击点处的像素颜色(对于QGraphicsScene,我们需要先将其渲染到一个QImage上,或者遍历所有图形项计算该点的颜色,这非常复杂)。更实用的方法是限制填充对象:只允许对单一的、封闭的QGraphicsPathItem(如矩形、椭圆、多边形)进行内部填充。实现步骤:

  1. 当使用填充工具点击时,通过m_scene->itemAt(scenePos, QTransform())获取被点击的图形项。
  2. 检查该图形项是否是QAbstractGraphicsShapeItem的子类(即有填充区域)。
  3. 如果是,则直接调用item->setBrush(QBrush(fillColor))改变其画刷颜色。

这虽然不是严格的像素级填充,但对于一个绘图软件的核心学习项目来说,已经足够体现功能并简化了实现难度。真正的像素填充涉及到图像处理,可以考虑作为扩展功能,将Scene的指定区域渲染为QImage,然后使用扫描线算法进行填充。

6. 高级功能实现:撤销重做与文件持久化

6.1 基于QUndoStack的撤销/重做机制

一个专业的软件必须支持撤销(Undo)和重做(Redo)。Qt提供了强大的QUndoStackQUndoCommand框架。我们的每一个绘图操作(添加一个图形、删除一个图形、修改图形属性)都应该封装成一个继承自QUndoCommand的命令。

例如,实现一个“添加图形”的命令:

// additemcommand.h #ifndef ADDITEMCOMMAND_H #define ADDITEMCOMMAND_H #include <QUndoCommand> #include <QGraphicsScene> #include <QGraphicsItem> class AddItemCommand : public QUndoCommand { public: AddItemCommand(QGraphicsItem *item, QGraphicsScene *scene, QUndoCommand *parent = nullptr); void undo() override; void redo() override; private: QGraphicsItem *m_item; QGraphicsScene *m_scene; bool m_isAdded; // 标记项是否已在场景中 }; #endif // ADDITEMCOMMAND_H // additemcommand.cpp AddItemCommand::AddItemCommand(QGraphicsItem *item, QGraphicsScene *scene, QUndoCommand *parent) : QUndoCommand(parent), m_item(item), m_scene(scene), m_isAdded(false) { setText("Add Item"); } void AddItemCommand::redo() { if (!m_isAdded) { m_scene->addItem(m_item); m_isAdded = true; } } void AddItemCommand::undo() { if (m_isAdded) { m_scene->removeItem(m_item); m_isAdded = false; } }

MainWindow中创建一个QUndoStack对象。每当完成一个绘图操作(如释放鼠标创建了一个新图形),就创建一个对应的命令(如AddItemCommand)并压入栈中(m_undoStack->push(command))。然后,将菜单栏“编辑”->“撤销”和“重做”动作的triggered信号连接到m_undoStackundo()redo()槽上即可。

6.2 图像文件的打开、保存与导出

打开与保存:我们需要支持常见的图像格式,如PNG、JPEG、BMP。Qt的QGraphicsSceneQGraphicsView没有直接的保存为图像文件的方法,但我们可以将Scene的内容渲染到一个QImage上,然后保存。

// 保存Scene为图片 void MainWindow::saveAsImage(const QString &fileName) { // 获取Scene的边界矩形 QRectF rect = m_scene->sceneRect(); // 创建一个与Scene等大的QImage,背景为白色 QImage image(rect.size().toSize(), QImage::Format_ARGB32); image.fill(Qt::white); QPainter painter(&image); painter.setRenderHint(QPainter::Antialiasing); // 将Scene渲染到QImage上 m_scene->render(&painter); // 保存图片 image.save(fileName); }

对于“打开”操作,我们可以使用QImageQPixmap加载图片文件,然后将其作为一个QGraphicsPixmapItem添加到Scene中。但需要注意的是,这会将整张图片作为一个图元,而不是可编辑的矢量图形。我们的绘图软件本质是矢量绘图(图形项是独立的),而打开位图会将其作为背景或一个整体对象。

序列化与自定义文件格式:为了保存可编辑的矢量图形信息(每个图形项的类型、位置、颜色、线宽等),我们需要定义自己的文件格式。最方便的是使用JSON或XML。我们可以为每种自定义图形项实现toJson()fromJson()方法(如前文MyRectItem示例)。保存时,遍历Scene中的所有项,将它们序列化为一个JSON数组,再写入文件。打开时,读取JSON文件,解析数组,根据类型标识符创建对应的图形项对象,并恢复其属性。

6.3 剪贴板操作与选择工具

选择工具:这是默认工具。当工具类型为Select时,我们需要允许用户通过点击或框选来选中图形项。QGraphicsView默认就支持用鼠标点击选择Item,并且可以通过拖拽选中的Item来移动它们。我们只需要确保在非选择工具下,View的选择功能被禁用(可以通过设置QGraphicsView::DragModeNoDrag,并拦截相关事件来实现)。

复制、剪切、粘贴:这些操作依赖于系统的剪贴板(QClipboard)和我们自定义的序列化格式。

  • 复制:获取当前选中的所有图形项,将它们序列化为一个自定义的MIME数据(例如,打包成JSON字符串),然后设置到剪贴板。
  • 剪切:先执行复制操作,然后删除当前选中的项(记得创建DeleteItemCommand加入撤销栈)。
  • 粘贴:从剪贴板读取我们自定义的MIME数据,反序列化生成一组新的图形项,将它们添加到Scene中(通常粘贴到视图中心或鼠标位置),并为这个添加操作创建撤销命令。

实现自定义MIME类型需要子类化QMimeData,重写formats()retrieveData()方法,或者更简单地,使用QClipboard::setText()将JSON字符串放到剪贴板,但这可能与其他软件不兼容。对于内部操作,这已经足够。

7. 界面美化、性能优化与调试

7.1 抗锯齿与高DPI屏幕适配

抗锯齿:为了使绘制的线条和图形边缘平滑,需要在几个地方开启抗锯齿。

  1. QGraphicsView上:view->setRenderHint(QPainter::Antialiasing);
  2. QGraphicsScene渲染到图像时:painter.setRenderHint(QPainter::Antialiasing);
  3. 在每个自定义图形项的paint()函数中:painter->setRenderHint(QPainter::Antialiasing);

高DPI适配:随着4K等高分辨率屏幕普及,Qt应用需要做好DPI适配。在main.cpp中,在创建QApplication之前,设置以下属性有很好的效果:

int main(int argc, char *argv[]) { // 启用高DPI缩放 QApplication::setAttribute(Qt::AA_EnableHighDpiScaling); // 使用高DPI图标(如果资源提供了) QApplication::setAttribute(Qt::AA_UseHighDpiPixmaps); QApplication a(argc, argv); MainWindow w; w.show(); return a.exec(); }

同时,确保你的图标资源有足够高的分辨率(例如,提供@2x,@3x倍图),Qt会自动根据缩放因子选择合适的图标。

7.2 绘制性能优化技巧

当图形项非常多时(比如用画笔绘制了很复杂的曲线),性能可能会下降。以下是一些优化手段:

  1. 项的可移动性与可选择性:对于背景或固定装饰性图形,调用item->setFlag(QGraphicsItem::ItemIsMovable, false)item->setFlag(QGraphicsItem::ItemIsSelectable, false)。这能减少Scene在事件处理和碰撞检测时的开销。
  2. 边界矩形缓存:对于形状复杂的自定义项(特别是自定义paint()函数很耗时的),可以调用item->setCacheMode(QGraphicsItem::DeviceCoordinateCache)。这会将项渲染到缓存纹理中,在项没有变化时直接使用缓存,大幅提升渲染速度,但会消耗更多内存。
  3. 局部更新:默认情况下,QGraphicsView会更新整个视口。如果只有小部分区域变化,可以调用scene->update(sceneRect)来只更新指定的Scene区域。
  4. 减少实时预览的复杂度:在拖拽绘制矩形、直线时,我们使用了一个临时Item进行预览。确保这个临时Item的绘制非常简单(例如,不使用复杂的画笔样式或阴影效果)。

7.3 常见问题排查与调试记录

在开发过程中,你肯定会遇到一些“坑”。这里记录几个我踩过的典型问题及其解决方法:

  1. 图形项不显示或位置错误

    • 检查坐标系统:确保你传递给图形项构造函数或设置函数的坐标是Scene坐标,而不是View坐标或屏幕坐标。
    • 检查Scene矩形:如果Scene的矩形(sceneRect)设置得太小,或者Item的位置远在矩形之外,View可能不会显示它。可以调用scene->setSceneRect(scene->itemsBoundingRect())让Scene自动调整到能包围所有项的大小。
    • 检查Z值:后添加的项会覆盖在先添加的项之上。如果项被完全覆盖,自然看不到。可以使用item->setZValue()来调整叠放顺序。
  2. 鼠标事件不响应或响应异常

    • 检查事件传播:确保在自定义的mousePressEvent等函数中,没有错误地调用event->ignore(),这会导致事件继续向上层传递,可能被其他部件处理。
    • 检查Item的可选择、可移动标志:如果Item的ItemIsSelectableItemIsMovable标志为false,它可能不会接收某些鼠标事件。
    • 调试输出:在事件处理函数中加入qDebug() << “Mouse Press at:” << event->scenePos();,查看坐标是否正确。
  3. 撤销/重做时程序崩溃

    • 检查Item所有权:确保在QUndoCommand的子类中管理好QGraphicsItem的生命周期。一个常见的模式是:在AddItemCommand的构造函数中接管Item的所有权(m_item->setParent(nullptr),使其脱离任何父对象),在redo()时添加到Scene(Scene获得所有权),在undo()时从Scene移除(命令重新获得所有权)。在命令的析构函数中,如果Item未被添加到任何Scene,则删除它。
    • 避免悬空指针:当Scene中的Item被外部删除时(如用户直接按Delete键),所有包含该Item指针的未执行Undo命令都应失效或做空指针检查。
  4. 保存的图片背景是黑色或透明

    • 检查QImage的初始化:在创建QImage用于保存时,必须用image.fill(Qt::white);显式填充背景色。QImage::Format_ARGB32格式的初始像素值是未定义的。
    • 检查Scene的背景色scene->setBackgroundBrush(Qt::white);只影响View的显示,不影响渲染到QImage。渲染到图像时,需要自己绘制背景。

这个项目从零开始实现一个功能完备的绘图软件,涉及了Qt桌面开发的方方面面。它没有调用任何复杂的第三方库,纯粹依靠Qt自身强大的能力。完成之后,你不仅收获了一个可以实际使用的工具,更重要的是,你建立了一套开发复杂桌面应用的方法论。你可以基于此继续扩展,比如添加图层管理、滤镜效果、更多形状工具,甚至尝试将视图部分用QML重写,探索更现代的UI设计。编程的乐趣,就在于这样一步步将想法变为现实的过程。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/7/15 4:31:00

电子信息工程专业课复习知识点总结:(一)电路分析基础——从理论到实践的应用视角

1. 电路分析基础的核心逻辑电路分析是电子信息工程的基石课程&#xff0c;就像建筑师需要掌握力学原理一样。我刚开始学电路时总想着死记公式&#xff0c;直到在实验室烧坏第三个电阻后才明白&#xff1a;电路是活的系统&#xff0c;那些定理本质是描述电子流动规律的语言。集总…

作者头像 李华
网站建设 2026/7/15 4:30:58

python-129-KeyCloak学习之(三)脚本调用接口自动从keycloak中获取公钥

文章目录 1 自动从keycloak获取公钥 1.1 X.509证书vs裸RSA公钥 1.2 提取公钥 2 基于flask_jwt_extended(app.py) 2.1 代码功能分步解析 2.1.1 配置与发现 (Discovery) 2.1.2 公钥提取与配置 2.1.3 创建受保护的路由 2.2 整体代码 3 基于authlib(app.py) 3.1 代码功能分步解析 3…

作者头像 李华
网站建设 2026/7/15 4:30:55

《计算机网络》核心考点实战:从历年真题到解题策略

1. 计算机网络考试题型解析与备考策略计算机网络作为计算机专业的核心课程&#xff0c;期末考试通常包含简答题、应用题和计算题三大类型。从历年真题来看&#xff0c;这三类题型占比约为3:3:4&#xff0c;其中计算题分值最高但也是失分重灾区。简答题主要考察基础概念的理解&a…

作者头像 李华
网站建设 2026/7/15 4:30:52

CH9141蓝牙透传模块实战:从智能车竞赛到快速原型开发

1. CH9141蓝牙透传模块初体验第一次拿到CH9141模块时&#xff0c;我正为智能车竞赛的无线调试发愁。传统的有线调试需要频繁插拔串口线&#xff0c;不仅效率低&#xff0c;还容易在高速行驶中造成接触不良。这个指甲盖大小的模块完美解决了这些问题——它就像给单片机装上了&qu…

作者头像 李华
网站建设 2026/7/15 4:26:44

MyTT库实战:无需TA-Lib,用Python轻松复现通达信技术指标

1. 为什么选择MyTT替代TA-Lib&#xff1f;如果你曾经尝试在Python中复现通达信的指标公式&#xff0c;大概率踩过TA-Lib这个坑。作为传统金融分析库&#xff0c;TA-Lib的安装过程堪称噩梦——需要先安装C语言编译环境&#xff0c;Windows用户还得折腾whl文件&#xff0c;Mac用户…

作者头像 李华