Russian Qt Forum

Qt => Model-View (MV) => Тема начата: __Heaven__ от Сентябрь 14, 2018, 09:47



Название: Архитектура модели с параметрами различных типов
Отправлено: __Heaven__ от Сентябрь 14, 2018, 09:47
Привет, друзья!
Прошу подсказать.
Мне необходимо реализовать модель таблицы с настройками, в которой задаются параметры типа double, QString и unsigned
Как бы так организовать хранение, чтобы в data() возвращался нужный параметр и в setData() он правильно конвертировался и складывался в структуру.
Пока на ум приходит структура типа
Код
C++ (Qt)
struct row
{
   QString name;
   QVariant::Type expectedType;
   void MyData::* fieldOffset;
}
Пугают касты по смещению и раздутая реализация


Название: Re: Архитектура модели с параметрами различных типов
Отправлено: Igors от Сентябрь 14, 2018, 11:13
Аргумент setData() и возвращаемое значение data() - однозначно QVariant или его аналог (напр std::any). А вот как хранить - зависит от того сколько кода завязано на саму структуру. Может разумно начать с лобового QVariant (вместо struct row) и посмотреть сколько разборок вылезет. Если "достаточно много"  - увы, придется городить темплейтщину.


Название: Re: Архитектура модели с параметрами различных типов
Отправлено: ViTech от Сентябрь 14, 2018, 11:37
Мне необходимо реализовать модель таблицы с настройками, в которой задаются параметры типа double, QString и unsigned
Как бы так организовать хранение, чтобы в data() возвращался нужный параметр и в setData() он правильно конвертировался и складывался в структуру.

Хорошо бы привести побольше подробностей, желательно с примером. В каком виде существуют исходные данные, которые отображаются через модель? Что значит: "в setData() он правильно конвертировался и складывался в структуру"?


Название: Re: Архитектура модели с параметрами различных типов
Отправлено: Fregloin от Сентябрь 14, 2018, 14:14
Здесь явно через QVariant будет проще всего. Да и не так он много жрёт памяти.


Название: Re: Архитектура модели с параметрами различных типов
Отправлено: __Heaven__ от Сентябрь 14, 2018, 19:45
Что-то подобное хочется иметь


Название: Re: Архитектура модели с параметрами различных типов
Отправлено: __Heaven__ от Сентябрь 14, 2018, 19:54
Хорошо бы привести побольше подробностей, желательно с примером. В каком виде существуют исходные данные, которые отображаются через модель? Что значит: "в setData() он правильно конвертировался и складывался в структуру"?
Исходные данные собираюсь записывать/читать в QSettings, который будет работать с ini или реестром в пределах моей программы. Сейчас создал структуру для передачи параметров в различные модули вида
Код
C++ (Qt)
struct Parameters {
   double temperature;
   QString path;
   unsigned someValue;
   // ещё штук 50 параметров
};
Параметры будут записываться в текстовый файл, который будут использовать программы расчёта. Формат файла нестандартный, когда-то давно придуманный.
В setData хотелось бы, чтобы вызывался правильный метод  toDouble/toString....
Список параметров в будущем может пополниться, убавиться, перетасоваться. Не хотелось бы в связи с этим делать много правок.


Название: Re: Архитектура модели с параметрами различных типов
Отправлено: __Heaven__ от Сентябрь 14, 2018, 20:03
Хотелось бы ещё, чтобы делегаты не позволяли в int записывать строку и double. Вроде, это тип QVariant определяет, если я не ошибаюсь. Сам проверять не хотел бы.


Название: Re: Архитектура модели с параметрами различных типов
Отправлено: RedDog от Сентябрь 14, 2018, 20:41
Что-то подобное хочется иметь
Подобное реализовали у себя в проекте QVariant-ом. Просто, удобно, заодно он и тип еще хранит.


Название: Re: Архитектура модели с параметрами различных типов
Отправлено: Igors от Сентябрь 15, 2018, 04:29
Тогда напрашивается QVariantMap (с ключами типа "Температура сплава").


