Qt: крик души

Как-то я привык, что в Qt есть все, что нужно разумному человеку, но жизнь оказалась богаче.

Представим себе какой-то обычный GUI(Windows)-диалог с настройками. Там галочки, пимпочки, выпадающие списки, общим числом с полсотни-сотню, например (5 табов по 10-15 параметров, так и получится).

Ну, понятно, по OK значения в программе апдейтятся, по Cancel - остаются как были, а значит нужен какой-то backing store и в Qt для этого есть, вроде как, Q_PROPERTY.

Сам диалог мы берем и рисуем за 5 минут (или больше, если 5 табов) в Qt Designer. И нам даже породят код для его генерации (главное в этот код не смотреть!) и будут этот код апдейтить если мы что-то передизайнили. Или даже возьмем QUILoader и код не нужен, нарисуем форму динамически, взяв ui-файл из ресурсов.

А вот дальше начинается сущее мучение:

  • Для каждого элемента надо написать Q_PROPERTY(..) (можно и без нее, но сохранение-чтение с пропертями проще).
  • Для каждой проперти - getter/setter ну и саму переменную для стораджа.
  • Значения надо проинициализировать (в конструкторе). Готового механизма "списком" (key-value) нет. Ну, есть полуготовый, можно QMap<QVariant>, но тоже ничего хорошего.
  • Нужно элемент диалога инициализировать из проперти перед показом диалога; нужно значение проперти обновлять, если в диалоге было нажато ОК.
  • Нужен какой-то механизм сохранения значений/восстановления (внешнее хранение настроек). Опять, детали механизма есть, а готового - нет.
И так - 75 (или сколько там настроек) раз. Ну то есть сериализацию/десериализацию можно циклом, затем и Q_PROPERTY, а все остальное - ручками.

Или не ручками, скажите мне, что я пропустил что-то в Qt!!!

Нет, я понимаю, что цивилизованный мир сажает на эту работу индуса (а нецивилизованный - студента), тот легко напишет 75 геттеров-сеттеров методом Copy-Paste, по 750 штук в день. Но ведь не может быть, чтобы это место не было уже автоматизировано в Qt?

Comments

perl?

Поясни подробнее?

программой на перле генерировать необходимый код

Индус на перле, понимаю!
Можно наверное и C-шным препроцессором обойтись по меньшей мере для половины задачи.

Но меня удивляет другое: для всех типовых задач в Qt уже есть готовые решения (так было по сей день). Если решения нет, то значит задача нетиповая, а я ломлюсь напролом через стену в метре от ворот.

Мне, вот, пришлось вручную сделать базовый класс для всех окон с настройками, перехватывать сигналы и по имени объекта и типу контрола вынимать значение и класть в настройки

Выкрутиться можно: геттеры-сеттеры препроцессором, все остальное - тоже более-менее можно вроде бы. Естесвенно, accept/reject перехватить.

Меня удивляет, что в этом месте нет готового решения, типовая же задача.

Вроде бы с геттерами-сеттерами это место подперто в Qt Creator, надо посмотреть повнимательнее..

В MFC, помнится, было то же самое...

То же самое - это "все ручками"?

Ну ерунда же какая-то, типовая же задача.

Можно мышкой натыкать. Тогда и переменная, и в конструкторе будет сразу, отмена, но это не всегда удобно, чаще руками лучше.

Вообще в Qt по мере развития, некоторые вещи имели свойство усложняться (в сторону лишнего гемора). Тут наверное надо привыкать..

А может это в грид какой-нить запихать и шут бы с ним?

Если честно, меня как пользователя 75 с гаком параметров править через ГУИ (да еще и разнесенные по разным вкладкам) - этож паника, анархия и технофашизм в одном флаконе. С этой точки зрения даже конфиг в XML смотрится уже неплохой идеей. Но тут, понятно, каждый сам себе злой чебурах.

75 - это для примера. У меня крик души настал на втором десятке, при том что 4 однотипных я сделал таки массивом.

Но вот у фотошопа (сложная программа) или там acdsee или, чтобы не про графику, у Foxit reader (посмотрел программы, окна которых открыты) - их больше, чем по 75 у каждой программы.
И ничего - у фотошопа я правлю единичные настройки, у acdsee - только quick view выключаю, Foxit reader - никогда ничего не правил вообще :)

Но при этом - найдется 1 или 5% пользователей, которые таки что-то поправят (и все - разное), поэтому параметры таки нужны.

Ну понятно. А в сторону QML, кторое якобы из себя все декларативное, не смотрели?

Как я уже написал, решил не заморачиваться ради одного диалога.

Но посмотрю со временем, выглядит вкусно.

