Контекстное меню в QTableView и QEventLoop
Я создаю контекстное меню стандартным путем:
QMenu contextMenu(tr("Context menu"), this);
QAction action1(tr("Копировать"), this);
QAction action2(tr("Выделить все"), this);
contextMenu.addAction(&action1);
contextMenu.addAction(&action2);
contextMenu.exec(mapToGlobal(pos));
Но появляющееся окно получается модальным - блокирует поток, пока я не выберу что-нибудь или не отменю открытие меню, например, кнопкой Esc.
Я использую QEventLoop, что позволяет мне в одном потоке обрабатывать события пользователя и производить некоторые другие несложные операции - работа с файлом, несложные повторяющиеся вычисления.
Пробовал принудительно делать меню немодальным:
contextMenu.setWindowModality(Qt::WindowModality::NonModal);
но меню после этого перестало появляться. Также я пробовал использовать
contextMenu.show();
и
contextMenu.popup(mapToGlobal(pos));
но и это не помогло.
Кто знает, как сделать контекстное меню немодальным, либо почему после принудительной установки contextMenu.setWindowModality(Qt::WindowModality::NonModal); меню перестало появляться?
QEventLoop я использую по методике от PavelK, и проблем не возникает.
Ответы (3 шт):
Вот так работает нормально:
MainWindow::MainWindow(QWidget *parent)
: QMainWindow(parent)
, ui(new Ui::MainWindow)
{
ui->setupUi(this);
ui->tableView->setContextMenuPolicy(Qt::CustomContextMenu);
connect(ui->tableView, &QTableView::customContextMenuRequested, [&](const QPoint& pos) {
static QMenu* menu = nullptr;
if (!menu) {
menu = new QMenu();
menu->addAction(new QAction("Action 1", this));
menu->addAction(new QAction("Action 2", this));
menu->addAction(new QAction("Action 3", this));
}
menu->popup( mapToGlobal(pos) );
});
}
Методы exec() в Qt работают по одному принципу. Они создают внутри себя QEventLoop и ждут пока по определённому условию не вызовется его слот quit(). Эта идея аналогична той, которую я вижу по вашей ссылке.
При этом локальный QEventLoop осуществляет доставку всех эвентов по местам назначения.
Почему exec() блокирует выполнение ваших методов?
К сожалению, не видя вашего кода, я могу лишь предположить, но вероятно вы в своих методах ждёте выхода из своего QEventLoop, в то время как он прерывается QEventLoop, который вызван в exec(). Поэтому выхода из вашего QEventLoop не произойдёт, пока не произойдёт выхода из QEventLoop метода exec().
Таким образом модальность меню тут совсем не причём. Более того, QMenu по умолчанию NonModal, а значит вызов contextMenu.setWindowModality(Qt::WindowModality::NonModal); не должен был поменять принцип работы программы.
Для исправления данной проблемы вам стоит использовать обычные эвенты и сигнал/слоты там, где это возможно. Локальный QEventLoop будет доставлять их даже во время исполнения QMenu::exec(). Ещё в приложении имеется свой QEventLoop, который запустится при вызове app.exec() и будет отвечать за доставку эвентов в остальное время.
Почему не работает QMenu::popup?
Вызов popup «асинхронный» (Если быть точным, то просто видимый эффект от него будет, когда приложение начнёт обрабатывать эвенты). После его вызова выполнение программы продолжается, объект QMenu уничтожается при выходе из области видимости, и вы даже не успеваете его увидеть. Обойти это можно используя new см.отв..
show() вообще не нужно использовать для QMenu.
Как все заработало у меня:
QMenu *m_contextMenu = nullptr;
QAction *m_action1 = nullptr, *m_action2 = nullptr;
...
connect(this, &QTableView::customContextMenuRequested,
[&] (const QPoint &pos) {
if (!m_contextMenu) {
m_contextMenu = new QMenu(tr("Context menu"), this);
if (!m_action1) {
m_action1 = new QAction(tr("Копировать"), this);
connect(m_action1, &QAction::triggered, this, &MyTableView::copyRow);
}
if (!m_action2) {
m_action2 = new QAction(tr("Выделить все"), this);
connect(m_action2, &QAction::triggered, this, &QAbstractItemView::selectAll);
}
m_contextMenu->addAction(m_action1);
m_contextMenu->addAction(m_action2);
}
m_contextMenu->popup(mapToGlobal(pos));
});
static мне не подошел, т.к. я использую несколько таблиц. Поэтому пришлось все вынести в члены класса.