Как изменить фон родительского виджета, когда дочерний виджет имеет фокус?

Я хотел бы выделить QFrame, если у одного из его дочерних виджетов есть фокус (поэтому пользователи знают, где искать курсор ;-)

используя что-то

ui->frame->setFocusPolicy(Qt::StrongFocus);
ui->frame->setStyleSheet("QFrame:focus {background-color: #FFFFCC;}");

выделяет QFrame, когда я нажимаю на него, но он теряет фокус, когда выбран один из его дочерних виджетов.

Возможные подходы:

  • Я мог бы connect() QApplication :: focusChanged (old, now) и проверить каждый новый объект, если он является дочерним элементом моего QFrame, но это становится беспорядочным.

  • Я мог бы также подклассифицировать каждый дочерний виджет и переопределять focusInEvent() / focusOutEvent() и реагировать на это, но с большим количеством разных виджетов это также много работы.

Есть ли более элегантное решение?

7
nl ja de

3 ответы

Ну, вы можете расширить QFrame , чтобы он прослушивал изменение фокуса своих дочерних виджетов. Или вы также можете установить фильтр событий на дочерние виджеты, чтобы поймать QFocusEvent .

Вот пример:

<Б> MyFrame.h

#ifndef MYFRAME_H
#define MYFRAME_H

#include 

class MyFrame : public QFrame
{
    Q_OBJECT

public:

    explicit MyFrame(QWidget* parent = 0, Qt::WindowFlags f = 0);

    void hookChildrenWidgetsFocus();

protected:

    bool eventFilter(QObject *object, QEvent *event);

private:

    QString m_originalStyleSheet;
};

#endif//MYFRAME_H

<Б> MyFrame.cpp

#include 
#include "MyFrame.h"

MyFrame::MyFrame(QWidget *parent, Qt::WindowFlags f)
    : QFrame(parent, f)
{
    m_originalStyleSheet = styleSheet();
}

void MyFrame::hookChildrenWidgetsFocus()
{
    foreach (QObject *child, children()) {
        if (child->isWidgetType()) {
            child->installEventFilter(this);
        }
    }
}

bool MyFrame::eventFilter(QObject *object, QEvent *event)
{
    if (event->type() == QEvent::FocusIn) {
        setStyleSheet("background-color: #FFFFCC;");
    } else if (event->type() == QEvent::FocusOut) {
        setStyleSheet(m_originalStyleSheet);
    }

    return QObject::eventFilter(object, event);
}

<Б> mainwindow.cpp

#include 
#include 
#include 
#include "MyFrame.h"
#include "mainwindow.h"

MainWindow::MainWindow(QWidget *parent) :
    QMainWindow(parent)
{
    setWindowTitle(tr("Test"));

    MyFrame *frame1 = new MyFrame(this);
    frame1->setLayout(new QVBoxLayout());
    frame1->layout()->addWidget(new QLineEdit());
    frame1->layout()->addWidget(new QLineEdit());
    frame1->layout()->addWidget(new QLineEdit());
    frame1->hookChildrenWidgetsFocus();

    MyFrame *frame2 = new MyFrame(this);
    frame2->setLayout(new QVBoxLayout());
    frame2->layout()->addWidget(new QLineEdit());
    frame2->layout()->addWidget(new QLineEdit());
    frame2->layout()->addWidget(new QLineEdit());
    frame2->hookChildrenWidgetsFocus();

    QHBoxLayout *centralLayout = new QHBoxLayout();
    centralLayout->addWidget(frame1);
    centralLayout->addWidget(frame2);

    QWidget *centralWidget = new QWidget();
    centralWidget->setLayout(centralLayout);

    setCentralWidget(centralWidget);
}
7
добавлено
Боюсь, это не должен быть принятым ответом. Он работает для этого простого случая, но далеко не является хорошим решением. Он очень хрупок и легко ошибается. 1) Что делать, если макет становится динамическим с добавлением или удалением дочерних виджетов в перспективе. 2) Что делать, если объект хочет установить фильтр событий на другие объекты и их фокусные события, отличные от детей, которые могут сделать что-то еще.
добавлено автор V.K., источник
@Archie Я думаю, что нужно стремиться к самому простому и универсальному решению. Предлагаемые решения не просты и универсальны. Предлагаемый дизайн чрезвычайно хрупкий, и он укусит вас позже, когда вы ожидаете его меньше всего. Как разработчик, вы можете знать ограничения в тот момент, но позже их забываете. И ваши члены команды вообще не знают о них. Они ожидают хорошие решения, которые работают в широком диапазоне условий (т. Е. Не только для идеально сферических объектов в вакууме). :) См. Мой ответ ниже. ИМХО это просто и универсально.
добавлено автор V.K., источник
@ V.K. Я думаю, вы пытаетесь привнести в это конкретные проблемы. Предлагаемое решение не должно быть универсальным. Как разработчик вы знаете ограничения своего конкретного дизайна (например, ожидаете ли вы другие фильтры событий или используете динамические макеты), поэтому вам необходимо адаптировать этот фрагмент в соответствии с вашим собственным дизайном. В любом случае, ни один из них не упоминается как предварительные условия в исходном вопросе.
добавлено автор Archie, источник
@ V.K. Я рад, что вы нацелены на лучшее решение. Это то, что нам нужно на SO. Несмотря на то, что вы опаздываете на 6 лет, ваше предложение выглядит хорошо.
добавлено автор Archie, источник
Арчи, спасибо за ответ. Можете ли вы дать мне указатель на то, как расширить QFrame для этой цели?
добавлено автор Elwood, источник
Отлично. Мне особенно понравилась идея hookChildrenWidgetsFocus() . (Но у меня немного дурная совесть, потому что я отнял Приговор от Фреда, который раньше придумал аналогичный подход. Спасибо вам, очень много!)
добавлено автор Elwood, источник