Название: Re: Архитектура модели с параметрами различных типов
Отправлено: ViTech от Сентябрь 15, 2018, 15:29
Исходные данные собираюсь записывать/читать в QSettings, который будет работать с ini или реестром в пределах моей программы. Сейчас создал структуру для передачи параметров в различные модули вида
Код
C++ (Qt)
struct Parameters {
   double temperature;
   QString path;
   unsigned someValue;
   // ...
};
Параметры будут записываться в текстовый файл, который будут использовать программы расчёта. Формат файла нестандартный, когда-то давно придуманный.
В setData хотелось бы, чтобы вызывался правильный метод  toDouble/toString....
Список параметров в будущем может пополниться, убавиться, перетасоваться. Не хотелось бы в связи с этим делать много правок.

Структуры данных должны в первую очередь быть удобными для использования в основной части программы, а не подстраиваться в угоду QSettings, QAbstractItemModel и прочим. Соответственно, если модулям удобно работать со структурой типа Parameters, значит такой она и должна быть. Если же это маленькая утилитка, которая что-то читает из QSettings, отображает/редактирует в QAbstractItemModel/QAbstractItemView и сохраняет в текстовый файл, то можно подумать о структуре с QVariant'ами. И я бы с опаской относился к конструкциям типа "void MyData::* fieldOffset;".

QAbstractItemModel может напрямую работать с Parameters. Помимо самой структуры Parameters нужна дополнительная информация для неё. Это имена ключей для чтения/записи в QSettings, строки для "Название" в таблице. Их можно хранить в виде статичного списка строк в каком-нибудь контейнере. Насколько я понял, в таблице есть "разделители", значит поля Parameters не отображаются "один-в-один" в строки таблицы. Чтобы строкам таблицы дать какие-то осмысленные "имена", можно для этого завести enum {TemperatureXxxx, TemperatureYyyy, TemperatureSeparator, ResultFile, ...}. Этот enum будет находиться в соответствии со списком строк "Название"  для таблицы и по нему можно делать switch по index.row() в data()/setData() для чтения/записи соответствующего поля структуры Parameters.

Да, при изменении списка параметров многое придётся исправлять руками (равно как и для структур типа row из первого поста). Чтобы было больше автоматизации, нужно думать, как по другому можно представить список параметров (например, как элементы в контейнере или в tuple запихнуть) и как они будут отображаться в строки таблицы (один маленький частный вариант использования). Не факт, что там возни будет меньше. И нужно ли это будет остальным частям программы.

Для варианта с использованием структуры типа row, можно попробовать привлечь std::function:
Код
C++ (Qt)
struct ParameterRow
{
   QString name;
   std::function<QVariant(const Parameters&)> value;
   std::function<void(Parameters&, const QVariant&)> setValue;
};

Что приведёт примерно к такому коду (возможно что-то можно ещё "укоротить"):
Код
C++ (Qt)
#include <QString>
#include <QVariant>
#include <QDebug>
 
#include <array>
#include <functional>
 
struct Parameters
{
   double   temperatureX;
   double   temperatureY;
   QString  path;
   unsigned someValue;
};
 
struct ParameterRow
{
   QString name;
   std::function<QVariant(const Parameters&)> value;
   std::function<void(Parameters&, const QVariant&)> setValue;
};
 
static const QString RowSeparator{"----------"};
 
static const std::array<ParameterRow, 5>
ParameterRows{{
   {"Temperature X",
    [](const Parameters& params) { return params.temperatureX; },
    [](Parameters& params, const QVariant& value) { params.temperatureX = value.toDouble(); }},
   {"Temperature Y",
    [](const Parameters& params) { return params.temperatureY; },
    [](Parameters& params, const QVariant& value) { params.temperatureY = value.toDouble(); }},
   {RowSeparator,
    [](const Parameters&) { return QVariant{}; },
    [](Parameters&, const QVariant&) {}},
   {"Result file",
    [](const Parameters& params) { return params.path; },
    [](Parameters& params, const QVariant& value) { params.path = value.toString(); }},
   {"Some value",
    [](const Parameters& params) { return params.someValue; },
    [](Parameters& params, const QVariant& value) { params.someValue = value.toUInt(); }}
}};
 
int main()
{
   Parameters params;
 
   ParameterRows[0].setValue(params, 1000);
   ParameterRows[1].setValue(params, 20);
   ParameterRows[3].setValue(params, "C:\\Windows\\System32");
   ParameterRows[4].setValue(params, 100500);
 
   for (const auto& row:ParameterRows)
       qDebug() << row.name << row.value(params);
 
   return 0;
}


