拖拽畫矩形完整實(shí)現(xiàn))
簡介本資源是一個基于Qt 5.15.2的圖形交互式繪圖Demo面向C GUI開發(fā)初學(xué)者與Qt進(jìn)階實(shí)踐者聚焦QGraphicsView框架下的鼠標(biāo)交互邏輯實(shí)現(xiàn)解決矩形繪制、拖拽、縮放、邊緣調(diào)整及刪除保存等核心UI操作問題。壓縮包共16個文件含5個頭文件如GraphicsViewTool.h、DrawROI.h定義圖形工具類與ROI繪制邏輯5個源文件如DrawROI.cpp、GraphicsView.cpp實(shí)現(xiàn)事件響應(yīng)與場景管理輔以vcxproj工程配置、ui界面文件、qrc資源文件及filters過濾器結(jié)構(gòu)完整便于理解Qt圖形視圖架構(gòu)分層設(shè)計。資源包僅18KB輕量易讀已有2348人學(xué)習(xí)下載。讀者可直接編譯運(yùn)行掌握QPainter繪圖基礎(chǔ)、QGraphicsRectItem動態(tài)控制、鼠標(biāo)事件press/move/release與鍵盤修飾鍵Ctrl滾輪協(xié)同處理等關(guān)鍵技能并復(fù)用代碼邏輯擴(kuò)展多邊形繪制、選中高亮、序列化保存等功能。1. 鼠標(biāo)拖拽畫矩形這件事為什么90%的初學(xué)者卡在“松手就消失”上你試過用QGraphicsViewQPainter實(shí)現(xiàn)鼠標(biāo)繪制矩形嗎不是靜態(tài)顯示一個框而是——按住左鍵拖動時實(shí)時預(yù)覽輪廓松開后永久留下一個可選中、可移動、可刪除的矩形對象。很多同學(xué)寫完發(fā)現(xiàn)拖動時能畫一松手矩形就沒了或者畫出來的是 QPainter 在 viewport 上臨時涂的“影子”根本沒進(jìn)場景scene更常見的是多個矩形疊加后選中錯亂、縮放失真、坐標(biāo)偏移幾十像素……這不是代碼沒寫完是根本沒理清 QGraphics 框架里「視圖-場景-圖元」三層職責(zé)的邊界。這套方案專為需要交付完整交互功能的課程設(shè)計、畢業(yè)設(shè)計或小型圖形工具模塊而生它不依賴 Qt Quick純 QWidget 路線用QGraphicsRectItem做真實(shí)圖元用QGraphicsScene管理生命周期用QPainter只負(fù)責(zé)極簡的臨時反饋——所有邏輯可調(diào)試、可擴(kuò)展、可答辯。適合正在啃 Qt 圖形模塊、被事件分發(fā)和坐標(biāo)系繞暈的 C 學(xué)生也適合要快速嵌入已有 UI 的工程師。2. 總體架構(gòu)設(shè)計三層分離與四類事件的精準(zhǔn)攔截Qt 圖形繪制最易混淆的就是把paintEvent()、mousePressEvent()、QGraphicsItem::paint()全塞進(jìn)同一個類里攪成一鍋粥。我們先立住骨架View 負(fù)責(zé)接收原始輸入Scene 負(fù)責(zé)狀態(tài)調(diào)度Item 負(fù)責(zé)自身渲染。整個流程不經(jīng)過任何自定義繪圖事件全部走 QGraphics 框架原生管線。這意味著你永遠(yuǎn)不需要重寫QWidget::paintEvent也不需要手動調(diào)用update()觸發(fā)重繪——框架會自動根據(jù)圖元變化刷新視圖。2.1 核心類職責(zé)劃分誰該管什么絕不越界DrawingView繼承QGraphicsView只做三件事——捕獲鼠標(biāo)按下/移動/釋放事件將視口坐標(biāo)轉(zhuǎn)為場景坐標(biāo)在拖拽過程中臨時創(chuàng)建/更新一個PreviewRectItem僅用于視覺反饋。它不持有任何業(yè)務(wù)數(shù)據(jù)不管理矩形列表不決定是否保存。DrawingScene繼承QGraphicsScene作為唯一狀態(tài)中心維護(hù)QListQGraphicsRectItem* m_rectItems提供addFinishedRect(QRectF)接口供 View 調(diào)用重寫mousePressEvent做全局點(diǎn)擊攔截比如點(diǎn)空白處取消當(dāng)前預(yù)覽所有持久化矩形的創(chuàng)建、刪除、ZOrder 排序都在這里完成。PreviewRectItem繼承QGraphicsRectItem一個輕量級臨時圖元僅在拖拽中存在。它重寫paint()方法用QPainter::setPen(Qt::DashLine)畫虛線框并禁用所有交互setFlag(ItemIsMovable, false)避免干擾主場景操作。DrawableRectItem繼承QGraphicsRectItem最終落地的正式圖元。啟用ItemIsMovable | ItemIsSelectable | ItemIsFocusable支持鍵盤刪除、雙擊編輯尺寸、右鍵菜單等擴(kuò)展能力——本方案已預(yù)留接口后續(xù)加功能不用改架構(gòu)。提示不要在DrawingView中直接 newQGraphicsRectItem后 add 到 scene。必須通過DrawingScene::addFinishedRect()統(tǒng)一入口否則 scene 無法感知新增圖元會導(dǎo)致items()查詢?yōu)榭?、信號連接失敗等玄學(xué)問題。2.2 坐標(biāo)系轉(zhuǎn)換為什么你算的坐標(biāo)總差 20 像素Qt 中存在三套坐標(biāo)系Widget 坐標(biāo)以 widget 左上角為原點(diǎn)、Viewport 坐標(biāo)以 view 視口左上角為原點(diǎn)、Scene 坐標(biāo)以 scene 邏輯坐標(biāo)系原點(diǎn)為原點(diǎn)。鼠標(biāo)事件給的是 Viewport 坐標(biāo)但QGraphicsScene::addItem()要求 Scene 坐標(biāo)。錯誤做法是手動加減horizontalScrollBar()-value()—— 這在有縮放、平移時必然翻車。正確解法是使用框架提供的轉(zhuǎn)換函數(shù)// 在 DrawingView::mousePressEvent 中 void DrawingView::mousePressEvent(QMouseEvent *event) { if (event-button() Qt::LeftButton) { // 將 viewport 坐標(biāo) → scene 坐標(biāo)自動處理縮放/滾動 QPointF scenePos mapToScene(event-pos()); m_startPos scenePos; m_previewItem new PreviewRectItem(); m_previewItem-setPen(QPen(Qt::blue, 2, Qt::DashLine)); scene()-addItem(m_previewItem); m_previewItem-setRect(QRectF(scenePos, scenePos)); // 初始寬高為0 event-accept(); } else { QGraphicsView::mousePressEvent(event); // 讓父類處理右鍵等其他事件 } }關(guān)鍵點(diǎn)mapToScene(event-pos())是唯一可靠方式。它內(nèi)部已封裝了QTransform矩陣運(yùn)算無論你是否調(diào)用scale()或translate()結(jié)果都精準(zhǔn)。我?guī)н^的某高校課程設(shè)計里A同學(xué)硬寫scenePos.x() horizontalOffset()結(jié)果在 125% 縮放屏下偏移整整 37 像素——從那以后我每次教坐標(biāo)轉(zhuǎn)換都強(qiáng)制讓學(xué)員先刪掉所有手算代碼只留這一行。2.3 拖拽狀態(tài)機(jī)四個狀態(tài)三處校驗(yàn)零內(nèi)存泄漏整個繪制過程本質(zhì)是一個有限狀態(tài)機(jī)狀態(tài)觸發(fā)條件View 行為Scene 行為Idle空閑未按下左鍵忽略所有 move/release無DragStart起始mousePressEvent左鍵記錄起點(diǎn)創(chuàng)建PreviewRectItem不添加到 items 列表Dragging拖拽中mouseMoveEvent更新PreviewRectItem的setRect()PreviewRectItem始終在 scene 中但不計入正式列表DragEnd結(jié)束mouseReleaseEvent刪除PreviewRectItem調(diào)用scene()-addFinishedRect()創(chuàng)建DrawableRectItem并加入m_rectItems狀態(tài)切換必須嚴(yán)格閉環(huán)。特別注意DragEnd時的資源清理// 在 DrawingView::mouseReleaseEvent 中 void DrawingView::mouseReleaseEvent(QMouseEvent *event) { if (event-button() Qt::LeftButton m_previewItem) { // 1. 獲取終點(diǎn) scene 坐標(biāo) QPointF endPos mapToScene(event-pos()); // 2. 構(gòu)造標(biāo)準(zhǔn) QRectF確保 leftright, topbottom QRectF rect QRectF(m_startPos, endPos).normalized(); // 3. 通知 scene 添加正式圖元 static_castDrawingScene*(scene())-addFinishedRect(rect); // 4. 徹底銷毀預(yù)覽項(xiàng)必須否則內(nèi)存泄漏 scene()-removeItem(m_previewItem); delete m_previewItem; m_previewItem nullptr; event-accept(); } else { QGraphicsView::mouseReleaseEvent(event); } }normalized()是血淚經(jīng)驗(yàn)當(dāng)用戶從右向左拖拽時QRectF(m_startPos, endPos)會產(chǎn)生負(fù)寬高導(dǎo)致QGraphicsRectItem渲染異常甚至崩潰。normalized()自動交換坐標(biāo)保證 rect 合法。另外removeItem()和delete必須成對出現(xiàn)——Qt 文檔明確要求從 scene 移除后若不再使用必須手動 delete否則對象殘留內(nèi)存。3. 關(guān)鍵模塊實(shí)現(xiàn)從預(yù)覽虛線到可操作矩形的完整鏈路現(xiàn)在進(jìn)入真正要抄作業(yè)的部分。下面給出DrawingScene和DrawableRectItem的核心實(shí)現(xiàn)每段代碼后都附帶參數(shù)說明和設(shè)計意圖不是貼完就走。3.1 DrawingScene狀態(tài)容器與業(yè)務(wù)中樞// drawing_scene.h class DrawingScene : public QGraphicsScene { Q_OBJECT public: explicit DrawingScene(QObject *parent nullptr); // 對外唯一添加正式矩形的入口 void addFinishedRect(const QRectF rect); // 獲取所有可操作矩形供外部查詢、導(dǎo)出、批量操作 QListQGraphicsRectItem* allDrawableRects() const; signals: void rectAdded(QGraphicsRectItem* item); // 供 UI 更新狀態(tài)欄如已繪制3個矩形 private: QListQGraphicsRectItem* m_rectItems; }; // drawing_scene.cpp DrawingScene::DrawingScene(QObject *parent) : QGraphicsScene(parent) { // 設(shè)置默認(rèn)畫布背景為淺灰便于看清白色矩形邊框 setBackgroundBrush(Qt::lightGray); } void DrawingScene::addFinishedRect(const QRectF rect) { DrawableRectItem *item new DrawableRectItem(); item-setRect(rect); item-setPen(QPen(Qt::black, 1.5)); // 正式圖元用實(shí)線 item-setBrush(QColor(200, 220, 255, 100)); // 半透明藍(lán)底 addItem(item); m_rectItems.append(item); // 發(fā)射信號上層可連接槽函數(shù)更新按鈕狀態(tài)或計數(shù)器 emit rectAdded(item); } QListQGraphicsRectItem* DrawingScene::allDrawableRects() const { return m_rectItems; }參數(shù)說明與設(shè)計點(diǎn)setBackgroundBrush(Qt::lightGray)不是為了美觀而是解決一個經(jīng)典問題——當(dāng)用戶畫出純白矩形時在默認(rèn)白底上完全不可見。設(shè)為淺灰后即使setBrush(Qt::white)也能看清邊框。QColor(200, 220, 255, 100)中的100是 alpha 通道0~255取值 100 約等于 40% 透明度。這樣既能看到矩形覆蓋區(qū)域又不會遮擋底層內(nèi)容符合工程中“可疊加、可識別”的交互原則。allDrawableRects()返回QList而非QVector因?yàn)镼List在 Qt 中對指針類型做了優(yōu)化插入/遍歷性能更好且與QGraphicsScene::items()返回類型一致減少類型轉(zhuǎn)換。3.2 DrawableRectItem可交互圖元的最小完備實(shí)現(xiàn)// drawable_rect_item.h class DrawableRectItem : public QGraphicsRectItem { public: explicit DrawableRectItem(QGraphicsItem *parent nullptr); // 重寫 boundingRect 以支持精確點(diǎn)擊檢測非必須但推薦 QRectF boundingRect() const override; // 重寫 paint控制外觀細(xì)節(jié) void paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget nullptr) override; protected: // 支持鍵盤刪除 void keyPressEvent(QKeyEvent *event) override; // 支持雙擊進(jìn)入編輯模式預(yù)留擴(kuò)展點(diǎn) void mouseDoubleClickEvent(QGraphicsSceneMouseEvent *event) override; }; // drawable_rect_item.cpp DrawableRectItem::DrawableRectItem(QGraphicsItem *parent) : QGraphicsRectItem(parent) { // 啟用所有基礎(chǔ)交互能力 setFlag(ItemIsMovable, true); setFlag(ItemIsSelectable, true); setFlag(ItemIsFocusable, true); // 設(shè)置選中時的虛線邊框Qt 默認(rèn)樣式 setPen(QPen(Qt::darkBlue, 2, Qt::DotLine)); } QRectF DrawableRectItem::boundingRect() const { // 擴(kuò)展 2px 邊界讓鼠標(biāo)在邊框外 2px 內(nèi)也能選中提升體驗(yàn) QRectF base QGraphicsRectItem::boundingRect(); return base.adjusted(-2, -2, 2, 2); } void DrawableRectItem::paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget) { // 先調(diào)用父類繪制基礎(chǔ)矩形含選中高亮 QGraphicsRectItem::paint(painter, option, widget); // 如果當(dāng)前被選中疊加一個半透明遮罩層增強(qiáng)視覺反饋 if (isSelected()) { painter-save(); painter-setPen(Qt::NoPen); painter-setBrush(QColor(255, 215, 0, 80)); // 金色半透明 painter-drawRect(rect().adjusted(2, 2, -2, -2)); painter-restore(); } } void DrawableRectItem::keyPressEvent(QKeyEvent *event) { if (event-key() Qt::Key_Delete || event-key() Qt::Key_Backspace) { // 安全刪除先從 scene 移除再 delete if (scene()) { scene()-removeItem(this); } delete this; // 注意this 指針在此后失效 event-accept(); } else { QGraphicsRectItem::keyPressEvent(event); } } void DrawableRectItem::mouseDoubleClickEvent(QGraphicsSceneMouseEvent *event) { // 預(yù)留此處可彈出尺寸編輯對話框 qDebug() Double clicked on rect: rect(); event-accept(); }設(shè)計說明boundingRect().adjusted(-2,-2,2,2)是典型的人機(jī)工程技巧。人眼定位精度有限直接點(diǎn)擊像素級邊框極易失敗。擴(kuò)展 2px 后用戶點(diǎn)擊邊框外側(cè)也能觸發(fā)選中體驗(yàn)提升顯著。keyPressEvent中的delete this是 Qt 官方認(rèn)可的安全寫法見 Qt Docs: Object Trees Ownership 前提是確保該對象不在棧上創(chuàng)建且沒有其他強(qiáng)引用。本方案中所有DrawableRectItem均由new創(chuàng)建符合前提。paint()中的qDebug()是調(diào)試鉤子實(shí)際交付時應(yīng)替換為信號發(fā)射如emit sizeEdited(rect())供主窗口更新屬性面板。3.3 主窗口集成三行代碼啟動繪制模式最后如何把這套模塊接入你的主 UI假設(shè)你用 Qt Designer 拖了一個QGraphicsView命名為graphicsView// main_window.cpp #include drawing_view.h #include drawing_scene.h MainWindow::MainWindow(QWidget *parent) : QMainWindow(parent), ui(new Ui::MainWindow) { ui-setupUi(this); // 1. 替換默認(rèn) view 為自定義 DrawingView DrawingView *view new DrawingView(this); ui-graphicsView-close(); // 移除 Designer 創(chuàng)建的默認(rèn) view ui-verticalLayout-addWidget(view); // 假設(shè)布局名為 verticalLayout // 2. 創(chuàng)建自定義 scene 并設(shè)置給 view DrawingScene *scene new DrawingScene(this); view-setScene(scene); // 3. 連接添加信號更新 UI 狀態(tài)例如按鈕文字、計數(shù)器 connect(scene, DrawingScene::rectAdded, this, MainWindow::onRectAdded); } void MainWindow::onRectAdded(QGraphicsRectItem *item) { int count static_castDrawingScene*(ui-graphicsView-scene()) -allDrawableRects().size(); ui-statusBar-showMessage(QString(已繪制 %1 個矩形).arg(count)); }關(guān)鍵點(diǎn)不要試圖在 Designer 中“提升為”自定義類——QGraphicsView提升后無法在.ui文件中修改其scene屬性會導(dǎo)致編譯期綁定失敗。必須在代碼中new并setScene()這是 Qt 官方推薦的動態(tài)集成方式。4. 避坑指南五個必踩的“松手就消失”型問題排查清單剛寫完代碼卻看不到矩形或者畫出來位置錯亂別急著重寫先對照這份按發(fā)生頻率排序的避坑清單逐條排查。這些問題我在三個不同高校的畢設(shè)答辯中反復(fù)見到90% 的“畫不出來”都源于其中某一條。4.1 現(xiàn)象鼠標(biāo)拖拽時有虛線框松手后矩形徹底消失原因mouseReleaseEvent中調(diào)用了scene()-addItem(item)但item是棧對象或局部變量作用域結(jié)束即銷毀或addItem()后未調(diào)用item-show()雖然QGraphicsItem默認(rèn)可見但某些 Qt 版本在特定編譯選項(xiàng)下需顯式調(diào)用。解決確認(rèn)DrawableRectItem* item new DrawableRectItem()在堆上創(chuàng)建檢查addItem()后是否誤加了item-hide()用qDebug() scene()-items().size()在添加前后打印數(shù)量驗(yàn)證。4.2 現(xiàn)象矩形畫出來了但位置嚴(yán)重偏移比如總在左上角原因在mousePressEvent中用了event-pos()直接構(gòu)造QRectF而event-pos()是 Viewport 坐標(biāo)不是 Scene 坐標(biāo)。解決必須統(tǒng)一使用mapToScene(event-pos())轉(zhuǎn)換在addFinishedRect()中打印qDebug() start: m_startPos end: endPos驗(yàn)證坐標(biāo)值是否合理正常應(yīng)在 0~1000 范圍內(nèi)而非 0~100。4.3 現(xiàn)象畫多個矩形后只能選中最上面一個下面的點(diǎn)不中原因QGraphicsRectItem默認(rèn)zValue()為 0新添加的圖元會覆蓋舊圖元但QGraphicsScene::items()返回順序不保證 ZOrder更常見的是setFlag(ItemIsSelectable, true)沒在構(gòu)造函數(shù)中調(diào)用導(dǎo)致圖元不可選。解決在DrawableRectItem構(gòu)造函數(shù)中強(qiáng)制setZValue(0)檢查setFlag(ItemIsSelectable, true)是否執(zhí)行用scene()-sendEvent(item, new QMouseEvent(...))模擬點(diǎn)擊測試單個圖元響應(yīng)。4.4 現(xiàn)象縮放view-scale(2,2)后拖拽預(yù)覽框嚴(yán)重變形或抖動原因PreviewRectItem::setRect()傳入的是未歸一化的QRectF(m_startPos, endPos)縮放時QRectF的寬高計算受變換矩陣影響產(chǎn)生浮點(diǎn)誤差累積。解決始終使用QRectF::normalized()構(gòu)造在PreviewRectItem::paint()中用painter-setRenderHint(QPainter::Antialiasing)抗鋸齒避免在mouseMoveEvent中高頻調(diào)用setRect()改為每 16ms約 60fps更新一次。4.5 現(xiàn)象程序退出時崩潰報QGraphicsScene: deleting items while scene is being destroyed原因DrawingScene析構(gòu)時m_rectItems中的DrawableRectItem*仍被 scene 持有而你在析構(gòu)函數(shù)中又手動delete了它們造成雙重釋放。解決DrawingScene析構(gòu)函數(shù)中只清空m_rectItems列表不 delete 任何指針因?yàn)镼GraphicsScene析構(gòu)時會自動 delete 所有addItem()過的圖元。正確寫法DrawingScene::~DrawingScene() { m_rectItems.clear(); // 僅清空列表指針由 scene 自動管理 }注意Qt 的內(nèi)存管理規(guī)則是——誰addItem()誰負(fù)責(zé)removeItem()后的deleteQGraphicsScene析構(gòu)時會自動removeItem()并delete所有子項(xiàng)。因此你只需在運(yùn)行時動態(tài)刪除如 Delete 鍵無需在析構(gòu)時干預(yù)。5. 驗(yàn)收與答辯用三張表一個動作讓老師當(dāng)場點(diǎn)頭畢設(shè)/課設(shè)答辯最怕什么不是代碼寫得不夠炫而是老師問“這個功能怎么驗(yàn)證”你支吾半天最后打開程序隨便點(diǎn)兩下效果還不穩(wěn)定。驗(yàn)收不是演示是用可復(fù)現(xiàn)的步驟、可量化的指標(biāo)、可回溯的日志證明你真的做完了、做對了、能用了。下面這套方法我在某高校連續(xù)三年指導(dǎo)學(xué)生答辯通過率 100%核心就一句話把“我實(shí)現(xiàn)了”變成“你按這步做立刻看到結(jié)果”。5.1 功能驗(yàn)收表五項(xiàng)核心能力逐條打鉤拿出一張紙或新建文本文件按順序執(zhí)行以下操作每完成一項(xiàng)打一個 ?。只要有一項(xiàng)失敗說明模塊未閉環(huán)必須返工。序號驗(yàn)證動作預(yù)期結(jié)果失敗表現(xiàn)定位方向1啟動程序鼠標(biāo)懸停在 view 區(qū)域狀態(tài)欄顯示“就緒”或無提示顯示“未初始化”或崩潰檢查DrawingScene是否成功setScene()2按住左鍵從 (100,100) 拖拽到 (300,200) 后松手場景中出現(xiàn)一個藍(lán)色虛線預(yù)覽框 → 松手后變?yōu)楹谏珜?shí)線矩形預(yù)覽框不出現(xiàn) / 松手后消失 / 位置偏移查mousePressEvent是否調(diào)用mapToScene()查addFinishedRect()是否執(zhí)行3用鼠標(biāo)點(diǎn)擊剛畫的矩形矩形邊框變?yōu)樯钏{(lán)色虛線狀態(tài)欄顯示“已選中1個”無反應(yīng) / 選中其他矩形檢查DrawableRectItem是否啟用ItemIsSelectable查boundingRect()是否正確4選中矩形后按 Delete 鍵矩形立即消失狀態(tài)欄計數(shù)減 1無反應(yīng) / 崩潰 / 閃退查keyPressEvent中delete this是否執(zhí)行用qDebug()打印scene()-items().size()5連續(xù)繪制 5 個矩形分別拖拽到不同位置所有矩形獨(dú)立可移動、可選中、不互相遮擋某個矩形無法拖動 / 選中后其他矩形也移動檢查每個DrawableRectItem的setFlag(ItemIsMovable)是否單獨(dú)調(diào)用確認(rèn)未誤設(shè)setParentItem()這張表的價值在于它把模糊的“功能正常”拆解成原子操作每個動作都有明確輸入、確定輸出、失敗特征和排查路徑。答辯時你可以直接說“老師請看第3項(xiàng)——我現(xiàn)在點(diǎn)擊這個矩形……您看邊框變色了狀態(tài)欄更新了這證明選中邏輯是通的?!?老師不需要懂代碼也能判斷你做對了。5.2 性能與健壯性驗(yàn)收兩個命令行指令定生死很多同學(xué)忽略非功能需求直到答辯被問“如果用戶瘋狂拖拽怎么辦”才慌神。其實(shí)只需兩條終端命令就能暴露所有隱患命令1壓力測試模擬高頻拖拽在mouseMoveEvent開頭加一行qDebug() MouseMove at QTime::currentTime().msec();然后快速左右拖拽 10 秒。觀察 Qt Creator 的 Application Output 面板如果出現(xiàn)大量重復(fù)日志如MouseMove at 123,MouseMove at 124,MouseMove at 125…說明事件未節(jié)流可能卡頓。解決方案在DrawingView中加時間戳過濾// class member QElapsedTimer m_moveTimer; // in mouseMoveEvent if (m_moveTimer.elapsed() 16) return; // 限制 60fps m_moveTimer.restart();命令2內(nèi)存泄漏掃描Linux/macOS編譯時加-fsanitizeaddress參數(shù)運(yùn)行后執(zhí)行繪制→刪除→再繪制循環(huán) 10 次退出時查看終端輸出。若出現(xiàn)AddressSanitizer: heap-use-after-free說明delete this時機(jī)錯誤若出現(xiàn)leak summary: 5 bytes in 1 blocks說明某個new沒配對delete。Windows 下可用 Visual Studio 的診斷工具原理相同。5.3 答辯話術(shù)用“問題-解法-證據(jù)”三段式征服老師別一上來就說“我用了 QGraphicsView 和 QPainter”。老師想聽的是你遇到了什么具體問題怎么想到這個解法以及怎么證明它有效。我教學(xué)生的固定話術(shù)模板“老師這個模塊最大的挑戰(zhàn)是‘松手瞬間矩形丟失’問題。我最初以為是addItem()沒調(diào)用后來發(fā)現(xiàn)是坐標(biāo)系混亂——鼠標(biāo)給的是 viewport 坐標(biāo)但 scene 要 scene 坐標(biāo)解法。所以我強(qiáng)制所有坐標(biāo)轉(zhuǎn)換都走mapToScene()并在addFinishedRect()里加了qDebug()打印轉(zhuǎn)換前后的值證據(jù)。您看這是日志截圖起點(diǎn) (102.3, 98.7) → 終點(diǎn) (298.1, 199.4)完全在合理范圍內(nèi)?!闭f完立刻切到程序現(xiàn)場演示從拖拽到松手的全過程同步念出日志內(nèi)容。這種“問題導(dǎo)向證據(jù)鏈”式表達(dá)比背誦技術(shù)名詞有力十倍。從那以后我每次帶學(xué)生做 Qt 圖形項(xiàng)目都強(qiáng)制他們在main()開頭加一行qInstallMessageHandler(customMessageHandler)把所有qDebug()輸出重定向到文件。答辯前夜他們把生成的debug.log打印出來夾在報告書里——老師翻到那頁看到密密麻麻的坐標(biāo)日志和? rectAdded基本就點(diǎn)頭了。希望幫到你。本文還有配套的精品資源點(diǎn)擊獲取