- PVSM.RU - https://www.pvsm.ru -

В Qt существует замечательная вещь — Q_PROPERTY, которая позволяет добавить необходимое свойство к любому QObject классу. Но в некоторых случаях пользоваться ими неудобно.
Например, у вас есть приложение, в котором существуют (или можно создавать) много различных объектов и, которые необходимо настраивать (выдавать диалог со свойствами выбранного объекта). Примером таких приложений может быть почти любая инженерная программа, редактор векторной графики, Visual Studio в конце концов.
Итак, существует множество объектов, которые пользователь может «настраивать». Логично иметь для этого какой-то универсальный механизм, ведь иначе придется для каждого объекта реализовывать свой собственный диалог настройки. Q_PROPERTY для этого не совсем подходит. Во-первых, нет стандартного виджета для редактирования Q_PROPERTY. Во-вторых, даже если вы воспользуетесь сторонним виджетом для этих целей, то наряду с вашими свойствами, будут показываться стандартные (как минимум objectName). Далее, Q_PROPERTY не поддерживает иерархии (иногда полезно сгруппировать связанные свойства); нет description (текстовая строка, объясняющая данное свойство); нет никаких средств для настройки внешнего вида Q_PROPERTY в GUI (для улучшения usability).
Для программиста иногда удобно работать со свойствами, как с отдельным объектом, независимыми от объекта-владельца. Их тогда можно передавать куда-то, не передавая весь объект-владелец целиком, или даже передавать отдельную подгруппу свойств.
Пытаясь сделать удобные свойства, я создал проект QtnProperty [1]. Далее я расскажу чем хорош этот проект и как этой библиотекой пользоваться.
Прежде всего библиотека состоит из нескольких частей:
Прежде всего, библиотека определяет два основных класса QtnProperty и QtnPropertySet. Первый является базовым для всех типов свойств (например QtnPropertyBool или QtnPropertyFloat), а второй представляет группу свойств. И QtnProperty и QtnPropertySet имеют общего родителя QtnPropertyBase, который, в свою очередь, является потомком QObject. В QtnPropertyBase классе определено всё, что есть общего между QtnProperty и QtnPropertySet:
QtnPropertySet класс содержит еще список дочерних свойств и методы работы с ними. Для каждого типа свойства (например целочисленного) существует два класса (QtnPropertyInt и QtnPropertyIntCallback). Первый хранит значение в классе, а второй вызывает некоторую функцию для получения или сохранения значения.
Вот список свойств, который реализован непосредственно в модуле QtnPropertyCore:
Все численные свойства (QtnPropertyInt, QtnPropertyUInt, QtnPropertyFloat, QtnPropertyDouble) имеют три дополнительных метода:
В этом модуле содержатся два виджета и делегаты свойств.
Класс QtnPropertyView реализует древовидный виджет для набора свойств.
Класс QtnPropertyWidget является составным и содержит QtnPropertyView и QLabel в качестве нижней панели.

Что бы поменять внешний вид или поведение некоторого отдельного свойства, были введены делегаты. Например, для QtnPropertyBool существуют два делегата: QtnPropertyDelegateBoolCheck и QtnPropertyDelegateBoolCombobox. Первый отображает checkbox, а второй — combobox с двумя настраиваемыми значениями (для значений true и false). Можно создавать свои делегаты и выставлять их в качестве дефолтных, тогда все свойства данного типа будут отображаться с помощью нового делегата. Иногда легче и удобнее создавать новый тип делегата, чем новый тип свойства. Например, свойство для хранения пути к файлу реализовано через специальный делегат QtnPropertyDelegateQStringFile для QtnPropertyQString.
Для примера рассмотрим небольшой набор свойств для текстового редактора.
Вот что мы хотим показать пользователю.