Я считаю, что оба ответа, которые вы дали, ошибочны. Они работают для простых случаев, но очень хрупкие и неуклюжие. Я считаю, что лучшее решение - это то, что вы на самом деле предложили в своем вопросе. Я бы пошел на подключение к QApplication :: focusChanged (from, to) . Вы просто соединяете свой объект основного кадра с этим сигналом, а в слоте вы проверяете, является ли объект to (тот, который получил фокус) дочерним элементом вашего объекта фрейма.

Frame::Frame(...)
{
// ...
  connect(qApp, &QApplication::focusChanged, this, &Frame::onFocusChanged);
// ...
}

// a private method of your Frame object
void Frame::onFocusChanged(QWidget *from, QWidget *to)
{
  auto w = to;
  while (w != nullptr && w != this)
    w = w->parentWidget();

  if (w == this)//a child (or self) is focused
    setStylesheet(highlightedStylesheet);
  else//something else is focused
    setStylesheet(normalStylesheet);
}

Преимущество очевидно. Этот код является коротким и чистым. Вы подключаете только один сигнальный слот, вам не нужно ловить и обрабатывать события. Он хорошо реагирует на любые изменения макета, сделанные после создания объекта. И если вы хотите оптимизировать ненужную перерисовку, вы должны кэшировать информацию о том, сфокусирован ли какой-либо ребенок, и изменить только таблицу стилей и только если это кэшированное значение изменится. Тогда решение будет префектом.

4
добавлено
См. forum.qt.io/topic/93641 о том, как исправить потенциальные проблемы с воспитания модальные окна. По сути, вы должны передать экземпляр окна верхнего уровня (обычно QMainWindow ) в качестве родителя, например, QDialog . В противном случае стандартные кнопки в QDialog будут застревать и т. Д. Это верно, по крайней мере, для Qt 5.11.
добавлено автор 8day, источник

Во-первых, создайте простой подкласс QFrame , который повторяет виртуальную функцию eventFilter (QObject *, QEvent *) :

class MyFrame : public QFrame {
    Q_OBJECT

public:
    MyFrame(QWidget *parent = 0, Qt::WindowFlags f = 0);
    ~MyFrame();

    virtual bool eventFilter(QObject *watched, QEvent *event);
};

Используйте MyFrame вместо QFrame , чтобы содержать ваши виджеты. Затем, где-то в вашем коде, где вы создаете виджеты, содержащиеся в MyFrame , установите фильтр событий для этих виджетов:

   //...
    m_myFrame = new MyFrame(parentWidget);
    QVBoxLayout *layout = new QVBoxLayout(myFrame);
    m_button = new QPushButton("Widget 1", myFrame);

    layout->addWidget(m_button);
    m_button->installEventFilter(myFrame);
    //...

В этот момент MyFrame :: eventFilter() будет называться до , любое событие будет доставлено в виджет, позволяя вам действовать, прежде чем виджет узнает об этом. В MyFrame :: eventFilter() верните true , если вы хотите отфильтровать событие (т. Е. Вы не хотите, чтобы виджет обрабатывал событие) или возвращали < code> false в противном случае.

bool MyFrame::eventFilter(QObject *watched, QEvent *event)
{
    if (watched == m_button) {//An event occured on m_button
        switch (event -> type()) {
            case QEvent::FocusIn:
               //Change the stylesheet of the frame
                break;
            case QEvent::FocusOut:
               //Change the stylesheet back
                break;
            default:
                break;
        }
    }

    return false;//We always want the event to propagate, so always return false
}
2
добавлено
В вашем конкретном случае вам это не нужно, но MyFrame может захотеть просмотреть несколько виджетов для нескольких разных событий, поэтому простая проверка на watched позволяет узнать, какой виджет (например, вы хотите использовать другой цвет фона в зависимости от того, какой виджет имеет фокус)
добавлено автор Fred, источник
Это то, что я искал, спасибо, Фред! Есть ли причина для if (смотрели == m_button) в eventFilter() ? Он отлично работает, и я могу добавить столько виджетов, не заботясь ...
добавлено автор Elwood, источник
Арг, почему я не могу принять два ответа? Вы и Арчи работали одновременно, чтобы помочь мне, вы оба пришли к аналогичному решению. Мне жаль, что я, наконец, принял ответ Арчи, поскольку его решение было немного более «полным». Но ваши объяснения были лучше. Надеюсь, вы можете простить меня ...;)
добавлено автор Elwood, источник
Qt
Qt
703 участник(ов)

Группа взаимопомощи Qt.Делимся советами и помогаем друг другу. Наш информационный канал @ProQt Полезная информация для начинающих: #newcomer Познавательная информация: #fyi #didyouknow Наши друзья: Чат с/с++ @ProCxx