Сделать как в firefox about:config

У FF для основных настроек есть человечий интерфейс... :)

Ну вот и сделай для десятка настроек - человечий, и все остальное в advanced, в grid.

У нас есть система с ~10 тыс настроек.
Взвод индусов сажать?

Да, я кстати, подобное делал на метаописании. Т.е. есть XML-ник с перечислением виджетов, по ним в рантайме создаются объекты (классов же много меньше, правда). UILoader делает тоже самое, в принципе, но там надо руками двигать/привязывать, а это не всегда удобно. Да и компоненты кастомные были большей частью, с заточкой на тачскрин.

Мне казалось что у них появился какой-то механизм взять это из XML-я, и запихнуть туда же

Если нет написать один раз и забыть

КОгда мы написали целую морду биллинга на Qt (и несколько отдельных АРМов на её базе), то да, пришлось по мере использования дописывать под себя кирпичи

Описание формы - да. Всякий backing store - я не нашел. Нашел feature request для Qt creator, дескать неплохо бы для Q_PROPERTY порождать готовый код. И вроде даже сделали, но креатором я не пользуюсь.

Там есть новый механизм, который Qt Quick (QtDeclarative): описание на некоем мета-языке, похожем на JSON (а может это он и есть) и яваскриптом посыпано сверху. И да, это хорошая идея т.к. из плюсов все видно, но ради одного диалога Preferences я не стал затеваться с этим пока.

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

Мы правда, тогда даже дизайнером не пользовались, кроме как для макетирования, чисто самому в коде писать получалось быстрее и компактнее, благо быстро своих оберток понарисовали для списков-селекторов и т.д.

Ну я тоже дизайнером не пользовался до сих пор - а тут решил, что будет удобно т.к. диалог Preferences имеет свойство постепенно расти.

Пока не въехал, стоит ли оно того. Но интересно же.

Кстати, а Dynamic Property не то, что нужно?

В смысле - без геттеров-сеттеров, default - в гуе, а остальное - так же? Ну да,наверное.

QMap - тоже вариант (и будут нормальные operator =)

Там есть Dynamic Property, которые вообще не объявляются. Оно создаётся во время QObject::setProperty и чтение только через QObject:property().
Если нужна реакция на изменение, то на объекте генерится QEvent::DynamicPropertyChange .
http://svn.webkit.org/repository/webkit/trunk/Source/WebKit/qt/Api/qwebp...
dynamicPropertyChangeEvent()

Я не вижу принципиальной разницы с QMap.

Мой вопрос был в другом: казалось бы задача (диалог с кнопками + переменные связанные с кнопками и обновляемые по OK + сохранение в registry/whatever) - абсолютно типовая. Почему нет готового решения?

> Я не вижу принципиальной разницы с QMap.

Дык, в QObject оно уже есть. Зачем ещё? ;)

> Мой вопрос был в другом: казалось бы задача (диалог с кнопками + переменные связанные с кнопками и обновляемые по OK + сохранение в registry/whatever) - абсолютно типовая.
> Почему нет готового решения?

А ХЗ.
Я когда в последний раз диалог рисовал делал это крейтором. Оно мне полей понагенерило. Может это и есть предлагаемое решение.

1. зачем backing store? брать из виджетов по нажатию OK

2. я в qt designer вижу кнопки promote to.... и потом можно добавлять проперти, правда почему-то только строки или bool. немного через зад, но в принципе похоже на то что надо.

1) А по нажатию Cancel? Восстанавливать виджеты из сохраненной копии? Ну да, наверное можно, хотя не нравится.

2) Добавлять проперти можно и так (к кнопке/чекбоксу/whatever), но что толку то? Там будут некие константы, связать их со значениями в виджете только по нажатию ОК - да, можно в базовом классе, но опять вручную....

По нажатию cancel не делать ничего.

Ещё раз повторю всё: новые значения лежат в виджетах, если диалог accepted() (нажата OK) или нажата кнопка Apply - достаются оттуда, применяются и сохраняются в конфиге. Если rejected() (нажата cancel) - просто не делать ничего.

С пропертями я хорошего способа не знаю, просто натыкал в designer. Сам когда надо делаю руками да.

Ещё можно поиграть с моделями, иногда даже не табличные данные хорошо в них ложатся. А уж если есть однообразные данные - тем более.

Кроме конфига есть еще собственно сама программа, которая пользуется этими значениями на рантайме (например, если открыли новый файл, надо сходить в значения preferences и посмотреть, что там показывать).

Она может брать эти значения
а) из контролов (control->value() или control->isChecked())
б) из каких-то переменных в программе.

