QT 上下文菜單內(nèi)存泄露之QMainWindow
QT 上下文菜單內(nèi)存泄露之QMainWindow 是本人要介紹的內(nèi)容,先來看內(nèi)容。創(chuàng)建Qt工程,基于QMainwindow,什么也不做,程序會(huì)自帶一個(gè)上下文菜單。
不斷點(diǎn)擊鼠標(biāo)右鍵,菜單將反復(fù)出現(xiàn),此時(shí)我用任務(wù)管理器查看其內(nèi)存變化,發(fā)現(xiàn)每次不斷增加,請(qǐng)問大家這是Qt的內(nèi)存泄漏嗎???我用MFC,CB均沒有發(fā)現(xiàn)類此錯(cuò)誤。
在Qt 4.7.0 和 4.7.3下可以重現(xiàn)該問題,在Qt 4.6.3下不存在該問題??梢源_定是Qt的一個(gè)bug。
問題重現(xiàn)
在工具欄或??看翱谥悬c(diǎn)擊右鍵(彈出上下文菜單),多點(diǎn)擊幾次,然后點(diǎn)擊按鈕。觀察控制臺(tái)輸出,可以看到很多個(gè) QMenu 對(duì)象。
- #include <QtGui>
- class MainWindow : public QMainWindow
- {
- Q_OBJECT
- public:
- explicit MainWindow(QWidget *parent = 0);
- private slots:
- void onButtonClicked();
- };
- MainWindow::MainWindow(QWidget *parent)
- {
- addToolBar("ToolBar");
- addDockWidget(Qt::LeftDockWidgetArea, new QDockWidget("DockWidget"));
- QPushButton * btn = new QPushButton("dump object tree");
- setCentralWidget(btn);
- connect(btn, SIGNAL(clicked()), SLOT(onButtonClicked()));
- }
- void MainWindow::onButtonClicked()
- {
- dumpObjectTree();
- }
- #include "main.moc"
- int main(int argc, char *argv[])
- {
- QApplication a(argc, argv);
- MainWindow w;
- w.show();
- return a.exec();
- }
原因
既然是QMainWindow的上下文菜單問題,直接看 contextMenuEvent 事件處理函數(shù)吧。
- void QMainWindow::contextMenuEvent(QContextMenuEvent *event)
- {
- event->ignore();
- ...
- QMenu *popup = createPopupMenu();
- if (popup) {
- if (!popup->isEmpty()) {
- popup->setAttribute(Qt::WA_DeleteOnClose);
- popup->popup(event->globalPos());
- event->accept();
- } else {
- delete popup;
- }
- }
- }
看仔細(xì)嘍,這兒設(shè)置了 Qt::WA_DeleteOnClose 屬性。
有什么用?設(shè)置該屬性后,當(dāng)我們調(diào)用該對(duì)象的 close() 成員時(shí),隱藏(hide)窗口同時(shí)會(huì)刪除(delete)該對(duì)象
有什么問題?問題出在,實(shí)際上隱藏菜單時(shí)沒有 調(diào)用菜單的close(),而是 調(diào)用的hide()的成員。
調(diào)用hide()而不是close(),是的該屬性不能發(fā)揮任何作用,進(jìn)而導(dǎo)致內(nèi)存泄露(Qt 之 show,hide,setVisible,setHidden,close 等小結(jié) )。
為了對(duì)比,我們看看Qt4.6.3的源碼部分:
- void QMainWindow::contextMenuEvent(QContextMenuEvent *event)
- {
- event->ignore();
- ...
- QMenu *popup = createPopupMenu();
- if (popup && !popup->isEmpty()) {
- popup->exec(event->globalPos());
- event->accept();
- }
- delete popup;
- }
而這個(gè),也就是我們的比較理想的答案了。
進(jìn)一步學(xué)習(xí)
前面說了,菜單隱藏時(shí)調(diào)用的是hide() 成員,而不是close() 成員。有神馬依據(jù)??
想想?如何讓菜單隱藏
鼠標(biāo):點(diǎn)擊菜單外區(qū)域
鍵盤:按下Esc鍵等
這樣就比較明朗了,對(duì)吧,直接看這兩個(gè)事件處理函數(shù)
鍵盤的按鍵事件(調(diào)用了hideMenu)
- void QMenu::keyPressEvent(QKeyEvent *e)
- {
- Q_D(QMenu);
- d->updateActionRects();
- int key = e->key();
- ...
- bool key_consumed = false;
- switch(key) {
- case Qt::Key_Escape:
- key_consumed = true;
- {
- QPointer<QWidget> caused = d->causedPopup.widget;
- d->hideMenu(this); // hide after getting causedPopup
- if (QMenuBar *mb = qobject_cast<QMenuBar*>(caused)) {
- mb->d_func()->setCurrentAction(d->menuAction);
- mb->d_func()->setKeyboardMode(true);
- }
- }
- break;鼠標(biāo)在菜單區(qū)域外按鍵,調(diào)用了hideUpToMenuBar(進(jìn)而調(diào)用hideMenu)
- void QMenu::mousePressEvent(QMouseEvent *e)
- {
- Q_D(QMenu);
- ...
- if (!rect().contains(e->pos())) {
- if (d->noReplayFor
- && QRect(d->noReplayFor->mapToGlobal(QPoint()), d->noReplayFor->size()).contains(e->globalPos()))
- setAttribute(Qt::WA_NoMouseReplay);
- if (d->eventLoop) // synchronous operation
- d->syncAction = 0;
- d->hideUpToMenuBar();
- return;
- }
- }
前面都調(diào)用了hideMenu,從名字也能猜猜它想干什么:
- void QMenuPrivate::hideMenu(QMenu *menu, bool justRegister)
- {
- ...
- menu->hide();
- }
小結(jié):QT 上下文菜單內(nèi)存泄露之QMainWindow 的內(nèi)容介紹完了,希望本文對(duì)你有所幫助!