Selecting QTreeView Switches Focus on Default QButton
-
Hi,
I have a QTreeView and 2 x QButtons: Ok and Cancel (accept and reject). The default button is Ok but when I run my project on MacOS and I click the mouse anywhere within the QTreeView, the highlighted button switches from Ok to Cancel.
I've simplified my code and managed to still get the issue:
assetSelectLabel = new QLabel(label); assetSelectTree = new QTreeView(); assetSelectTree->setFixedSize(260, 360); assetSelectTree->setHeaderHidden(true); assetSelectTree->setDragEnabled(false); assetSelectTree->setEditTriggers(QAbstractItemView::NoEditTriggers); assetSelectItemModel = new QStandardItemModel(this); // Data would usually be added here assetSelectTree->setModel(assetSelectItemModel); assetSelectTree->setCurrentIndex(assetSelectItemModel->invisibleRootItem()->index()); assetSelectTree->expandAll(); okButton = new QPushButton(QString("&OK")); okButton->setDefault(true); cancelButton = new QPushButton(QString("&Cancel")); buttonBox = new QDialogButtonBox(Qt::Horizontal); buttonBox->addButton(cancelButton, QDialogButtonBox::RejectRole); buttonBox->addButton(okButton, QDialogButtonBox::AcceptRole); QVBoxLayout *assetSelectLayout = new QVBoxLayout; assetSelectLayout->addWidget(assetSelectLabel); assetSelectLayout->addWidget(assetSelectTree); QGridLayout *mainLayout = new QGridLayout; mainLayout->setSizeConstraint(QLayout::SetFixedSize); mainLayout->addLayout(assetSelectLayout, 0, 0); mainLayout->addWidget(buttonBox, 1, 0); setLayout(mainLayout); connect(buttonBox, &QDialogButtonBox::accepted, this, &assetAssetSelector::slotAccept); connect(buttonBox, &QDialogButtonBox::rejected, this, &assetAssetSelector::slotReject);
When the program runs, the Ok button is highlighted and hitting the Enter key calls the accept slot, but as soon as I click inside the QTreeView, either on an item or a blank space, the Cancel button is higlighted and hitting the Enter key calls the reject slot.
I'm unsure what I need to change to ensure the Ok button continues to be highlighted.
Thanks.
-
Which Qt Version are you running, and which OS is the one that works without switching the default button?
-
Which Qt Version are you running, and which OS is the one that works without switching the default button?
Qt 6.6.0 (Clang 13.0 (Apple), arm64)
I can't verify at this time, but I don't think the same thing happens when I run the same code but on Windows.
I tried adding the following code:
assetSelectTree->setFocusPolicy(Qt::NoFocus);
and this seems to resolve what I'm seeing, but I'm not sure if it's the correct way to do it?
-
Qt 6.6.0 (Clang 13.0 (Apple), arm64)
I can't verify at this time, but I don't think the same thing happens when I run the same code but on Windows.
I tried adding the following code:
assetSelectTree->setFocusPolicy(Qt::NoFocus);
and this seems to resolve what I'm seeing, but I'm not sure if it's the correct way to do it?
@gsephelec
Qt::NoFocus
certainly helps, unless you need the tree to acquire focus at some point.
MacOS treats buttons differently from other OSes at times, e.g. in aQDialogButtonBox
. But bouncing from OK to Cancel sounds wrong to me. Unless related to application code, it could be a bug in Qt. However, I can't reproduce it on the spot.
A recent focus chain fix related to macOS has landed in 6.6, so you can't be affected from the bug anymore.
Can you isolate the issue in a minimal, compilable reproducer? -
@gsephelec
Qt::NoFocus
certainly helps, unless you need the tree to acquire focus at some point.
MacOS treats buttons differently from other OSes at times, e.g. in aQDialogButtonBox
. But bouncing from OK to Cancel sounds wrong to me. Unless related to application code, it could be a bug in Qt. However, I can't reproduce it on the spot.
A recent focus chain fix related to macOS has landed in 6.6, so you can't be affected from the bug anymore.
Can you isolate the issue in a minimal, compilable reproducer?Sure thing, I've uploaded a completely minimal project that recreates the issue here:
-
@gsephelec
Qt::NoFocus
certainly helps, unless you need the tree to acquire focus at some point.
MacOS treats buttons differently from other OSes at times, e.g. in aQDialogButtonBox
. But bouncing from OK to Cancel sounds wrong to me. Unless related to application code, it could be a bug in Qt. However, I can't reproduce it on the spot.
A recent focus chain fix related to macOS has landed in 6.6, so you can't be affected from the bug anymore.
Can you isolate the issue in a minimal, compilable reproducer?I noticed other implementions of QDialog have similar functionality, eg, if a textbox is selected, the default button swicthes from Ok to Cancel, despite Ok being the default button.
Could you reproduce the issue with the code I posted?
Thanks
-
I noticed other implementions of QDialog have similar functionality, eg, if a textbox is selected, the default button swicthes from Ok to Cancel, despite Ok being the default button.
Could you reproduce the issue with the code I posted?
Thanks
@gsephelec
I'm throwing this thought in. No testing, and I don't have Mac anyway.I note that the the
cancelButton
is the first button you add to theQDialogButtonBox
. I don't know why or whether this is desired/intended, but my suspicion is that it resets/selects the first button? Try swapping the order of yourcancelButton
/okButton
, does it now select theokButton
? -
@gsephelec
I'm throwing this thought in. No testing, and I don't have Mac anyway.I note that the the
cancelButton
is the first button you add to theQDialogButtonBox
. I don't know why or whether this is desired/intended, but my suspicion is that it resets/selects the first button? Try swapping the order of yourcancelButton
/okButton
, does it now select theokButton
? -
I noticed other implementions of QDialog have similar functionality, eg, if a textbox is selected, the default button swicthes from Ok to Cancel, despite Ok being the default button.
Could you reproduce the issue with the code I posted?
Thanks
@gsephelec
I don't download reproducer code from external sources.
If you want me to reproduce, the code has to be so minimal that you can post it here. -
@gsephelec
I don't download reproducer code from external sources.
If you want me to reproduce, the code has to be so minimal that you can post it here.See code here:
main.cpp
#include "mainwindow.h" #include <QApplication> int main(int argc, char *argv[]) { QApplication a(argc, argv); MainWindow w; w.show(); return a.exec(); }
mainwindow.cpp
#include "mainwindow.h" #include "assetDialog.h" MainWindow::MainWindow(QWidget *parent) : QMainWindow(parent) { assetAssetSelector dialog(this); dialog.exec(); } MainWindow::~MainWindow() { }
mainwindow.h
#ifndef MAINWINDOW_H #define MAINWINDOW_H #include <QMainWindow> #include <QtWidgets> #include <QTreeView> QT_BEGIN_NAMESPACE namespace Ui { class MainWindow; } QT_END_NAMESPACE class MainWindow : public QMainWindow { Q_OBJECT public: MainWindow(QWidget *parent = nullptr); ~MainWindow(); private: Ui::MainWindow *ui; QLabel *assetSelectLabel; QTreeView *assetSelectTree; QDialogButtonBox *buttonBox; QPushButton *okButton; QPushButton *cancelButton; QStandardItemModel *assetSelectItemModel; }; #endif // MAINWINDOW_H
assetDialog.cpp
#include "assetDialog.h" assetAssetSelector::assetAssetSelector(QWidget *parent) : QDialog(parent) { assetSelectLabel = new QLabel("Test"); assetSelectTree = new QTreeView(); assetSelectTree->setFixedSize(260, 360); assetSelectTree->setHeaderHidden(true); assetSelectTree->setDragEnabled(false); assetSelectTree->setEditTriggers(QAbstractItemView::NoEditTriggers); assetSelectItemModel = new QStandardItemModel(this); // Data would usually be added here assetSelectTree->setModel(assetSelectItemModel); assetSelectTree->setCurrentIndex(assetSelectItemModel->invisibleRootItem()->index()); assetSelectTree->expandAll(); okButton = new QPushButton(QString("&OK")); okButton->setDefault(true); cancelButton = new QPushButton(QString("&Cancel")); buttonBox = new QDialogButtonBox(Qt::Horizontal); buttonBox->addButton(cancelButton, QDialogButtonBox::RejectRole); buttonBox->addButton(okButton, QDialogButtonBox::AcceptRole); QVBoxLayout *assetSelectLayout = new QVBoxLayout; assetSelectLayout->addWidget(assetSelectLabel); assetSelectLayout->addWidget(assetSelectTree); QGridLayout *mainLayout = new QGridLayout; mainLayout->setSizeConstraint(QLayout::SetFixedSize); mainLayout->addLayout(assetSelectLayout, 0, 0); mainLayout->addWidget(buttonBox, 1, 0); setLayout(mainLayout); connect(buttonBox, &QDialogButtonBox::accepted, this, &assetAssetSelector::slotAccept); connect(buttonBox, &QDialogButtonBox::rejected, this, &assetAssetSelector::slotReject); } void assetAssetSelector::slotAccept() { /* Accept the dialog */ accept(); } void assetAssetSelector::slotReject() { /* Reject the dialog */ reject(); }
assetDialog.h
#ifndef ASSETDIALOG_H #define ASSETDIALOG_H #pragma once #include "mainWindow.h" #include <QDialog> #include <QtWidgets> #include <QTreeView> #include <string> QT_BEGIN_NAMESPACE class QLabel; class QLineEdit; class QComboBox; //class QCheckBox; class QDialogButtonBox; class QPushButton; QT_END_NAMESPACE class assetAssetSelector : public QDialog { Q_OBJECT public: assetAssetSelector(QWidget *parent = nullptr); protected slots: void slotAccept(); void slotReject(); private: QLabel *assetSelectLabel; QTreeView *assetSelectTree; QDialogButtonBox *buttonBox; QPushButton *okButton; QPushButton *cancelButton; QStandardItemModel *assetSelectItemModel; }; #endif // ASSETDIALOG_H
This repoduces the error on Macbook Pro Nov 2023 M3 Pro, Qt 6.6.0 (Clang 13.0 (Apple), arm64).
Thanks
-
See code here:
main.cpp
#include "mainwindow.h" #include <QApplication> int main(int argc, char *argv[]) { QApplication a(argc, argv); MainWindow w; w.show(); return a.exec(); }
mainwindow.cpp
#include "mainwindow.h" #include "assetDialog.h" MainWindow::MainWindow(QWidget *parent) : QMainWindow(parent) { assetAssetSelector dialog(this); dialog.exec(); } MainWindow::~MainWindow() { }
mainwindow.h
#ifndef MAINWINDOW_H #define MAINWINDOW_H #include <QMainWindow> #include <QtWidgets> #include <QTreeView> QT_BEGIN_NAMESPACE namespace Ui { class MainWindow; } QT_END_NAMESPACE class MainWindow : public QMainWindow { Q_OBJECT public: MainWindow(QWidget *parent = nullptr); ~MainWindow(); private: Ui::MainWindow *ui; QLabel *assetSelectLabel; QTreeView *assetSelectTree; QDialogButtonBox *buttonBox; QPushButton *okButton; QPushButton *cancelButton; QStandardItemModel *assetSelectItemModel; }; #endif // MAINWINDOW_H
assetDialog.cpp
#include "assetDialog.h" assetAssetSelector::assetAssetSelector(QWidget *parent) : QDialog(parent) { assetSelectLabel = new QLabel("Test"); assetSelectTree = new QTreeView(); assetSelectTree->setFixedSize(260, 360); assetSelectTree->setHeaderHidden(true); assetSelectTree->setDragEnabled(false); assetSelectTree->setEditTriggers(QAbstractItemView::NoEditTriggers); assetSelectItemModel = new QStandardItemModel(this); // Data would usually be added here assetSelectTree->setModel(assetSelectItemModel); assetSelectTree->setCurrentIndex(assetSelectItemModel->invisibleRootItem()->index()); assetSelectTree->expandAll(); okButton = new QPushButton(QString("&OK")); okButton->setDefault(true); cancelButton = new QPushButton(QString("&Cancel")); buttonBox = new QDialogButtonBox(Qt::Horizontal); buttonBox->addButton(cancelButton, QDialogButtonBox::RejectRole); buttonBox->addButton(okButton, QDialogButtonBox::AcceptRole); QVBoxLayout *assetSelectLayout = new QVBoxLayout; assetSelectLayout->addWidget(assetSelectLabel); assetSelectLayout->addWidget(assetSelectTree); QGridLayout *mainLayout = new QGridLayout; mainLayout->setSizeConstraint(QLayout::SetFixedSize); mainLayout->addLayout(assetSelectLayout, 0, 0); mainLayout->addWidget(buttonBox, 1, 0); setLayout(mainLayout); connect(buttonBox, &QDialogButtonBox::accepted, this, &assetAssetSelector::slotAccept); connect(buttonBox, &QDialogButtonBox::rejected, this, &assetAssetSelector::slotReject); } void assetAssetSelector::slotAccept() { /* Accept the dialog */ accept(); } void assetAssetSelector::slotReject() { /* Reject the dialog */ reject(); }
assetDialog.h
#ifndef ASSETDIALOG_H #define ASSETDIALOG_H #pragma once #include "mainWindow.h" #include <QDialog> #include <QtWidgets> #include <QTreeView> #include <string> QT_BEGIN_NAMESPACE class QLabel; class QLineEdit; class QComboBox; //class QCheckBox; class QDialogButtonBox; class QPushButton; QT_END_NAMESPACE class assetAssetSelector : public QDialog { Q_OBJECT public: assetAssetSelector(QWidget *parent = nullptr); protected slots: void slotAccept(); void slotReject(); private: QLabel *assetSelectLabel; QTreeView *assetSelectTree; QDialogButtonBox *buttonBox; QPushButton *okButton; QPushButton *cancelButton; QStandardItemModel *assetSelectItemModel; }; #endif // ASSETDIALOG_H
This repoduces the error on Macbook Pro Nov 2023 M3 Pro, Qt 6.6.0 (Clang 13.0 (Apple), arm64).
Thanks
@gsephelec
Sorry for taking so long.The
setVisible()
override ofQDialog
checks, if there is aQPushButton
at the next position in the focus chain. If it finds one, it sets it as a default button. That matches almost all use cases. But it can get in the way of aQDialogButtonBox
inside aQDialog
. In your case, the cancel button is next in the focus chain. Therefore it wins the prize and becomes default.There is a simple workaround:
Definevoid setVisible(bool visible) override;
in the (new)protected
section ofassetDialog.h
and implement it as follows:void assetAssetSelector::setVisible(bool visible) { QDialog::setVisible(visible); okButton->setDefault(true); }
It's a bit hacky, but it does the job.
You can file a bugreport if you want - you have a perfect reproducer.
I can't promise if we will actually fix it. I somehow feel, that a fix in Qt would break a lot of other stuff.