Russian Qt Forum
Ноябрь 25, 2024, 14:05 *
Добро пожаловать, Гость. Пожалуйста, войдите или зарегистрируйтесь.
Вам не пришло письмо с кодом активации?

Войти
 
  Начало   Форум  WIKI (Вики)FAQ Помощь Поиск Войти Регистрация  

Страниц: [1] 2   Вниз
  Печать  
Автор Тема: Архитектура модели с параметрами различных типов  (Прочитано 13416 раз)
__Heaven__
Джедай : наставник для всех
*******
Offline Offline

Сообщений: 2130



Просмотр профиля
« : Сентябрь 14, 2018, 09:47 »

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

Сообщений: 11445


Просмотр профиля
« Ответ #1 : Сентябрь 14, 2018, 11:13 »

Аргумент setData() и возвращаемое значение data() - однозначно QVariant или его аналог (напр std::any). А вот как хранить - зависит от того сколько кода завязано на саму структуру. Может разумно начать с лобового QVariant (вместо struct row) и посмотреть сколько разборок вылезет. Если "достаточно много"  - увы, придется городить темплейтщину.
Записан
ViTech
Гипер активный житель
*****
Offline Offline

Сообщений: 858



Просмотр профиля
« Ответ #2 : Сентябрь 14, 2018, 11:37 »

Мне необходимо реализовать модель таблицы с настройками, в которой задаются параметры типа double, QString и unsigned
Как бы так организовать хранение, чтобы в data() возвращался нужный параметр и в setData() он правильно конвертировался и складывался в структуру.

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

Пока сам не сделаешь...
Fregloin
Супер
******
Offline Offline

Сообщений: 1025


Просмотр профиля
« Ответ #3 : Сентябрь 14, 2018, 14:14 »

Здесь явно через QVariant будет проще всего. Да и не так он много жрёт памяти.
Записан
__Heaven__
Джедай : наставник для всех
*******
Offline Offline

Сообщений: 2130



Просмотр профиля
« Ответ #4 : Сентябрь 14, 2018, 19:45 »

Что-то подобное хочется иметь
Записан
__Heaven__
Джедай : наставник для всех
*******
Offline Offline

Сообщений: 2130



Просмотр профиля
« Ответ #5 : Сентябрь 14, 2018, 19:54 »

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

Сообщений: 2130



Просмотр профиля
« Ответ #6 : Сентябрь 14, 2018, 20:03 »

Хотелось бы ещё, чтобы делегаты не позволяли в int записывать строку и double. Вроде, это тип QVariant определяет, если я не ошибаюсь. Сам проверять не хотел бы.
Записан
RedDog
Частый гость
***
Offline Offline

Сообщений: 221


Просмотр профиля
« Ответ #7 : Сентябрь 14, 2018, 20:41 »

Что-то подобное хочется иметь
Подобное реализовали у себя в проекте QVariant-ом. Просто, удобно, заодно он и тип еще хранит.
Записан
Igors
Джедай : наставник для всех
*******
Offline Offline

Сообщений: 11445


Просмотр профиля
« Ответ #8 : Сентябрь 15, 2018, 04:29 »

Тогда напрашивается QVariantMap (с ключами типа "Температура сплава").
Записан
ViTech
Гипер активный житель
*****
Offline Offline

Сообщений: 858



Просмотр профиля
« Ответ #9 : Сентябрь 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;
}
Записан

Пока сам не сделаешь...
Igors
Джедай : наставник для всех
*******
Offline Offline

Сообщений: 11445


Просмотр профиля
« Ответ #10 : Сентябрь 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 );
Записан
ViTech
Гипер активный житель
*****
Offline Offline

Сообщений: 858



Просмотр профиля
« Ответ #11 : Сентябрь 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() например) и, допустим, посылать сигнал, что оно одно изменилось, если такое потребуется. В Вашем решении структура изменится только целиком. Может такое поведение и лучше подходит, это уже поставленной задачи зависит.
Записан

Пока сам не сделаешь...
Igors
Джедай : наставник для всех
*******
Offline Offline

Сообщений: 11445


Просмотр профиля
« Ответ #12 : Сентябрь 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, ни лямбд, ни даже мапы Улыбающийся Но если подходить непредвзято - а чем он собсно плох? Что мы выигрываем наворачивая "рефлексию" и.т.п.? Ну разве что "применяем на практике прочитанные знания", но объективно - аж ничего  Улыбающийся
Записан
ViTech
Гипер активный житель
*****
Offline Offline

Сообщений: 858



Просмотр профиля
« Ответ #13 : Сентябрь 16, 2018, 13:57 »

Да, это просто "код начинающего". Ни std::function, ни лямбд, ни даже мапы Улыбающийся Но если подходить непредвзято - а чем он собсно плох? Что мы выигрываем наворачивая "рефлексию" и.т.п.? Ну разве что "применяем на практике прочитанные знания", но объективно - аж ничего  Улыбающийся

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

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

Пока сам не сделаешь...
sergek
Гипер активный житель
*****
Offline Offline

Сообщений: 872


Мы должны приносить пользу людям.


Просмотр профиля
« Ответ #14 : Сентябрь 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 можно сделать красиво, но я не трогаю - работает и ладно.
Записан

Qt 5.13.0 Qt Creator 5.0.1
Win10, Ubuntu 20.04
Страниц: [1] 2   Вверх
  Печать  
 
Перейти в:  


Страница сгенерирована за 0.206 секунд. Запросов: 23.