выделение пользовательского QWidgetAction при наведении - PullRequest
5 голосов
/ 10 марта 2019

Мое приложение имеет QMenuBar с числом QMenu с, каждое из которых имеет число QAction с и суб-QMenu с. Большинство QAction -элементов являются производными от QWidgetAction с повторно реализованными QWidgetAction::createWidget методами.

Обычно QAction s и QMenu подсвечиваются при наведении курсора мыши. Даже QWidgetAction не доставляет хлопот, пока здесь:

animated graphics of how highlighting is expected to work

Но как только я переопределю QWidgetAction::createWidget, чтобы вернуть пользовательский QWidget

QWidget* MyWidgetAction::createWidget(QWidget* parent) { return new MyWidget(parent); }

подсветка больше не работает. Так я и сам реализовал:

void MyWidget::set_highlighted(bool h)
{
  setBackgroundRole(h ? QPalette::Highlight : QPalette::Window);
  setAutoFillBackground(h);
}
void MyWidget::enterEvent(QEvent*) override { set_highlighted(true); }
void MyWidget::leaveEvent(QEvent*) override { set_highlighted(false); }

Однако, он не работает должным образом:

animated graphics of what goes wrong with highlighting

Я уже понял, что метод enterEvent не вызывается до тех пор, пока не будут закрыты все подменю, что происходит только с некоторой задержкой после того, как мышь покидает подменю или его действие (кстати, как я могу изменить задержку?) , То же самое с событиями перемещения мыши.

Вопрос : Как правильно переопределить выделение при наведении? Пользователь не должен замечать, что пользовательский виджет и стандарт QAction ведут себя по-разному. Что делает по умолчанию QWidgetAction::createWidget и как его воспроизвести? Я уже посмотрел на источник Qt , но это довольно запутанно.

Код для воспроизведения анимации

Фактический производственный код

Ответы [ 2 ]

3 голосов
/ 12 марта 2019

Я думаю, что причина в том, что вы не включили отслеживание мыши на вашем виджете, поэтому родительское меню не может уведомлять, что курсор мыши меняет свою позицию.

Предлагаю добавить в конструктор вашего MyWidget класса эту строку:

setMousetracking(true);

Редактировать № 1:
Я нашел уродливый трюк, но, похоже, он работает:

// You WidgetAction class
class MyWidgetAction : public QWidgetAction
{
public:
    MyWidgetAction(QObject *parent = nullptr);
    QWidget* createWidget(QWidget* parent) override {
        w = new MyWidget(parent);
        return w;
    }
    void highlight(bool hl) { w->set_highlighted(hl); }

private:
    MyWidget *w;
};

// In your code
QMenu *menu = ui->menuBar->addMenu("The Menu");
menu->addAction("Standard QAction 1");
menu->addAction("Standard QAction 2");
menu->addMenu("submenu")->addAction("subaction1");
QWidgetAction *a = new MyWidgetAction();
a->setText("My action 1");
a->setParent(menu); // Needed for the trick
menu->addAction(a);
menu->addAction("Standard QAction 3");
menu->addAction("Standard QAction 4");

// The ugly trick
connect(menu, &QMenu::hovered, this, [menu](QAction *act){
    QList<MyWidgetAction*> lCustomActions = menu->findChildren<MyWidgetAction*>();
    for (MyWidgetAction *mwa : lCustomActions){
        mwa->highlight(mwa == act);
    }
});

Я видел, что сигнал hovered всегда отправляется правильно, поэтому я подключаю его к лямбде, чтобы проверить для каждого пользовательского WidgetAction, является ли он текущим объектом поиска, и выделю вручную в этом случае.


Редактировать # 2:
Чтобы избежать цикла for в лямбде в моем первом редактировании, вы также можете создать фильтр событий для управления выделением при перемещении мыши:

class WidgetActionFilterObject : public QObject
{
    Q_OBJECT
public:
    explicit WidgetActionFilterObject(QObject *parent = nullptr);

protected:
    bool eventFilter(QObject *obj, QEvent *evt) override {
        if (evt->type() == QEvent::Type::MouseMove){
            QMouseEvent *mouse_evt = static_cast<QMouseEvent*>(evt);
            QAction *a = static_cast<QMenu*>(obj)->actionAt(mouse_evt->pos());
            MyWidgetAction *mwa = dynamic_cast<MyWidgetAction*>(a);
            if (mwa){
                if (last_wa && mwa != last_wa){
                    last_wa->highlight(false);
                }
                mwa->highlight(true);
                last_wa = mwa;
            } else {
                if (last_wa){
                    last_wa->highlight(false);
                    last_wa = nullptr;
                }
            }
        }
        return QObject::eventFilter(obj, evt);
    }

private:
    MyWidgetAction *last_wa = nullptr;
};

Тогда единственное, что вам нужно сделать, это установить фильтр событий в каждом меню, которое содержит ваш пользовательский WidgetAction:

menu->installEventFilter(new WidgetActionFilterObject(this));

И вы получите тот же результат без петли для каждого hovered сигнала.

1 голос
/ 13 марта 2019

Обратите внимание , что это не полный ответ (я не стремлюсь к этой награде), а скорее какой-то твердый фон в QMenu и как он обрабатывает виджеты.

У вас есть две проблемы:

1) Реализация по умолчанию QWidgetAction::create() ничего не делает. Вы намерены переопределить это в своей реализации, поэтому было бы удивительно, если бы ваш оригинальный код сработал .

2) После того, как вы отвергли QWidgetAction, вы столкнулись с большой проблемой: QMenu не содержит QWidgets с QLayout. Это контейнер указателей на QAction с и вектор их вычисленных QRect с. Он поддерживает состояние, но это , а не традиционный контейнер виджетов Qt.

"Но подожди!" Вы говорите, что насчет QMenuPrivate::widgetItems? Хороший вопрос. QMenu отслеживает добавленные в него виджеты, но они не сильно интегрированы. Все, что действительно делает QMenu, - это резервирует sizeHint виджета и следит за тем, чтобы он не рисовал там, где виджет , вероятно, расположен в соответствии с его sizeHint.

"Я уже пробовал это. Это не решает проблему. Как правило, события перемещения мыши, ввода и выхода принимаются, если не отображается подменю. К вашему сведению: однажды я включал mouseTracking на каждом виджет в MWE, без разницы. "

Я упоминал выше, что QMenu не интегрируется с виджетами, добавленными в него? Это то, где это начинает действительно говорить. Подменю представляют собой спагетти-код для конкретной платформы и тесно связаны с соответствующим кодом QPA QMenu. Такие вещи, как внутренние поля, смещение для рисования меню (на некоторых платформах подменю смещается на несколько пикселей), в котором в данный момент находится подменю, все они , а не , распространяются на QWidget, добавленные к меню. Бросьте в подменю, которое захватывает ваш фокус ( и создает свой собственный цикл событий!), И внезапно вы попадаете в неизведанные воды.

Если вы хотите добавить кнопку в одноуровневое меню, QWidgetAction сделает это. Действия, которые действуют как действия и отображают подменю? Вы входите на территорию "include qmenu_p.h".

"Чем больше я думаю о вашем решении, тем больше я чувствую, что это уже правильное решение. Что должно быть неправильно, чтобы QMenu отвечал за выделение своих действий? Я думаю, что мое желание сделать каждый виджет действия ответственным за свое выделение что неуместно ... "

Правильно. QMenu не уделяет особого внимания вашему виджету. Он просто создает пространство для него и общается в основном через связанные QAction.

...