Название: Re: Архитектура модели с параметрами различных типов
Отправлено: Igors от Сентябрь 15, 2018, 16:07
Структуры данных должны в первую очередь быть удобными для использования в основной части программы, а не подстраиваться в угоду QSettings, QAbstractItemModel и прочим. Соответственно, если модулям удобно работать со структурой типа Parameters, значит такой она и должна быть.
C "концепцией" согласен, тем более что в расчетах QVariant скорее всего окажется невыносим. Но реализация монструозна :) Думается можно обойтись куда более простыми средствами, напр чем плоха простая перегонка из структуры в контейнер QVariant и обратно?
Код
C++ (Qt)
typedef QPair<QString, QVariant> TParam;
typedef QVector<TParam> TParamVec;
 
TParamVec Param2VarVec( const ParameterRow & src );
ParameterRow VarVec2Param( const TParamVec & src );


Название: Re: Архитектура модели с параметрами различных типов
Отправлено: ViTech от Сентябрь 15, 2018, 17:10
C "концепцией" согласен, тем более что в расчетах QVariant скорее всего окажется невыносим. Но реализация монструозна :) Думается можно обойтись куда более простыми средствами, напр чем плоха простая перегонка из структуры в контейнер QVariant и обратно?
Код
C++ (Qt)
typedef QPair<QString, QVariant> TParam;
typedef QVector<TParam> TParamVec;
 
TParamVec Param2VarVec( const ParameterRow & src );
ParameterRow VarVec2Param( const TParamVec & src );

Можно и так. Тут получается, что если за исходную точку брать структуру типа Parameters, то большинство решений будет примерно "то же самое, только с боку" :). Так или иначе придётся к каждому  полю структуры обращаться отдельно. Пока в С++ не добавят рефлексию, чтобы по полям итерировать как по tuple хотя бы. Ну или особо тёмную магию применять. Опять же, нужно дополнительную информацию где-то хранить ("Название") и как-то задавать соответствие между полем структуры и номером строки в таблице.

В моих вариантах решения можно точечно изменять отдельное поле структуры (в setData() например) и, допустим, посылать сигнал, что оно одно изменилось, если такое потребуется. В Вашем решении структура изменится только целиком. Может такое поведение и лучше подходит, это уже поставленной задачи зависит.


Название: Re: Архитектура модели с параметрами различных типов
Отправлено: Igors от Сентябрь 16, 2018, 08:41
Можно и так. Тут получается, что если за исходную точку брать структуру типа Parameters, то большинство решений будет примерно "то же самое, только с боку" :). Так или иначе придётся к каждому  полю структуры обращаться отдельно.
Да, по имени/ключу (довольно популярная задача)

Пока в С++ не добавят рефлексию, чтобы по полям итерировать как по tuple хотя бы. Ну или особо тёмную магию применять. Опять же, нужно дополнительную информацию где-то хранить ("Название") и как-то задавать соответствие между полем структуры и номером строки в таблице.
Ладно, давайте еще "загрубим"
Код
C++ (Qt)
QVariant GetParamByName( const ParameterRow & row, const QString & name )
{
 if (name == str_Temperature)
  return row.temperature;
 
 if (name == str_Path)
  return row.path;
 
 if (name == str_Value)
  return row.someValue;
 
Q_ASSERT(0);  // invalid key
return 0;
}
Да, это просто "код начинающего". Ни std::function, ни лямбд, ни даже мапы :) Но если подходить непредвзято - а чем он собсно плох? Что мы выигрываем наворачивая "рефлексию" и.т.п.? Ну разве что "применяем на практике прочитанные знания", но объективно - аж ничего  :)


Название: Re: Архитектура модели с параметрами различных типов
Отправлено: ViTech от Сентябрь 16, 2018, 13:57
Да, это просто "код начинающего". Ни std::function, ни лямбд, ни даже мапы :) Но если подходить непредвзято - а чем он собсно плох? Что мы выигрываем наворачивая "рефлексию" и.т.п.? Ну разве что "применяем на практике прочитанные знания", но объективно - аж ничего  :)

Так тоже можно. Если процессор занять больше нечем. А то им сейчас скучно на своих гигагерцах, пущай хоть строки посравнивают :).