А вот что нужно сделать программисту, что бы получить такой property set:
QtnPropertySet* textProperties = new QtnPropertySet(owner);
QtnPropertyBool* enableWrapping = new enableWrapping(textProperties);
enableWrapping->setName(tr("enableWrapping"));
enableWrapping->setDescription(tr("Enable/disable text wrapping"));
enableWrapping->setValue(true);
QtnPropertyQColor* textColor = new QtnPropertyQColor(textProperties);
textColor->setName(tr("textColor"));
textColor->setDescription(tr("Foreground text color"));
textColor->setValue(QColor(0, 0, 0));
QtnPropertySet* Tabulation = new QtnPropertySet(textProperties)
Tabulation->setName(tr("Tabulation"));
Tabulation->setDescription(tr("Tabulation settings"));
QtnPropertyBool* replaceWithSpaces = new QtnPropertyBool(Tabulation);
replaceWithSpaces->setName(tr("replaceWithSpaces");
replaceWithSpaces->setDescription(tr("Automatically replace tabs with spaces"));
replaceWithSpaces->setValue(false);
QtnPropertyUInt* tabSize = new QtnPropertyUInt(Tabulation);
tabSize->setName(tr("tabSize"));
tabSize->setDescription(tr("Number of spaces to be placed."));
tabSize->setState(QtnPropertyStateImmutable);
tabSize->setValue(4);
tabSize->setMinValue(1);
tabSize->setMaxValue(10);
// создаём обработчик сигнала propertyDidChange
auto lambda = [tabSize, replaceWithSpaces](const QtnPropertyBase* changedProperty,
const QtnPropertyBase* firedProperty,
QtnPropertyChangeReason reason) {
// если изменилось значение
if (reason & QtnPropertyChangeReasonValue)
{
if (*replaceWithSpaces)
// делаем tabSize изменяемым
tabSize->removeState(QtnPropertyStateImmutable);
else
// делаем tabSize не изменяемым
tabSize->addState(QtnPropertyStateImmutable);
};
QObject::connect(replaceWithSpaces, &QtnProperty::propertyDidChange, lambda);
// и еще код для настройки делегата replaceWithSpaces
...
Чтобы облегчить жизнь программистам (или даже дать возможность описывать свойства не-программистам) был разработан небольшой кодо-генератор QtnPEG.
Вот так выглядит описание нашего примера в Text.pef файле:
property_set Text
{
Bool enableWrapping
{
description = "Enable/disable text wrapping";
value = true;
}
QColor textColor
{
description = "Foreground text color";
value = QColor(0, 0, 0);
}
property_set Tabs Tabulation
{
description = "Tabulation settings";
Bool replaceWithSpaces
{
description = "Automatically replace tabs with spaces";
value = false;
// устанавливаем специальный делегат
delegate ComboBox
{
// назначаем текст для значения true
labelTrue = "On";
// назначаем текст для значения false
labelFalse = "Off";
}
// определяем код слота для сигнала replaceWithSpaces.propertyDidChange
slot propertyDidChange
{
tabSize.switchState(QtnPropertyStateImmutable, !replaceWithSpaces);
}
}
UInt tabSize
{
description = "Number of spaces to be placed.";
state = QtnPropertyStateImmutable;
value = 4;
}
}
}
Достаточно просто и лаконично. QtnPEG создает два файла: Text.peg.h и Text.peg.cpp, где находятся два класса. QtnPropertySetText и QtnPropertySetTabs — классы наследники от QtnPropertySet.
В сгенерированных классах можно увидеть следующую декларацию подсвойств, например для QtnPropertySetText:
// start children declarations
QtnPropertyBool& enableWrapping;
QtnPropertyQColor& textColor;
QtnPropertySetTabs& Tabulation;
// end children declarations
Таким образом программист в C++ коде может работать с этим классом, как со структурой с полями. Например:
void doSomething(const QtnPropertySetText& textParams, QString& text)
{
if (textParams.Tabulation.replaceWithSpaces)
{
QString spaces(QChar::Space, textParams.Tabulation.tabSize);
text.replace(QChar::Tabulation, spaces);
}
}
Надеюсь я смог дать некоторое представление о моем проекте.
Буду рад, если кому-то он пригодится, особенно там, где есть много настроек и разрабатывать GUI для всего и вся накладно. Если, по вашему мнению, для полноценного использования QtnProperty [1] не хватает какой-то функциональности, прошу комментировать.
Автор: lexxmark
Источник [2]
Сайт-источник PVSM.RU: https://www.pvsm.ru
Путь до страницы источника: https://www.pvsm.ru/open-source/58113
Ссылки в тексте:
[1] QtnProperty: https://github.com/lexxmark/QtnProperty
[2] Источник: http://habrahabr.ru/post/217203/
Нажмите здесь для печати.