31 January, 2013

Qt Designer плагин для виджета содержащего контейнер

Иногда, довольно таки не часто, но приходится создавать для своих виджетов плагины для Qt Designer. В Assistant отлично расписанно, как создать типовой плагин, мало того Qt Creator имеет отличный wizard, который выполняет большую часть работы. НО... Иногда (совсем редко) случается так, что нужно создать не совсем стандартный плагин. Ниже я буду рассказывать о случае, когда нужен плагин для виджета содержащего внутри себя контейнер, например QFrame или любой другой. Причем необходимо, чтобы этот вложенный контейнер оставался функционален в Qt Designer, и в нем можно было по прежнему размещать другие виджеты. О тонкостях ниже.

На картинке показан виджет для которого мы будем делать плагин, вот примерный код констурктора такого виджета:
PanelWithButtons::PanelWithButtons(QWidget *parent) :
    QWidget(parent)
{
    QVBoxLayout *layout = new QVBoxLayout();
    QFrame *frame = new QFrame(this);
    frame->setObjectName(QLatin1String("InternalFrame"));
    frame->setFrameShape(QFrame::Box);
    layout->addWidget(frame, 1);
    QHBoxLayout *buttonsLayout = new QHBoxLayout();
    buttonsLayout->addWidget(new QPushButton(tr("OK"), this));
    buttonsLayout->addWidget(new QPushButton(tr("Cancel"), this));
    layout->addLayout(buttonsLayout);
    setLayout(layout);
}
Все предельно просто, а на картинке справа, то, как наш виджет будет выглядеть в Object Inspector'е.






Для того, чтобы получить такой эффект, нам необходимо внести внутренний QFrame под наблюдение Qt Designer'а, для этого потребуется:
  1. Получить инстанс QDesignerFormWindowManagerInterface, это делается в методе initialize плагина, таким образом:
    void PanelWithButtonsPlugin::initialize(QDesignerFormEditorInterface *core)
    {
        if (m_initialized)
            return;
        
        m_manager = core->formWindowManager();
        Q_ASSERT(m_manager);
    
        m_initialized = true;
    }
    Q_ASSERT нужен только для пущей уверенности при разработке.
  2. Теперь в  методе createWidget мы будем регистрировать наш фрейм:
    QWidget *PanelWithButtonsPlugin::createWidget(QWidget *parent)
    {
        PanelWithButtons *panel = new PanelWithButtons(parent);
    
        QDesignerFormWindowInterface *window = m_manager->activeFormWindow();
        if (window && parent == window->mainContainer()) {
            QFrame *frame = panel->findChild<QFrame *>();
            Q_ASSERT(frame);
    
            window->manageWidget(frame);
        }
    
        return panel;
    }
    Главная тонкость кроется в условии, дело в том, что метод createWidget вызывается в трех случаях:
    1.  Когда запускается Qt Designer.
    2.  Когда мышка начинает тащить виджет с панели на форму.
    3.  И когда виджет, наконец-то, размещается на форме.
    Так вот, регистрировать внутренний виджет нужно только в третьем случае, иначе это приведет к крашу дизайнера. Первая часть условия в коде выше отбрасывает первый случай, если  window пуст, значит речь идет о первичном запуске. Вторая часть условия обрабатывает второй случай, и значит буквально следующее: "если существует активное окно и это окно является родителем для нашего только что созданного виджета". Во втором случае виджет создается лишь для того, чтобы отображаться рядом с курсором, пока вы его тащите на форму, и этот инстанс будет уничтожен сразу же, как только виджет окажется на форме, следовательно для него нет необходимости вносить внутренний фрейм под наблюдение.
  3. Для того, чтобы все это заработало, необходимо добавить следующие заголовки: 
    #include <QDesignerFormEditorInterface>
    #include <QDesignerFormWindowManagerInterface>
    #include <QDesignerFormWindowInterface>
На этом все.

4 comments:

  1. Все равно не понимаю. Зачем это может понадобиться ?

    ReplyDelete
    Replies
    1. Для того чтобы в ваш кастомный виджет можно было вставлять други виджеты прямо в дизайнере,а не толкьо посредством C++ кода.

      Delete
  2. Здравствуйте. Встала такая же задача, основываясь на вашей статье попытался решить её, но безрезультатно. Дизайнер просто добавляет виджет на мой компонент, но не интегрирует его в определенную область. Не могли бы вы опубликовать исходники этого проекта, хочу свериться. Спасибо.

    ReplyDelete
    Replies
    1. Доброго дня, проект для которого я это делал под NDA, а демка для этой статьи давно стерта с диска. Но, как говориться, I'm pretty sure что все необходимое здесь перечисленно. Вовсяком случае для 4 версии, хотя я сомневаюсь, что в 5 что-то сильно поменялось.

      И про интеграцию, я наеюсь что вы нt забываете в Object Inspector выставить нужный вам Layout?

      Delete