Вот второй случай - это и есть backing store. А первый - нехорош тем, что cancel мы нажали, а значения контролов от этого - не изменились.

а, это конечно да, надо хранить.

а зачем вообще qt проперти, почему нельзя это обычными плюсовыми полями делать?

Чтобы поэкономить на сериализации, вместо перечисления всех полей в коде - цикл.

При этом сами пропертя могут быть и плюсовыми полями, конечно

Я налабал live templates в qtcreator, в три щелчка добавлялись пропертисы.

Отвлечённо (но чуть в тему) -- а никаким IDE не пользуешься?
Я Intellij Idea юзаю, и оно умеет автоматом клепать геттеры/сеттеры (и реорганизацию, если что, одной кнопкой можно делать).

Visual C++ 2010 и тамошняя IDE. +Visual Assist X.

А рефакторинга по одной кнопке я боюсь.

Я обычно перед началом использования всякого UIшного инструмента делаю себе примочки для этих целей. Т.е. типичное решение - табличка, UIшный код, адрес переменной - куда класть и всякие флаши (формат и т.д.)

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

Например мне недавно пришлось убеждать одного морального индуса в том, что большую часть наговнокоженного кода для считывания кучи типовых значений из файла специального формата(есть библиотека, но не суть), можно выкинуть в трубу.
Но вы представьте, человек с десятилетним стажем C++ спрашивает - (суть, не дословно) "а как ре-использовать, ведь имена переменных разные". Он даже сначала не сразу понял, когда я ему показал как можно std::map использовать для "динамического создания" переменных.
А вы говорите "Для каждого элемента надо написать Q_PROPERTY(..)". Там блядь для сотен значений, с от силы с четырьмя разными типами, для каждого был написан, нет даже скопипастен метод с изменением имени переменной... А ведь эти методы ещё надо вызывать, и что вы думаете? сотня "if(name==v1){}else" - это блядь 21 век!

Статическое написание сотен копипастов имеет то преимущество, что имена переменных (методов и прочего) проверяются на этапе компиляции.
А динамически будет в одном месте map.set("var1",value), а в другом - map.get("var1_with_mistype"), особенно это весело, если используется расширенный get с default value.
Ну то есть понятно как лечить, ключ должен быть enum или чем-то подобным, но тоже полностью не спасет.

Ну и реальность в Qt похоже (не пробовал, но похоже), такая, что QtDeclarative - и есть ответ.

Да, на счёт текстовых имён переменных я тоже думал, и тоже пришёл к варианту с enum'ом, как раз из-за возможности уползания ошибок из компиляции в рантайм.
Кстати, из-за той копирастии методов, была не одна ошибка - то есть всё типично, скопоровали но не всё поменяли, что и следовало ожидать..

Ну да, или enum или дефайн с кавычками(а ключ к мэпу - всегда без кавычек).
Но как-то это все безобразно скучно и муторно.

Копирастию методов легко сделать через дефайн, где и имя метода и имя переменной - все будет браться из одного слова, отчего проблем будет меньше.

Но проблемы на использовании никуда не денутся, если кто-то написал getVar7 вместо getVar17, то эту багу хрен поймаешь.

Копирастию методов легко сделать через дефайн, где и имя метода и имя переменной - все будет браться из одного слова, отчего проблем будет меньше.

там не всё так просто было - одним дефайном в том конкретном случае не отделаться - их нужно было много, так как всё было жутко размазано. В итоге чтобы добавить новый параметр (обычного типа) в формат файла (то есть в модуль считывания), нужно было изменить больше 10 (!) мест.

Но проблемы на использовании никуда не денутся, если кто-то написал getVar7 вместо getVar17, то эту багу хрен поймаешь.

До рефакторинга такая проблема могла возникнуть внутри модуля считывания файла (ну и, конечно, снаружи) - так как надо было вручную добавлять элемент в длинющую "if(){}else" и ещё в кучу мест.
А после рефакторинга проблема такого рода может возникнуть только вне модуля считывания файла (при использовании считанных данных). да и нет там таких имён..

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

Я в рантайме использовал setProperty. Кода в классе для свойства не было.
http://doc.qt.nokia.com/4.7-snapshot/qobject.html#setProperty

Лол. Используйте QSettings и будет вам счастье

Причем тут это?

The QSettings class provides persistent platform-independent application settings.

А я вот спрашиваю про

Представим себе какой-то обычный GUI(Windows)-диалог с настройками. Там галочки, пимпочки, выпадающие списки, общим числом с полсотни-сотню, например (5 табов по 10-15 параметров, так и получится).

Ну и дальше там.

Как вот QSettings помогут поставить Defaults по кнопке 'Reset Defaults'?