Russian Qt Forum

Qt => Кладовая готовых решений => Тема начата: kdm от Апрель 03, 2011, 23:56



Название: Облегчение создания сложных виджетов с контейнерами в Qt Designer
Отправлено: kdm от Апрель 03, 2011, 23:56
Если у вас сложный виджет вроде панели с заголовком (как во вложении), а ниже заголовка -контейнер для виджетов, которые в него надо добавлять в дизайнере и надо таких панелей много, то чтобы не писать все вручную (добавление виджетов в контейнер), а визуально все лицезреть и руками добавлять в контейнер виджеты, можно сделать небольшой хак.

Для таких целей правильнее сделать плагин для дизайнера и правильно все делать все тогда... но у нас нет времени или мы не знаем, как это делать, или и то и другое (как например, у меня (плюсуем "влом").
Тогда есть в дизайнере возможность преобразовать виджет к нужному типу (правой кнопка мыши по виджету и "преобразовать в", указываем имя класса и заголовочный файл, жмемь добавить, преобразовать). Надо отметить, что дизайнер будет генерировать код, который как бы работает с QWidget, но на самом деле использует в качестве имени класса тот, который мы указали. Поэтому с этим мы и поиграемся.

Итак поместим объект QWidget на форму, преобразуем его в WidgetsGroup (который будет представлять из себя панель с заголовком и контейнером для кнопок, лейболов... чего угодно.

Напишем этот класс (я сделал это, добавивь класс формы, как во вложении).
Приведем код заголовочного файла класса.
Код
#ifndef WIDGETSGROUP_H
#define WIDGETSGROUP_H
 
#include <QWidget>
#include <QVBoxLayout>
 
namespace Ui {
   class WidgetsGroup;
}
 
class WidgetsGroup : public QWidget
{
   Q_OBJECT
public:
   explicit WidgetsGroup(QWidget *parent = 0);
   ~WidgetsGroup();
 
   void addWidget(QWidget* widget);
private:
   Ui::WidgetsGroup *ui;
};
 
#endif // WIDGETSGROUP_H

И приведем файл реализации. Все в принципе обычно, как делает креатор по умолчанию.
Код
#include "WidgetsGroup.h"
#include "ui_WidgetsGroup.h"
 
#include <QChildEvent>
#include <QVBoxLayout>
#include <QDebug>
#include <QCoreApplication>
 
WidgetsGroup::WidgetsGroup(QWidget *parent) :
   QWidget(parent),
   ui(new Ui::WidgetsGroup)
{
   ui->setupUi(this);
}
 
WidgetsGroup::~WidgetsGroup()
{
   delete ui;
}

Теперь будем думать. Для того, чтобы дизайнер, а если быть точнее UI Compiler сгенерировал код, который бы сам добавлял виджеты в контейнер Contents, то было бы очень просто, мы просто добавили метод setLayout в этот класс, с помощью которого бы потом все виджеты добавлись куда нужно. Но код генеруется примерно такой:
Код
      widget = new WidgetsGroup(frame);
       widget->setObjectName(QString::fromUtf8("widget"));
       verticalLayout_2 = new QVBoxLayout(widget);
То есть назначение лейаута идет в указании парента в конструкторе лейаута. Это немного все усложняет. Но только немного.

При добавлении чайлда к потомкам QObject (коим является и этот класс) происходит событие childEvent. Переопределим метод класса, который обрабатывает это событие.
Код
void WidgetsGroup :: childEvent (QChildEvent* event)
{
 
}
Поместим туда определение название класса, который добавлеятся в качестве чайлда и, если он QObject, то наверняка это тот самый потомок QLayout, которого добавляет код формы к нашему классу вместе с виджетами чайлдами (кнопками, лейблами...). Если все-таки это виджет, то это те самые кнопки и лейблы.
Код
void WidgetsGroup :: childEvent (QChildEvent* event)
{
       if (event->added())
       {
           if (event->child()->metaObject()->className() == QString("QObject").toLatin1())
           {
           }
           else if (event->child()->isWidgetType())
           {
           }
       }
}
 
Здесь отметим, что если мы проведем реперант лейаута сразу:
    
Код
ui->ContentsFrame->setLayout(static_cast<QLayout*>(event->child()));
То наткнемся на сег. фолт, который будет происходит, когда что-то в мутексе пытается обратиться к полю d->recursive и т.д. (глубокая отладка ничего прояснить не дала по этому поводу) в одном из файлов Qt. Поэтому сразу репарент производить не следует, для этого воспользуемся сигнал-слотовым механизмом Qt, т.к. postEvent(Contents, child) тоже не дал нужных результатов.
Добавим к классу сигнал wantAddLayout (QLayout* layout), который сигнализирует о том, что класс хочет добавить лейаут в свой виджет-контейнер Contents:
Код
     void wantAddLayout(QLayout* layout);
И прайвет-слот, который будет принимать этот сигнал:
Код
   private slots:
   void addLayout (QLayout* layout);
Теперь свяжем в конструкторе сигнал со слотом, используя тип соединения QueuedConnection, что позволит отложить на время репарент, дав тем самым всему что генерировало сег. фолт "инициализировывывы...ваться".
Код
connect(this, SIGNAL(wantAddLayout(QLayout*)), this, SLOT(addLayout(QLayout*)), Qt::QueuedConnection);
В обработчике события childEvent добавим следующие строки
Код
void WidgetsGroup :: childEvent (QChildEvent* event)
{
       if (event->added())
       {
           if (event->child()->metaObject()->className() == QString("QObject").toLatin1())
           {
               emit wantAddLayout(static_cast<QLayout*>(event->child()));
               event->ignore();
           }
           else if (event->child()->isWidgetType())
           {
               event->ignore();
           }
       }
   }
Теперь надо подумать о том, что не только тот код дизайнере захочет добавить лейаут к виджету, который мы будем использовать позже для дизайна главной формы, но еще и код, который располагает виджеты внутри этого класса по местам. Поэтому добавим флаг, означающий что эта форма построена и все последующие добавления чайлдов это уже из главной формы.
Код
    bool uiwasntbuilt;
Используем этот флаг в конструкторе:
Код
WidgetsGroup::WidgetsGroup(QWidget *parent) :
   QWidget(parent),
   ui(new Ui::WidgetsGroup)
{
   uiwasntbuilt = true;
   ui->setupUi(this);
   uiwasntbuilt = false;
     connect(this, SIGNAL(wantAddLayout(QLayout*)), this, SLOT(addLayout(QLayout*)), Qt::QueuedConnection);
}
  ... и в обработчике события:
Код
void WidgetsGroup :: childEvent (QChildEvent* event)
{
   if (!uiwasntbuilt)
   {
       if (event->added())
       ...
   }
   else
   {
       event->accept();
       QWidget::childEvent(event);
   }
}
Таким образом все готово. Можно использовать этот класс для того, чтобы мутить много панелей в главной форме и в них располагать виджеты-кнопки, лейблы... (как показано во вложении)

Код и исполняемый файл релиз-версии находится двумя постами ниже (из-за переполнения вложений для этого поста).


Название: Re: Облегчение создания сложных виджетов с контейнерами в Qt Designer
Отправлено: Fat-Zer от Апрель 04, 2011, 00:18
а можно выложить полный код, а то здесь буковок очень много читать их не хочется, а по коду посмотреть, что это, будет быстрее.


Название: Re: Облегчение создания сложных виджетов с контейнерами в Qt Designer
Отправлено: kdm от Апрель 04, 2011, 00:22
Делал в проекте одном вырежу оттуда все ненужное и вложу в первый месседж код. Просто решил сделать запостить такую муть, а вот типа что вы думаете, потом подумал, что можно сделать пост для кладовой готовых решений.
Edit: Вложения в первом посте переполнились, поэтому выкладываю тут.

Краткий очерк, зачем это делал: нужны были панельки с заголовками, в которых определенная функциональность и одновременно с этим нужно было легко размещать все элементы внутри них в дизайнере. Плагин для дизайнера писать не хотелось, никогда не пробовал, не знаю как делать, сколько времени уйдет, вот узнав, про то, что можно преобразовывать виджеты в дизайнере, решил сделать такой вот хак, чтобы и класс панельки сделать и виджеты внутрь нее запихнуть из дизайнера.

Так вот, чтобы думаете по поводу такового вот подхода?


Название: Re: Облегчение создания сложных виджетов с контейнерами в Qt Designer
Отправлено: kdm от Апрель 04, 2011, 01:04
Есть готовые решения для панелек, например, Sintegrial Action Panel, однако в дизайнере, добавляемые в них добавляемые виджеты просто плавают, ни лейаут не задать, ни как-то упорядочить и так и появляются. А писать плагин к дизайнеру дело муторное, потому что для дизайнера можно и mingw собирать, а для модуля дизайнера в креаторе надо студией, либо пересобирать креатор, при чем возникали траблы сборки.