Плюс варианта с массивом ParameterRows в том, что там всё в одном месте собрано ("Название", чтение/запись поля, соответствие строке таблицы), а не разбросано по разным местам. При изменении Parameters проще исправлять и меньше шансов ошибиться/забыть. Массив формируется в compile time и инициализируется один раз во время запуска программы. И там ещё есть места для оптимизаций, это начальный вариант, что первое в голову пришло.


Название: Re: Архитектура модели с параметрами различных типов
Отправлено: sergek от Сентябрь 16, 2018, 18:56
Вариант, предложенный ViTech, мне нравится. Но для него нужно иметь шпаргалку "0 -Temperature X", "1 - Temperature Y" и т.д., 50 штук. Вполне себе рабочий вариант. Тем более, что формально расширять это применение довольно просто. Но все ж я таким шифровальным таблицам предпочитаю правила, которые проще запомнить и использовать.

Коллега, задававший вопрос, не сообщил главного - какой он хочет получить интерфейс. Если я правильно его понял, то запись и получение значений таблицы с настройками предполагается выполнять с использованием наименований параметров, исполняющих роль строковых ключей. Т.е. что-то типа setData(const QString& name, QVariant value) и QVariant data(const QString& name).
Поэтому вариант словаря типа мапы здесь мне кажется наиболее уместным. Вопрос только в том, как это организовать, чтобы использование было наиболее простым. Вопрос эффективности оставим на потом, т.к. обращение к мапе занимает значительно меньшее время, нежели файловые функции.

Я предлагаю сделать это так:
1) параметры, подлежащие хранению в файле, организовать в виде структуры (класса), аналогичной предложенной здесь Parameters;
2) создать базовый класс, например, CInterface, содержащий упомянутый словарь. Этот словарь в качестве ключа использует имя параметра, а содержит адрес (void*) этого параметра в структуре Parameters и его тип. В этом классе должны быть реализованы 3 метода:
   - setData, который по имени отыскивает нужный адрес параметра, выполняет требуемые преобразования типа и записывает в параметр значение;
   - data, который аналогично ищет, преобразовывает и возвращает значение в QVariant;
   - темплейтный метод push, который инициализирует упомянутый словарь (т.е. определяет тип и добавляет в мапу адрес параметра и его тип).
3) структура Parameters наследует этот базовый класс и имеет конструктор, в котором вызывается метод push со всеми параметрами.

Все методы базового класса должны быть реализованы для всех возможных используемых в качестве параметров типов (или для всех типов в QVariant). Поэтому при использовании различных структур Parameters (с разными параметрами) следует только определить конструкторы. Добавил параметр - нужно добавить новый push.

Реализация темплейта push примерно такая:
Код:
template<class C> void CInterface::push(C& ptr, const QString& name){
    if(typeid(ptr) == typeid(bool))                 
        записываем в мапу [name] = &ptr, bool;
    else if(typeid(ptr) == typeid(char))             
        записываем в мапу [name] = &ptr, char;
    else if(typeid(ptr) == typeid(short))           
        записываем в мапу [name] = &ptr, short;
...
}
Использование выглядит так:
Код
C++ (Qt)
struct Parameters : public CInterface
{
   double temperature;
   QString path;
   unsigned someValue;
   // ещё штук 50 параметров
   Parameters();
};
Parameters::Parameters() {
   push(temperature, "temperature");
   push(path, "path");
   push(someValue, "someValue");
}

Думаю, реализация data и setData не вызовет вопросов.
Подход не теоретический, у меня работает давно. Возможно, с использованием c++11 можно сделать красиво, но я не трогаю - работает и ладно.


Название: Re: Архитектура модели с параметрами различных типов
Отправлено: Old от Сентябрь 16, 2018, 21:04
Вариант, предложенный ViTech, мне нравится. Но для него нужно иметь шпаргалку "0 -Temperature X", "1 - Temperature Y" и т.д., 50 штук.
Так ViTech сразу предложил описать enum и никакие шпаргалки ненужны, а IDE еще будет автодополнять эти идентификаторы при наборе.

   - темплейтный метод push, который инициализирует упомянутый словарь (т.е. определяет тип и добавляет в мапу адрес параметра и его тип).
А как вы в мапе тип сохраняете, в type_info?


Название: Re: Архитектура модели с параметрами различных типов
Отправлено: sergek от Сентябрь 16, 2018, 21:05
Не удержался, попробовал сделать пример. Оказалось, что темплейтный метод очень простой:

Код
C++ (Qt)
struct CAddress {
   void* address;
   size_t type;
 
   CAddress() {
       address = nullptr;
       type = QMetaType::UnknownType;
   }
 
   CAddress(void* ptr, size_t t) {
       address = ptr;
       type = t;
   }
};
 
class CInterface
{
protected:
   QMap<QString, CAddress> parameters;
public:
   QVariant data(const QString& name);
   void setData(const QString& name, QVariant value);
   template<class C> void push(C& p, const QString& name = QString());
 
};
 
template<class C> void CInterface::push(C& ptr, const QString& name) {
   parameters[name] = CAddress(&ptr, QVariant(ptr).type());
}
Весь пример - в прикрепленном файле.


Название: Re: Архитектура модели с параметрами различных типов
Отправлено: sergek от Сентябрь 16, 2018, 21:36
А как вы в мапе тип сохраняете, в type_info?
QVariant::type()


Название: Re: Архитектура модели с параметрами различных типов
Отправлено: Igors от Сентябрь 17, 2018, 07:04
Весь пример - в прикрепленном файле.
Все-таки цеплять наследованием не очень хорошо - структура может быть вообще "незнакома с Qt"  :) Но в целом мне понравилось, спасибо

Вариант, предложенный ViTech, мне нравится. Но для него нужно иметь шпаргалку "0 -Temperature X", "1 - Temperature Y" и т.д., 50 штук.
Да, нужны enum'чики, по сути мы присваиваем каждому полю уникальное ID. Пусть его придется мапить в имя и обратно, но часто это имеет смысл, обращение по ID удобнее,  Ну и опять-таки, можно обойтись простейшими средствами, напр
Код
C++ (Qt)
QVariant GetParamByID( const ParameterRow & row, ParameterRow::TID ID )
{
switch (ID) {
 case  ParameterRow::id_Temperature:
  return row.temperature;
 
 case ParameterRow::id_Path:
  return row.path;
 
 case ParameterRow::id_Value:
  return row.someValue;
 
 default:
   Q_ASSERT(0);  // invalid ID
}
return 0;
}
 
Оформлять это в более общем виде (неизбежно раздувая, наворачивая код) - ну не знаю.. "Выйгрышь" конечно будет (если такие структуры идут бурным потоком), но насколько - хз. Спрячусь за стандартную фразу "зависит от задачи"  :)


Название: Re: Архитектура модели с параметрами различных типов
Отправлено: sergek от Сентябрь 17, 2018, 09:34
Да, нужны enum'чики, по сути мы присваиваем каждому полю уникальное ID. Пусть его придется мапить в имя и обратно, но часто это имеет смысл, обращение по ID удобнее
А пожалуйста... (c) "И". В CInterface меняем QMap на вектор, в push вместо parameters[name] используем append, забываем name и городим enum  ;) Для красоты еще можно определить operator[].
Вообще-то в оригинальном варианте у меня используется вектор, поскольку вся конструкция используется для сериализации/десериализации объектов классов при передаче по сети (есть у меня библиотека удаленных вызовов процедур). Поэтому разыменования членов класса мне не требуется. Причем, для простых типов вычисление размера тоже очень простое: QMetaType(addr.type).sizeOf().


Название: Re: Архитектура модели с параметрами различных типов
Отправлено: __Heaven__ от Сентябрь 17, 2018, 20:27
Предложения с name, кажется, не подходят. Используется 2 язычный интерфейс.

Сейчас ещё рассматриваю вариант не дешевле ли будет договориться сделать всё на виджетах...


Название: Re: Архитектура модели с параметрами различных типов
Отправлено: __Heaven__ от Сентябрь 17, 2018, 20:31
Ой. Сообразил. name это же имя в настройках :)


Название: Re: Архитектура модели с параметрами различных типов
Отправлено: Racheengel от Сентябрь 19, 2018, 18:39
У QVariantMap главный плюс - автоматический поиск по ключу, без свитчей и лямд.
Поэтому мы делали подобную штуку именно через варианты.
Разве что надо обращать внимание на типы - у QVariant есть пересекающееся подмножество с QMetaInfo, а есть не пересекающееся (косяк Qt, мож когда починять...)