Re[14]: Генерация пользовательского интерфейса
От: d8m1k Россия  
Дата: 16.09.12 11:59
Оценка:
Я не говорю что Ваша идея плохая, просто она другая. Я тоже думал о подобном способе давно. Думал зачем я пишу в коде когда можно понаделать настроек в базе данных и к ним форм. Но мне показалось это сильно ограничивает возможности. Либо при росте потребностей эти настройки буду совершенствовать и это может привести к тому что настраивание будет похлеще самого программирования. Вместо этого я рутинные операции выносил в методы базовых классов. Таким образом делая прикладное программирование всё лаконичнее, но при этом не усложняя возможности делать что то нестандартное.

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

Можно в любой момент рефакторингом проанализировать состояние модели, считать структуру и значения и отобразить в аналогичном графическом виде. Но это не рационально. Лучше что бы представление подписывалось на события изменения состояний в модели и адекватно модифицировалось, отображало: открытые методы -> кнопки, открытые массивы -> гриды, открытые свойства -> поля для ввода, создание объекта -> создание панели или контрола.

Ещё по-теме: попытки формализовать написание кода с помощью UML диаграмм пока провалились. Вернее подобные есть, но они заточены для специальных областей. Сложновато с помощью формального описания достичь таких же возможностей, которые естественно описать языком.

Мне интересно я один так рассуждаю?
Так же хочу узнать, что в моих рассуждениях спорно?
Re[15]: Генерация пользовательского интерфейса
От: adontz Грузия http://adontz.wordpress.com/
Дата: 16.09.12 12:05
Оценка: +1
Здравствуйте, d8m1k, Вы писали:

D>Я не говорю что Ваша идея плохая, просто она другая. Я тоже думал о подобном способе давно. Думал зачем я пишу в коде когда можно понаделать настроек в базе данных и к ним форм. Но мне показалось это сильно ограничивает возможности. Либо при росте потребностей эти настройки буду совершенствовать и это может привести к тому что настраивание будет похлеще самого программирования. Вместо этого я рутинные операции выносил в методы базовых классов. Таким образом делая прикладное программирование всё лаконичнее, но при этом не усложняя возможности делать что то нестандартное.


Вы всё никак не хотите понять, что я и не собирась покрывать все потребности. Я покрываю 80-90% потребностей, остальное пишется вручную. С базовыми классами, хелперами или ещё чем-то вопрос отдельный. Всё что вы говорите про UML и .т.п. верно. Тут выбор простой: частично автоматизировать всё (как у вас) или полностью автоматизировать часть (как у меня). Я считаю что полностью автоматизировать часть правильнее, потому что часть получается на практике довольно большая и про неё можно потом забыть навсегда.
A journey of a thousand miles must begin with a single step © Lau Tsu
Re[2]: Генерация пользовательского интерфейса
От: d8m1k Россия  
Дата: 16.09.12 15:15
Оценка:
W>Только UI генерируется не по коду, а по модели данных. Как справедливо заметил adontz, генерировать из кода неперспективно, так как код есть также отражение модели.

По коду генерировать может быть сложнее и к тому же в коде должны быть крючки за которые представление могло бы цепляться, что бы полностью вовремя адекватно соответствовать.
Модель данных это мёртвая структура — просто схема. Управляет ей код. Код решает где что когда зачем изменить.
Re: Генерация пользовательского интерфейса
От: Аноним  
Дата: 17.09.12 09:59
Оценка:
Здравствуйте, d8m1k, Вы писали:

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


Почему 3D в играх поставляется в виде исходников (моделек, текстур и т.п.), а 3D в мультиках — в виде отрендеренных кадров? Потому, что в играх необходимые данные для построения сцены доступны только на этапе выполнения, а в мультиках — изначально. Аналогия понятна?

Описанный подход имеет смысл ТОЛЬКО для построения пользовательского интерфейса "по модели базы данных" или ее частному случаю — "некой структуре XML". Просто потому, что данные заранее неизвестны и задача (построения UI) другого решения, кроме автоматического, не имеет. Когда все о программе известно заранее, и интерфейс, в силу этого, определен, в чем смысл строить его автоматически?

Есть, конечно, смежные решения. Например, инструменты, которые генерируют формы по классам в design-time. Предполагается, что программист их использует как заготовку и доведет до ума.

Или вот — http://en.wikipedia.org/wiki/Semantic_HTML. Смесь семантики и средств настройки отображения. Или XSLT тот же самый.

Что давно интересовало лично меня — это как раз инструмент "построения пользовательского интерфейса по модели базы данных". Идея же очень простая: спецификация формата "метатаблиц", управляющих представлением данных + набор клиентов (веб-интерфейс, десктопный rich, мобильный вьювер). Правда, в этой жизни я уже вряд ли этим проектом займусь
Re: Генерация пользовательского интерфейса
От: Mr.Delphist  
Дата: 17.09.12 10:13
Оценка:
Здравствуйте, d8m1k, Вы писали:

D>Интересно, существуют ли такие системы разработки ПО, где программисту достаточно задумываться только над логикой работы программы, а удобный графический пользовательский интерфейс (UI) строился бы автоматически?


Если памть не изменяет, то что-то такое было в FoxPro for Windows v2.6 — можно было накропать таблицы, а затем по ним породить какую-то заготовку UI-формы с небольшим допиливанием ручками.
Re: Генерация пользовательского интерфейса
От: Sinclair Россия https://github.com/evilguest/
Дата: 18.09.12 07:15
Оценка: 25 (3) +1 :)
Здравствуйте, d8m1k, Вы писали:

D>Интересно, существуют ли такие системы разработки ПО, где программисту достаточно задумываться только над логикой работы программы, а удобный графический пользовательский интерфейс (UI) строился бы автоматически?

Нет. Ключевое слово выделено.
Автоматических генераторов UI по "коду" (по классам либо таблицам) — как грязи. Лично я свой первый "генератор UI" такого рода написал в 1991 году, для dbf файлов.
Проблема — только в одном: удобства нет.
Потому, что с точки зрения пользователя, UI — это набор сценариев. Выходим из точки А — приходим в точку Б.
Если мы не знаем, какие сценарии нужны пользователю, всё превращается в убогие "создать — отредактировать — удалить".
С точки зрения программиста, всё выглядит "красиво". С точки зрения пользователя, приходится совершать сотни ненужных действий.
Примеры факторов, которые влияют на эффективность и удобство UI можно приводить десятками.
Вот, скажем, если мы проектируем мозгом, то можем заметить, что адрес в нашей задаче у человека как правило один. Иногда — два. Ещё реже — больше адресов. Это позволяет нам спроектировать UI так, чтобы он был оптимален для основного случая, и немножечко менялся для редких исключений.
Автогенератор просто увидит Address[], и будет вынужден состряпать в гуе ажно целую таблицу, которую надо где-то воткнуть.
Отсюда появляются все эти окна с десятком табов, на каждом из которых — отдельная таблица, не нужная почти никогда. Вместо простого и понятного интерфейса, по которому сразу видно, у кого вообще никакого адреса не заполнено, а у кого их больше чем один.

Потенциально можно добавить в DSL специальную "семантическую разметку" — например, сообщить системе, что адресов бывает 0, 1, 2 и много. И ввести соответствующие частоты. И даже научить генератор придумывать, как решать такие задачи — как компоновать гуй приемлемым для человека образом на основе этих данных.
Но после этого есть риск получить трудозатраты по "семантической разметке" такого UI выше, чем при написании UI традиционным способом.
И чем сложнее и умнее будет этот автогенератор — тем хуже. Потому, что надо будет выворачивать мозг для понимания того, с чего это вдруг инпут контрол для имени уехал в другой угол, а то и на другую вкладку от фамилии. Надо будет трассировать логику генератора, а потом ещё и выбирать, в каком из десятков мест возможных подкруток надо подкрутить так, чтобы в другом месте UI чего-нибудь не отвалилось.
Уйдемте отсюда, Румата! У вас слишком богатые погреба.
Re[2]: Генерация пользовательского интерфейса
От: adontz Грузия http://adontz.wordpress.com/
Дата: 18.09.12 18:59
Оценка:
Здравствуйте, Sinclair, Вы писали:

S>Нет. Ключевое слово выделено.

S>Автоматических генераторов UI по "коду" (по классам либо таблицам) — как грязи. Лично я свой первый "генератор UI" такого рода написал в 1991 году, для dbf файлов.
S>Проблема — только в одном: удобства нет.

А оно и не нужно. Генератор позволяет сделать не слишком ужасный интерфейс и быстро выдать первую версию. И это крайне полезно, потому что сценарии и их структура, часто определяются наличием программы.
A journey of a thousand miles must begin with a single step © Lau Tsu
Re[3]: Генерация пользовательского интерфейса
От: Sinclair Россия https://github.com/evilguest/
Дата: 19.09.12 05:21
Оценка: 3 (1)
Здравствуйте, adontz, Вы писали:

A>А оно и не нужно. Генератор позволяет сделать не слишком ужасный интерфейс и быстро выдать первую версию. И это крайне полезно, потому что сценарии и их структура, часто определяются наличием программы.

Всё зависит от того, что вы называете словом "ужасный".
Если альтернатива — тупой кодер, который набрасывает едит боксы и грид на диалог, косяча с выравниванием, табордером, и клавиатурными шорткатами — тогда да, автогенератор рулит. Но их, я повторюсь, как грязи.
А вот если мы рассматриваем дизайн как процесс проектирования UI с учётом потребностей пользователей, то все эти сотни гридов и Property Dialog — катастрофически ужасны.
Уйдемте отсюда, Румата! У вас слишком богатые погреба.
Re[2]: Генерация пользовательского интерфейса
От: d8m1k Россия  
Дата: 19.09.12 12:18
Оценка:
Мне понравился Ваш ответ. Спасибо, что пояснили на примере. Я полностью с Вами согласен и тоже приходил к подобным мыслям. Расширение формального описания пользовательского интерфейса всякими настройками — фактически затаскивает логику пользовательского интерфейса в модель. Сведения о том как компоненты расположены у пользователя вредны в модели, они её загромождают неотносящимся к ней. Может представление будет реализовано в виде командной строки, где эти сведения совершенно не к чему.

Просто сгенерировать пользовательский интерфейс мало, нужны возможности для его настройки.

Попытаюсь упрощённо описать суть моей архитектуры.

Пользовательский интерфейс (UI) и бизнес-логика (модель) разделены. Взаимодействие с базой данных я не беру, это отдельная тема.

Модель во время выполнения с точки зрения представления выглядит как дерево вложенных друг в друга объектов. Объект может быть простого типа: число, дата, строка и т.п., может быть действием (паттерн команда), может быть составным типом: набором полей или массивом записей — таблицей. Таблица дополнительно может включать в свой состав поля и действия. По ходу выполнения могут добавляться/удаляться отдельные узлы.
Важное отличие от формального описания: структура дерева известна только во время выполнения программы.

Каждый такой тип (и простой, и сложный) у меня представлен своим классом, который кроме прочего снабжён событиями, возникающими при изменении его состояния. События позволяют генератору UI выполнять свои функции. Для простых типов это ValueChanging и ValueChanged – начало и окончание обновления значения. Для таблицы – начало и окончание обновления всего или только некоторых строк…

Для каждого типа модели на стороне UI есть свой контрол. Я использовал DevExpress. Полезной особенностью для моей идеи оказалась возможность настройки внешнего вида многих компонентов в режиме выполнения. Для числа, даты, строки и т.п. – приспособил потомки от CalcEdit, DateEdit, TextEdit. Для набора полей породил класс от LayoutControl. Для таблиц — потомок GridControl. Все эти потомки я обучил связываться с соответствующими объектами модели, подписываться на события.

При запуске приложения генератор UI вызывает некий метод из модели, который возвращает стартовый объект составного типа. Генератор создаёт соответствующий контрол и связывает его с объектом модели. Привязываясь, контрол считывает содержимое объекта модели и создаёт внутри себя соответствующие контролы, которые в свою очередь так же связывает. Действия модели отображаются в кнопки или инструменты на панели. Вложенные в объект наборы полей во всплывающие панели. Объекты простых типов двусторонне связываются с моделью.

Дерево модели отображается на дерево визуальных элементов однозначно. Визуальные элементы точно так же вложены друг в друга как это представлено в интерфейсе модели.

Если в модели запрограммировано, что при выполнении какого то действия создастся некий набор полей, то модель вызывает событие добавления нового элемента к родительскому элементу. И генератор UI реагирует — создаёт панель. То есть генератор не знает что панель появляется после нажатия вот этой вот кнопки. Он только слушается модель.
Заранее в какой либо схеме невозможно же описать все варианты что когда зачем должно появиться/исчезнуть в пользовательском интерфейсе. А если возможно, то такая схема будет кодом.

Если модель только написана и программа запущена 1-ый раз, то элементы на контролах располагаются по умолчанию: контролы в LayoutControl друг за другом списком, вложенные LayoutControl в панелях в определённом месте. Панель инструментов тоже аналогично. Всё при первом запуске в неудобном виде, однако полностью адекватно интерфесу модели. Я настраиваю внешний вид и запоминаю его.

В простейшем случае запоминать можно относительно места в дереве объектов. Пусть для простоты у каждого элемента в дереве свой вид.

Настройка это не программирование, а только декларативное описание, причём зажатое в рамки привязанности к модели. Можно сравнить с работой в дизайнере среды разработки:

В дизайнере среды разработки:
  • Доступно море компонентов не обязательно адекватных модели
  • Можно вкладывать компоненты в компоненты как угодно
  • Необходимо связывать компоненты с моделью
    Моя система:
  • На каждом этапе выполнения компоненты уже созданы генаратором UI, и нужно только настроить их вид и расположение
  • Порядок вложения уже определён моделью на каждом этапе работы системы
  • Компоненты уже связаны с интерфейсом модели

    В девэкспрессе понаделали своих велосипедиков для такой визуальной настройки. Для компонентов во время работы программы можно даже выводить Properties Panel которая представлена в VisualStudio. У меня мечта — было бы здорово приспособить инструмент вроде Expression Blend от WPF/Silverlight, только учитывающий упомянутые ограничения и работающий в режиме выполнения программы. Можно было бы у каждого контейнера расположить строго определённые в данный момент элементы, подобрать для них вид компонентов, может быть для удобства пользователя создать для каждого элемента несколько. Но все чтоб они были привязаны к модели в текущем состоянии работы программы.

    Проиллюстрирую архитектуру на примере из реальной практики. Я значительно упростил задачу с целью показать используемый принцип как можно ярче.
    Задача. В организацию поступают запросы на получение документов. От приложения требуется реализовать возможность прикреплять требуемые документы к запросу. Новые запросы разбирает исполнитель, который формирует список прикреплённых документов. Обработанные исполнителем запросы подписывает или направляет на доработку начальник. Начальник не может менять список документов.
    Модель обработчика запросов я представил классом

    internal class Request : FieldKitBase {
        internal Request(ModeBase mode) {
            this.mode = mode;
            rec = new RequestActiveRec(mode.ReadOnly);
            docs = new Docs();
        }
    
        protected override void CreateFields(ref FieldCollection fields) {
            fields.Add(docs);
            //другие поля
            mode.CreateFields(this, ref fields);
        }
    
        protected override object ID { get { return row.ID; } }
        protected override void Load(object id) { rec.Load(id); }
        protected override void Save() { rec.Save(); }
    
        private readonly ModeBase mode;
        private RequestActiveRec rec;
        private Docs docs;
    }

    В нём инкапсулировано относящееся ко всем режимам.
    В нём описано только, что запрос включает некие поля в зависимости от режима, и поле — список документов, состав которого так же зависит от режима.
    internal class Docs : TableNestedBase {
        internal Docs(Request owner, ModeBase mode) {
            base.Owner = owner;
            this.mode = mode;
        }
    
        protected override void CreateActions(ref ActionList actionList) {
            mode.CreateAction(this, ref actionList);
        }
        private readonly ModeBase mode;
    }

    Все различая Request и Docs делегированы в потомки ModeBase
    public abstract class ModeBase {
        internal virtual void CreateActions(Docs docs, ref ActionList actionList) {
            actionList.Add(actionList.ActionDefault = new ActionShellExecute(docs));
        }
        internal virtual void CreateFields(Request request, ref FieldCollection fields) { }
        internal abstract bool ReadOnly { get; }
    
        protected Request request;
    }
    
    internal class ModeDocsPrepare : ModeBase {
        internal override bool ReadOnly { get { return false; } }
        internal override void CreateActions(Docs docs, ref ActionList actionList) {
            base.CreateActions(docs, ref actionList);
            actionList.Add(new ActionDocIns(docs));
            actionList.Add(new ActionDocDel(docs));
        }
    }
    
    public class ModeDocsSigning : ModeBase {
        internal override bool ReadOnly { get { return false; } }
        internal override void CreateFields(Request request, ref FieldCollection fields) {
            base.CreateFields(request, ref fields);
            fields.Add(new ActionDocSigned(request, docs));
            fields.Add(new ActionDocNotSigned(request, docs));
        }
    }

    Для исполнителя создаю объект
    new Request(new ModeDocsPrepare());

    и связываю его
    Load(id_объекта);

    Для начальника
    new Request(new ModeDocsSigning());

    Для простого просмотра, без возможности каких либо изменений
    new Request(new ModeAll());

    где
    public class ModeOnlyView : ModeBase {
        internal override bool ReadOnly { get { return true; } }
    }

    Эти объекты передаю генератору объектов, который создаёт контролы, связывает, те в свою очередь наполняют себя контролами и связывают с соответствующими объектами модели. Динамически реагируют на изменения её интерфейса.
    Не приходиться рисовать формы в дизайн тайме и связывать их с моделью. Приходиться располагать компоненты по местам и настраивать их внешний вид во время 1-ого выполнения программы.

    В примере я попытался показать, что формальное описание ограничено. Заранее неизвестен ведь состав объекта Request. А если режимов будет много и будут возможны их сочетания. В программе их можно расщепить на составляющие и задать логику как эти составляющие сочетаются. А в схеме придётся перебирать все варианты.

    Предусмотрены "центральные" настройки по-умолчанию, которые для каждой версии приложения свои, но единые для всех пользователей. У пользователя могут быть свои настройки, но не привязаны к версии программы.
    Настройки по-умолчанию применяются в случаях
  • если пользователь не сделал своих настроек в данном контейнере
  • если сам намеренно удалил свои настройки
    Есть какой-то контейнер модели изменился в новой версии, то пользователь, у которого уже есть свои настройки соответствующего визуального контейнера, увидит новые элементы в вероятно неудобном месте, а старых не увидит. Например, новые столбцы таблицы увидит в конце. Новые поля будут по-умолчанию скрыты, однако доступны для настройки.

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

    Фактически на уровне модели я программирую только что программа делает. На уровне представления как представлен интерфейс модели.

    P.S. Мне даже кажется что Presentator в MVP, и ViewModel в MVVM придуманы для того что бы адаптировать неадекватный интерфейс модели к неподходящему представлению.
  • Re[3]: Генерация пользовательского интерфейса
    От: maxkar  
    Дата: 19.09.12 14:58
    Оценка:
    Здравствуйте, d8m1k, Вы писали:

    D>Фактически на уровне модели я программирую только что программа делает. На уровне представления как представлен интерфейс модели.


    D>P.S. Мне даже кажется что Presentator в MVP, и ViewModel в MVVM придуманы для того что бы адаптировать неадекватный интерфейс модели к неподходящему представлению.


    Посмотрел ваш пример. У вас там типичный View model. Ну и заодно слияние (причем жуткое) между логикой поведения и логикой отображения. Стоит чуть подергать вашу модель, и начнутся проблемы. Вот у вас "Request" сам себя умеет загружать. Давайте чуть усложним сценарий. Какой-то клиент заказывал документы, выписку потерял и теперь хочет найти его заказ. Какие-то ключевые слова из запроса помнит. У кого будет метод findRequests(String query) и почему? Это именно функциональность. Для добавления логирования/статистики и т.п. вам придется вашу же модель править. Для введения различных полномочий — создавать везде наборы различных полей и т.п. При желании она точно так же абстрактно распиливается на модель предметной области без "base.CreateFields+fields.Add", структуры UI (все, что касается ваших fields + немного оберток для удобной генерации "action" (через делегаты/рефлексию/классы)) и конвертацию первого во второе (да, вместе с конвертацией действий в Action). Первая диктуется реализацией предметной области и архитектурой. Второе — набором используемых библиотек. Третье — как раз логикой вашего приложения.

    Что касается Prenenter'а и ViewModel. Обе модели (и более, моделей может быть несколько) вполне адекватны своим задачам. Только вот задачи у различных частей разные. У view — в первую очередь хороший пользовательский интерфейс. У model — множество других. Это могут быть обеспечение прозводительности (для определенных сценариев очень может влиять на схему БД), обеспечение различных способов доступа к системе (тот же REST/web-services в качестве внешнего интерфейса), обеспечение расширяемости "снаружи" (это когда не обязательно модифицировать классы для каждого действия). Вот в вашем случае Request скорее всего был бы вообще немодифицируемым. Обращения с ним — через RequestManager. Вся логика обработки — внутри RequestManager, а не Request. Для поиска по запросу можно создать SearchManager (или Searcher), не меняя других классов. Ну да, в UI придется привязать, это нормально. А логика "модели" еще и меняться может. Что-то распилить на части, что-то собрать. И так на нескольких уровнях. Вот у меня периодически крупные объекты пилятся на несколько более мелких с различным временем жизни. А потом для обмена с клиентом собираются в протоколе. Сделано это для возможности кэширования (какие-то части меняются чаще, какие-то реже) и чистоты абстракций. А на клиенте из этих "собранных" объектов собираются еще более большие для отображения в UI, добавляется еще полей и т.п. ViewModel (да и промежуточные преобразования) позволяет не переделывать весь UI при изменении нижележащей модели, изменения более локальны.

    И еще раз относительно вашего примера. Он у вас простой. Действительно простой. При этом пример построен от workflow и полностью им определяется. Им же определялись бы viewModel для более сложных приложений. Что еще интересно — у вас workflow ровно один. Когда одни и те же сущности начинают участвовать в нескольких workflow, начнутся проблемы. Например, если добавить построение отчетов, поиск/аналитику по проведенным заявкам, отслеживание задержек в обработке заявки, создание похожих "заявок". Еще более это усложнится, если те же документы, например, нужно получать из других систем. В общем, там вы в результате получите "модель хранения в базе", которая не отражает напрямую сценарии пользователя. И там же вам придется как-то генерировать UI. Для хорошей поддержки сценариев вам придется ограничивать (да, именно ограничивать!) количество доступных действий. Подчеркну — не расширять, а ограничивать. И набор действий там определяется не состоянием объекта, а именно сценарием. Ваши модели или начнут вырастать во viewModel (под сценарии), либо в интерфейсе будет нагенерирована куча всего. Вы попробуйте аккуратно проследить, откуда именно пришли поля/методы в вашу модель. От реализации сценария/сценариев пользователя или из соображения структур данных/протоколов обмена. Там будет понятно, для чего нужна viewModel и для чего — "обычная модель".

    P.S. Не видно, как и где у вас выводятся прикрепленные документы. Было бы интересно на это посмотреть. Вот Sinclair хороший пример приводил для "обычно однократного" поля ввода адреса. Допустим, в вашей системе обычно нужно прикрепить один документ. И только в 5% случаев — два и более. Как вы собираетесь изменять модель, чтобы добавлять ровно один документ было удобно? Без лишних кликов в таблице и т.п.? А это первый шаг к "удобному" интерфейсу, где абстрактная модель и viewModel различаются. Причем в каких-то сценариях реализации документы могут добавляться каким-нибудь drag+drop из папки или окна документов (со своим поиском и т.п.). Не нагенерируете вы такого. И никаким изменением порядка элементов на экране удобного сценария не добъетесь.
    Re[4]: Генерация пользовательского интерфейса
    От: adontz Грузия http://adontz.wordpress.com/
    Дата: 19.09.12 15:35
    Оценка:
    Здравствуйте, Sinclair, Вы писали:

    A>>А оно и не нужно. Генератор позволяет сделать не слишком ужасный интерфейс и быстро выдать первую версию. И это крайне полезно, потому что сценарии и их структура, часто определяются наличием программы.

    S>Всё зависит от того, что вы называете словом "ужасный".
    S>Если альтернатива — тупой кодер, который набрасывает едит боксы и грид на диалог, косяча с выравниванием, табордером, и клавиатурными шорткатами — тогда да, автогенератор рулит. Но их, я повторюсь, как грязи.
    S>А вот если мы рассматриваем дизайн как процесс проектирования UI с учётом потребностей пользователей, то все эти сотни гридов и Property Dialog — катастрофически ужасны.

    Думаю, мы легко договоримся, если я напомню слово "бюджет".
    A journey of a thousand miles must begin with a single step © Lau Tsu
    Re[4]: Генерация пользовательского интерфейса
    От: d8m1k Россия  
    Дата: 19.09.12 19:12
    Оценка:
    Здравствуйте, maxkar, немного сумбурно, попытаюсь ответить.

    M>Ну и заодно слияние (причем жуткое) между логикой поведения и логикой отображения.



    M>У кого будет метод findRequests(String query) и почему?

    Логично его сделать в списке запросов. Если давать доступ к этому методу пользователю то вместо String query был бы неудобен.

    M>Это именно функциональность.

    Точно!

    M>Для добавления логирования/статистики и т.п. вам придется вашу же модель править.

    Конечно. Для логирования/статистики придётся править то, что скрывается под интерфейсом предоставленным в модели для представления.

    M>Для введения различных полномочий — создавать везде наборы различных полей и т.п. При желании она точно так же абстрактно распиливается на модель предметной области без "base.CreateFields+fields.Add", структуры UI (все, что касается ваших fields + немного оберток для удобной генерации "action" (через делегаты/рефлексию/классы)) и конвертацию первого во второе (да, вместе с конвертацией действий в Action). Первая диктуется реализацией предметной области и архитектурой. Второе — набором используемых библиотек. Третье — как раз логикой вашего приложения.

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

    M>Что касается Prenenter'а и ViewModel. Обе модели (и более, моделей может быть несколько) вполне адекватны своим задачам.

    Да я не говорю что они плохи — фасады с адаптерами тоже нужны.

    M>Только вот задачи у различных частей разные. У view — в первую очередь хороший пользовательский интерфейс. У model — множество других. Это могут быть обеспечение прозводительности (для определенных сценариев очень может влиять на схему БД), обеспечение различных способов доступа к системе (тот же REST/web-services в качестве внешнего интерфейса), обеспечение расширяемости "снаружи" (это когда не обязательно модифицировать классы для каждого действия).

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

    M>Вот в вашем случае Request скорее всего был бы вообще немодифицируемым. Обращения с ним — через RequestManager. Вся логика обработки — внутри RequestManager, а не Request. Для поиска по запросу можно создать SearchManager (или Searcher), не меняя других классов. Ну да, в UI придется привязать, это нормально.

    Не понимаю, что мешает?

    M>А логика "модели" еще и меняться может. Что-то распилить на части, что-то собрать. И так на нескольких уровнях.

    Да, всё течёт, меняется. Революционно или эволюционно. Я революций у себя не вижу.

    M>Вот у меня периодически крупные объекты пилятся на несколько более мелких с различным временем жизни. А потом для обмена с клиентом собираются в протоколе. Сделано это для возможности кэширования (какие-то части меняются чаще, какие-то реже) и чистоты абстракций. А на клиенте из этих "собранных" объектов собираются еще более большие для отображения в UI, добавляется еще полей и т.п. ViewModel (да и промежуточные преобразования) позволяет не переделывать весь UI при изменении нижележащей модели, изменения более локальны.

    У меня тоже можно распилить/слить внутри модели а выводить на представление преждний удобный фасад.

    M>И еще раз относительно вашего примера. Он у вас простой. Действительно простой. При этом пример построен от workflow и полностью им определяется. Им же определялись бы viewModel для более сложных приложений. Что еще интересно — у вас workflow ровно один. Когда одни и те же сущности начинают участвовать в нескольких workflow, начнутся проблемы. Например, если добавить построение отчетов, поиск/аналитику по проведенным заявкам, отслеживание задержек в обработке заявки, создание похожих "заявок".

    Я к этому стремлюсь что бы можно было делать как можно больше. Ограничен и стандартизирован пользовательский интерфейс. Например цеплять новые документы или показывать отчёты можно в нём только определённым образом. Что то вроде файн ридера или векторного редактора построить так не получиться. Но получиться там где достаточно более-менее однообразного пользовательского интерфейса.

    M>Еще более это усложнится, если те же документы, например, нужно получать из других систем. В общем, там вы в результате получите "модель хранения в базе", которая не отражает напрямую сценарии пользователя.

    Что мешает внутри модели вызывать внешние сервисы для получения документов?

    M>И там же вам придется как-то генерировать UI.

    Если изменения касаются взаимодействия с пользователем то придётся, если можно обойтись — то не обязательно.

    M>Для хорошей поддержки сценариев вам придется ограничивать (да, именно ограничивать!) количество доступных действий. Подчеркну — не расширять, а ограничивать. И набор действий там определяется не состоянием объекта, а именно сценарием. Ваши модели или начнут вырастать во viewModel (под сценарии), либо в интерфейсе будет нагенерирована куча всего. Вы попробуйте аккуратно проследить, откуда именно пришли поля/методы в вашу модель. От реализации сценария/сценариев пользователя или из соображения структур данных/протоколов обмена. Там будет понятно, для чего нужна viewModel и для чего — "обычная модель".

    Я за ViewModel!

    M>P.S. Не видно, как и где у вас выводятся прикрепленные документы. Было бы интересно на это посмотреть.

    Некуда они не выводятся. Просто пользователь может добавлять из любого файла или через буфер — тоже стандартная операция UI, но настраиваемая.
    Есть в моей системе несколько операций, которые не удалось привязать к какому либо объекту.
    У меня их получилось немного, но они есть:
  • вопрос пользователю
  • чтение из файла
  • запись в файл
  • ShellExecute

    M>Вот Sinclair хороший пример приводил для "обычно однократного" поля ввода адреса. Допустим, в вашей системе обычно нужно прикрепить один документ. И только в 5% случаев — два и более. Как вы собираетесь изменять модель, чтобы добавлять ровно один документ было удобно? Без лишних кликов в таблице и т.п.? А это первый шаг к "удобному" интерфейсу, где абстрактная модель и viewModel различаются.

    Собираюсь путём доработки ViewModel.

    M>Причем в каких-то сценариях реализации документы могут добавляться каким-нибудь drag+drop из папки или окна документов (со своим поиском и т.п.).

    А это уже функции компонентов графического интерфейса. А возможность drag+drop или невозможность — их настройка.

    M>Не нагенерируете вы такого. И никаким изменением порядка элементов на экране удобного сценария не добъетесь.

    Если не порядком то настройкой самих элементов пользовательского интерфейса. А сценарий называется ViewModel. Я это понял.

    P.S. Спасибо за пост. Понимать стал немного больше. Может мне не хватает чёткости в моих описаниях. Рад буду любой критике, особенно на примерах. Хочу разобраться, что сложно реализуемо по моей системе.
  • Re: Генерация пользовательского интерфейса
    От: Аноним  
    Дата: 20.09.12 00:48
    Оценка: 9 (2)
    Здравствуйте, d8m1k, Вы писали:

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

    Для серьезного применения я про такое не слышал. Очевидно, это будет малополезно, так как в большинстве приложений UI — это весьма сложная часть программы, нередко сложнее, чем код, реализующий саму функциональность, тем более, благодаря фирме Apple в последние годы требования к UI значительно повысились. Нагенерировать, конечно, что-то можно, но после доработки до качественного и удобного состояния от этого ничего не останется.
    Re[5]: Генерация пользовательского интерфейса
    От: Sinclair Россия https://github.com/evilguest/
    Дата: 20.09.12 03:59
    Оценка:
    Здравствуйте, adontz, Вы писали:

    A>Думаю, мы легко договоримся, если я напомню слово "бюджет".

    Конечно. Дешево и сердито — это как раз об автогенерённом UI
    Уйдемте отсюда, Румата! У вас слишком богатые погреба.
    Re[2]: Генерация пользовательского интерфейса
    От: d8m1k Россия  
    Дата: 20.09.12 10:26
    Оценка:
    А>Такой инструмент просто шикарно было бы иметь для утилит. Им можно было бы заменять командную строку. То есть приложение представляет класс или набор классов для выполнения определенной задачи. Когда мы хотим использовать его автоматически, то есть из другого кода, мы его используем как обычную библиотеку, а когда вручную — пользуемся автоматически сгенерированным интерфейсом. Только такие классы должны удовлетворять определенным требованиям, в частности, поддерживать реактивность, да и вообще использовать некий стандартный API. Но это не является каким-то ограничивающим фактором, в целом это обычные классы.
    Согласен.

    А>Для серьезного применения я про такое не слышал. Очевидно, это будет малополезно, так как в большинстве приложений UI — это весьма сложная часть программы, нередко сложнее, чем код, реализующий саму функциональность, тем более, благодаря фирме Apple в последние годы требования к UI значительно повысились. Нагенерировать, конечно, что-то можно, но после доработки до качественного и удобного состояния от этого ничего не останется.

    Не просто нагенерировать, но и дать возможность его настраивать, как разработчику так и пользователям на свой вкус.
    Фактически в создании пользовательского интерфейса вполне можно обойтись без программирования. Запрограммированы отдельные компоненты графического интерфейса. В WPF/Silverligt это просто декларативное описание XAML. С помощью шаблонов стилей меняют вид и поведение этих компонентов до неузнаваемости без программирования. Только управление существенной частью этих настроек можно поручить генератору, который слушается ViewModel.

    Интересно, меню MS Word какого нибудь 2003 не по такому ли принципу сделан был? Разработчики добавляли какую-либо действие в модель приложения и для неё генерировался команда, которую можно вынести в мению/толбар.
    Re[5]: Генерация пользовательского интерфейса
    От: maxkar  
    Дата: 20.09.12 21:18
    Оценка: 66 (1)
    Здравствуйте, d8m1k, Вы писали:

    D>Здравствуйте, maxkar, немного сумбурно, попытаюсь ответить.


    M>>Ну и заодно слияние (причем жуткое) между логикой поведения и логикой отображения.

    D>
    Ну а как? Если это в первую view model — да, нормально. А для модели предметной области "загрузка документа" по ID совсем никак не подходит. Я плохо представляю абстракцию "создать документ, затем его загрузить", а вот для формы "вывод документа" это подходит нормально. Ну а если туда еще добавить необходимость протаскивать настройки базы, например (чтобы ActiveRecord создать), то все еще хуже. Либо пользователю класса нужно знать все необходимые настройки, либо будет фабрика. А в случае фабрики совсем не понятно, зачем документу перезагружать себя. Ну а Docs и ModeBase — типичный UI. Для уровня бизнес логики (например, поставляемого в отдельной библиотеке) удобнее иметь списки действий методами, а не заполнять в динамике — больше контроля. Повторю: это может быть нормальной view model (нужно смотреть, как она используется). А вот использовать данный набор классов для решения других задач (не UI) будет неудобно.

    M>>Для введения различных полномочий — создавать везде наборы различных полей и т.п. При желании она точно так же абстрактно распиливается на модель предметной области без "base.CreateFields+fields.Add", структуры UI (все, что касается ваших fields + немного оберток для удобной генерации "action" (через делегаты/рефлексию/классы)) и конвертацию первого во второе (да, вместе с конвертацией действий в Action). Первая диктуется реализацией предметной области и архитектурой. Второе — набором используемых библиотек. Третье — как раз логикой вашего приложения.

    D>Мне кажется я просто загнал то, что обычно реализуют в ViewModel в то что я назвал здесь интерфейс модели. Хорошо, можно в модели уровень бизнес-фасада рассматривать как ViewModel. Значит генератор сопровождает представление по ViewModel.
    Да, это будет правильнее. И по ViewModel как раз можно пытаться генерировать представление, она именно для этого предназначена и более-менее отражает структуру интерфейса и взаимодействия пользователя с программой.

    M>>Только вот задачи у различных частей разные. У view — в первую очередь хороший пользовательский интерфейс. У model — множество других. Это могут быть обеспечение прозводительности (для определенных сценариев очень может влиять на схему БД), обеспечение различных способов доступа к системе (тот же REST/web-services в качестве внешнего интерфейса), обеспечение расширяемости "снаружи" (это когда не обязательно модифицировать классы для каждого действия).

    D>Конечно разные. У модели функциональность, у представления обеспечивать связь с миром по другую сторону экрана. Задачу удобства я предполагаю решать через настройки — декларативное описание. Так же как это делается классически, но не во время разработки, а во время выполнения программы, когда известно что требуется отобразить.
    Во время рантайма делать можно. И оно даже может работать. Но в случае, например, большого количества "опциональных" полей все может начать плавать (когда наборы отображаемых данных плохо предсказуемы). Генерация UI на этапе "дизайна" просто позволит проверить чуть больше (наличие нужных полей, нормальный вид интерфейсов и т.п.). Ваши модели ведь можно и на этапе компиляции анализировать (через reflection, например). В compile-time же можно сгенерировать и наброски интерфейса, и пример, на котором будут делаться настройки (без данных, с заглушками и т.п.). Или можно декларативно описывать ViewModel и из нее генерировать те же наброски интерфейса плюс "каркас" для реализации ViewModel (интерфейсы, пустые классы реализации). В случае с мини-языком рантайм вообще будет получаться в точности ваш (т.е. будут генерироваться те же самые класса). Оба этих подхода позволят большую часть ваших настроек сделать еще до запуска программы на данных.
    Я здесь не утверждаю, что настрйоки не следует делать в рантайме. Это можно делать в рантайме! "Предварительный анализ" позволяет делать некоторые вещи вроде проверки корректности и упрощения дизайна на ранних стадиях, но и без этого можно жить. Да и для настройки можно собрать приложение с другим набором "компонент" (которые допускают настройку/перемещение), а пользователю дать уже все настроенное.

    M>>Еще более это усложнится, если те же документы, например, нужно получать из других систем. В общем, там вы в результате получите "модель хранения в базе", которая не отражает напрямую сценарии пользователя.

    D>Что мешает внутри модели вызывать внешние сервисы для получения документов?
    Это актуально только для модели предметной области. Если вы делаете ViewModel, там все нормально. Просто "получить внешний документ XYZ из системы А" в интерфейсе реализуется выбором системы и полем ввода ID, а в нижележащей модели все сложнее. Там и сервис по доставанию документов, и добавленные в него несколько сервисов для конкретных систем. А еще тот поисковик не имеет своего состояния. У него нет полей "выбранная система/введенный ID документа/метод искать". Зато у него есть метод "достать документ(id системы, id документа)". И еще внутри спрятаны настройки, которые никто не видит. В общем, пример на разницу view model vs нижележащая модель.

    D>Есть в моей системе несколько операций, которые не удалось привязать к какому либо объекту.

    D>У меня их получилось немного, но они есть:
    D>вопрос пользователю
    D>чтение из файла
    D>запись в файл
    D>ShellExecute
    А примеры последних трех можно? Обычно чтение/запись — это операция вроде импорта/экспорта и загрузки/сохранения. Да, может потребоваться диалог, но это ваш первый пункт. ShellExecute вообще в чистом виде подразумевает действие, которое правда реализуется извне, а не внутри (обычный Action в большинстве случаев).

    D>P.S. Спасибо за пост. Понимать стал немного больше. Может мне не хватает чёткости в моих описаниях. Рад буду любой критике, особенно на примерах. Хочу разобраться, что сложно реализуемо по моей системе.


    А здесь нужно смотреть на ваши типичные программы. Вполне может быть, что для ваших задач ваш подход будет работать. Ну и для прототипов (или временных заглушек интерфейса) может работать. На практике есть несколько соображений, которые препятствуют вашему подходу:
  • Для большинства приложений настраивать придется 90-100% автосгенерированного интерфейса (процент зависит от класса приложений, но для больших приложений это обычно так). Начиная размерами/расположением полей и заканчивая tab order/горячими клавишами (акселераторами) и т.п. С учетом необходимого количества настроек там будет без разницы, подгонять все в настройках или сразу написать правильно.
  • Не понятно, реализуются ли (и насколько удобно) у вас "резиновые" интерфейсы. Это когда из-за изменения размера шрифта (например) не начинается полный бардак, а интерфейс более-менее разумно перераскладывается. В Java это верстка на LayoutManager'ах (а не в абсолютных координатах). Проблема в визуальном тюнинге — правильно указать "привязки" объекта, отступы и т.п.
  • Может быть неудобно делать локализацию и подписи вообще. У вас локализация (и названия полей) вероятно делается через view model, но это лишние методы для пробрасывания значений (строк) из ресурсов в UI. Плюс предыдущий пункт (резиновая верстка) становится очень важен, так как длины надписей будут различаться в разных языках. Да и вообще с надписями (label для полей ввода) не понятно. Может быть для каждого поля по названию ("Фамилия", "Имя"), а может для двух/трех полей быть одна надпись ("ФИО").
  • Раз уж заговорили про надписи. Нужно от злых шутников как-то защищаться. А то поменяют такие подписи "фамилия" и "имя" друг с другом и потом остальные будут мучаться.
  • С интерактивностью непросто будет. Например, валидация (подсвечивание полей). Всплывающие подсказки (зависящие, например, от значения поля — с какими-то подробностями об ошибке/дополнительной информацией, которая не всегда нужна и т.п.).
  • Не факт, что пользователям нужно давать возможность настраивать интерфейс. Они вряд ли смогут сделать "удобный" интерфейс. Что-то подкрутить — да. А вот по-настоящему удобно вряд ли. О usability нужно хотя бы в общем виде что-то знать.
  • У вас растет сложность ViewModel. Все необходимые поля приходится выносить в объект (или в описание объекта), явно писать текстом. Создавать класс на страничку/вид (view). Какая-то часть из этого кода точно необходима (привязка к нижележащим моделям и т.п.). А вот какая-то часть может не быть одним объектом. У меня, например, "моделей окна" в чистом виде обычно нигде нет. Для создания окна передаются одна или несколько моделей данных. По мере необходимости из них делаются еще несколько моделей (модель списка, отдельно взятые значения для индикаторов, полей данных и т.п.). Могут создаваться "временные" поля (для вводимых пользователем значений). При этом они создаются даже не на уровне класса, а на уровне метода, только там, где нужны. Плюс там же привязка прямо по месту действий к методам модели. Очень много применяется реактивное программирование (да, нижележащая модель как раз таки имеет состояние). Т.е. view model привязана к UI и формируется рядом с ним. Все равно с "другим" UI эта модель будет не совместима, так что особых проблем нет. Но и лишней писанины (SomeWindowModel...) нет, большая часть данных передается одним или несколькими "простыми" параметрами. Необходимость формулировать четкую и законченную ViewModel не всегда является проблемой и зависит от того, сколько UI получится сгенерировать автоматически, а сколько — нет. Если вдруг в 90% все будет автоматически работать, тогда такой проблемы нет (все формулируется моделью без настройки view).

    Вот как-то так. Можно все эти проблемы решать. Но тут две крайности. В одних случаях "правильная настройка" интерфейса с учетом всех пунктов выше будет занимать столько же (а может и больше) времени, чем создание правильного интерфейса с нуля (даже поверх ваших же моделей). А в других у вас модель получится очень функциональная и фактически будет представлять собой тот самый UI. Да, renderer (отображение) у нее будет через "другие" компоненты UI. Но по функциональности там будет уже почти все то же самое. Та же валидация+подсказки+значение для поля ввода в вашей модели так и будут присутствовать. И будете вы интерфейс писать уже на ViewModel но уже почти в терминах UI (ну может без координат и еще каких-то мелочей)

    И вот посередине между двумя крайностями выше, там, где указанные проблемы не актуальны, лежит и область применимости вашего подхода. Она есть, но слишком маленькая для создания широко распространенных продуктов. Скорее всего, там даже не одна область, а несколько несвязных областей. И вот в каждой такой миниобласти могут быть свои библиотеки/генераторы/etc..., но в широкое применение они не идут как раз в силу своей специфичности.
  • Re[6]: Генерация пользовательского интерфейса
    От: d8m1k Россия  
    Дата: 21.09.12 11:41
    Оценка:
    S>Конечно. Дешево и сердито — это как раз об автогенерённом UI

    В компонентах под названием грид (или датагрид) часто реализуют данный подход. При связывании грида с источником данных, грид автоматически создаёт колонки согласно источнику данных. Типы столбцов выбирается по типам свойств считанных из модели. Названия колонок по умолчанию — это имена свойств объектов. При удалении какого либо поля в модели исчезнет соответствующая колонка. Почему бы не распространить такой принцип дальше.
    Если в модели для представления отдан не список, а один объект. Почему бы не создать компонент, который может связаться с объектом как с источником данных, который так же считывает содержимое этого объекта подбирает типы вложенных компонентов, связывает их с соответствующими элементами объекта? А если тип элемента окажется списком, тогда нужен грид. А каждое поле строки списка может так же быть сложного типа.
    Re[6]: Генерация пользовательского интерфейса
    От: d8m1k Россия  
    Дата: 21.09.12 20:23
    Оценка:
    M>>>Ну и заодно слияние (причем жуткое) между логикой поведения и логикой отображения.
    D>>
    M>Ну а как? Если это в первую view model — да, нормально. А для модели предметной области "загрузка документа" по ID совсем никак не подходит. Я плохо представляю абстракцию "создать документ, затем его загрузить", а вот для формы "вывод документа" это подходит нормально. Ну а если туда еще добавить необходимость протаскивать настройки базы, например (чтобы ActiveRecord создать), то все еще хуже. Либо пользователю класса нужно знать все необходимые настройки, либо будет фабрика. А в случае фабрики совсем не понятно, зачем документу перезагружать себя.
    Загрузку себя я вынес из конструктора в Load(id) так как в некоторых случаях нужно создать сначала пустые объекты, связать их, затем уже в связанном состоянии заполнять данными, что бы сработали связи.
    Настройки базы данных лежат за ActiveRecord.

    M>Ну а Docs и ModeBase — типичный UI.

    Класс Request является фасадом, т.е. ViewModel. Конечно можно Request переделать в абстрактный класс, а создание и заполнение данными его конкретных экземпляров поручить фабрике. Docs тогда тоже переделать в абстракцию, а его создание поручить фабричному методу Request. Можно по-другому: вынести поведение Load(id) вместе с RequestActiveRec в стратегию ModeBase, а создание так же Request с параметром ModeBase поручить той же фабрике.

    M>А вот использовать данный набор классов для решения других задач (не UI) будет неудобно.

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

    M>Для уровня бизнес логики (например, поставляемого в отдельной библиотеке) удобнее иметь списки действий методами, а не заполнять в динамике — больше контроля. Повторю: это может быть нормальной view model (нужно смотреть, как она используется).

    Статические методы лаконичнее и понятнее, зато, например, действие ActiveShellExecute может понадобиться где-то, и если его оформить в виде метода класса то использовать его где то ещё не получиться.

    Суть в том что Request и Docs имеют некий видимый из представления интерфейс, а под ним скрываться может всё что угодно. У меня в примере чисто конкретно ActiveRecord и чисто таблица.

    M>И по ViewModel как раз можно пытаться генерировать представление, она именно для этого предназначена и более-менее отражает структуру интерфейса и взаимодействия пользователя с программой.

    Почему только более-менее? Если что то будет не соответствовать, значит представление придётся подкручивать под ViewModel. Но подкручивать, состыковывать — это же задача ViewModel.
    Почему бы полностью интерфейс ViewModel не сделать однозначно соответствующим пользовательскому? В ViewModel набор возможностей — что можно сделать. А в пользовательском интерфейсе реализация этих возможностей — как делать. А если уж они полностью соответствуют, то установка связей это просто механическая работа, почему бы не автоматизировать?

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

    Если оно не предсказуемо в дизайн тайме, значит нужно делать в рантайме.

    M>Генерация UI на этапе "дизайна" просто позволит проверить чуть больше (наличие нужных полей, нормальный вид интерфейсов и т.п.).

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

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

    Можно и на этапе дизайна генерировать интерфейс. По сути это будет то же самое: в этом режиме фоном запускается модель приложения или какие то её части и по ней автоматизированно строиться интерфейс. Можно и так взглянуть на это.

    M>(когда наборы отображаемых данных плохо предсказуемы)

    так если они плохо предсказумы, я так понимаю на этапе дизайна, так как же можно удостовериться, что интерфейс пользователя сделан соответствующим?

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

    Так тоже можно. Это другой подход. Я предлагаю большую часть настроек поручить генератору UI, ту часть которую делать по-любому.

    M>Я здесь не утверждаю, что настрйоки не следует делать в рантайме. Это можно делать в рантайме! "Предварительный анализ" позволяет делать некоторые вещи вроде проверки корректности и упрощения дизайна на ранних стадиях, но и без этого можно жить.

    На любых стадиях не всегда удобный и красивый но всегда рабочий пользовательский интерфейс.

    M>Да и для настройки можно собрать приложение с другим набором "компонент" (которые допускают настройку/перемещение), а пользователю дать уже все настроенное.

    Можно просто отдавая приложение пользователям отключить какие-то настройки.

    D>>Есть в моей системе несколько операций, которые не удалось привязать к какому либо объекту.

    D>>У меня их получилось немного, но они есть:
    D>>вопрос пользователю
    D>>чтение из файла
    D>>запись в файл
    D>>ShellExecute
    M>А примеры последних трех можно? Обычно чтение/запись — это операция вроде импорта/экспорта и загрузки/сохранения. Да, может потребоваться диалог, но это ваш первый пункт. ShellExecute вообще в чистом виде подразумевает действие, которое правда реализуется извне, а не внутри (обычный Action в большинстве случаев).

    Реализация диалогов на уровне модели не ясно, поэтому создал абстракцию
    public abstract class ForWinUI
    {
        protected static ForWinUI Instance;//Задаётся на WinUI уровне
        #region FileSave
        internal static DialogResultBusiness FileSave(object sender, EventArgsFileSave args) {
            return Instance.DoFileSave(sender, args);
        }
        protected abstract DialogResultBusiness DoFileSave(object sender, EventArgsFileSave args);
        public delegate string GetInfoDelegate();
        public class EventArgsFileSave {
            public GetInfoDelegate GetInfo { get; internal set; }
            public string Filter { get; internal set; }
            public string Title { get; internal set; }
        }
        #endregion
        #region FileOpen
        internal static DialogResultBusiness FileOpen(object sender, EventArgsFileOpen args) {
            return Instance.DoFileOpen(sender, args);
        }
        protected abstract DialogResultBusiness DoFileOpen(object sender, EventArgsFileOpen args);
        public class EventArgsFileOpen {
            ///<summary>in</summary>
            public string Filter { get; internal set; }
            ///<summary>in</summary>
            public string Title { get; internal set; }
            ///<summary>out</summary>
            public byte[] Bytes { internal get; set; }
            ///<summary>out</summary>
            public string FileName { internal get; set; }
            ///<summary>out</summary>
            public string FileExtension { internal get; set; }
            ///<summary>out</summary>
            public DateTime FileDate { internal get; set; }
        }
        #endregion
        #region ShellExecute
        internal static void ShellExecute(object sender, EventArgsShellExecute args) {
            Instance.DoShellExecute(sender, args);
        }
        protected abstract void DoShellExecute(object sender, EventArgsShellExecute args);
        public class EventArgsShellExecute {
            public byte[] Data { get; internal set; }
            public string FileExtension { get; internal set; }
        }
        #endregion
    //дальше абстракции других способностей
    }

    Действие ShellExecute примерно такое
    internal class ActionShellExecute : ActionBase{
        internal ActionShellExecute(IFile owner) { this.file = file; }
            private readonly IFile file;
        internal override void Execute() {
            ForWinUI.ShellExecute(this, new ForWinUI.EventArgsShellExecute() { 
                Data = file.Content,
                FileExtension = file.Extension
            });
        }
    }

    В представлении абстракция реализована
    public class FromBusinessHandlers : ForWinUI
    {
        static FromBusinessHandlers() {
            Instance = new FromBusinessHandlers();
        }
        protected override DialogResultBusiness DoFileOpen(object sender, ForWinUI.EventArgsFileOpen args) {
            var d = new OpenFileDialog() {
                Filter = args.Filter,
                Title = args.Title
            };
            if (d.ShowDialog() == DialogResult.OK) {
                //args.Data = File.ReadAllText(d.FileName,Encoding.UTF8);
                args.Bytes = File.ReadAllBytes(d.FileName);
                args.FileDate = File.GetCreationTime(d.FileName);
                args.FileExtension = Path.GetExtension(d.FileName).Substring(1);
                args.FileName = Path.GetFileNameWithoutExtension(d.FileName);
                return DialogResultBusiness.OK;
            }
            return DialogResultBusiness.Cancel;
        }
        protected override DialogResultBusiness DoFileSave(object sender, ForWinUI.EventArgsFileSave args) {
            var d = new SaveFileDialog() {
                Filter = args.Filter,
                Title = args.Title
            };
            if (d.ShowDialog() == DialogResult.OK) {
                using (StreamWriter w = new StreamWriter(d.FileName)) {
                    w.Write(args.GetInfo());
                }
                return DialogResultBusiness.OK;
            }
            return DialogResultBusiness.Cancel;
        }
        protected override void DoShellExecute(object sender, EventArgsShellExecute args) {
            using (var p=new Process()) {
                p.StartInfo.FileName=Path.GetTempPath()+"file."+args.FileExtension;
                using (var f = new FileStream(p.StartInfo.FileName, FileMode.Create)) {
                    f.Write(args.Data, 0, args.Data.Length);
                    f.Close();
                }
                p.Start();
            }
        }
    //дальше реализация других способностей
    }



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

    M> Для большинства приложений настраивать придется 90-100% автосгенерированного интерфейса (процент зависит от класса приложений, но для больших приложений это обычно так). Начиная размерами/расположением полей и заканчивая tab order/горячими клавишами (акселераторами) и т.п. С учетом необходимого количества настроек там будет без разницы, подгонять все в настройках или сразу написать правильно.
    Зато не придётся
  • Подбирать компоненты для каждого элемента
  • Настраивать их взаимную вложенность
  • Связывать компоненты с ModelView
    Удобно ведь, создал класс с полями разных типов, а интерфейс с контролами уже готов в любой момент.
    Да и просто удобно разработчику. Захотел отследить что у тебя в модели делается. Вывел это в интерфейс. А нужные компоненты уже создались и связались и можно экспериментировать сразу же не отвлекаясь на дизайн.

    M> Не понятно, реализуются ли (и насколько удобно) у вас "резиновые" интерфейсы. Это когда из-за изменения размера шрифта (например) не начинается полный бардак, а интерфейс более-менее разумно перераскладывается. В Java это верстка на LayoutManager'ах (а не в абсолютных координатах). Проблема в визуальном тюнинге — правильно указать "привязки" объекта, отступы и т.п.

    Это зависит от самих компонентов. Для ModelView вообще фиолетово, что там и как в представление твориться.
    Я использовал LayoutControl от DivExpress. Он следит за "резиновостью", хотя и не всё идеально.

    M> Может быть неудобно делать локализацию и подписи вообще. У вас локализация (и названия полей) вероятно делается через view model, но это лишние методы для пробрасывания значений (строк) из ресурсов в UI. Плюс предыдущий пункт (резиновая верстка) становится очень важен, так как длины надписей будут различаться в разных языках. Да и вообще с надписями (label для полей ввода) не понятно. Может быть для каждого поля по названию ("Фамилия", "Имя"), а может для двух/трех полей быть одна надпись ("ФИО").

    По-хорошему сами названия Фамилия, Имя тоже в настройках. В модели универсальные для всех FirstName и LastName.

    M> Раз уж заговорили про надписи. Нужно от злых шутников как-то защищаться. А то поменяют такие подписи "фамилия" и "имя" друг с другом и потом остальные будут мучаться.

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

    M> С интерактивностью непросто будет. Например, валидация (подсвечивание полей). Всплывающие подсказки (зависящие, например, от значения поля — с какими-то подробностями об ошибке/дополнительной информацией, которая не всегда нужна и т.п.).

    Не сложно, надо только позаботиться об этом в генераторе UI. Примерно так. Модель решает что у поля Field1 возникла ошибка Error123. На уровне представления для Error123 настроено значение "Не правильно". Модель следит за валидацией: у какого поля когда какие ошибки показывать. Представление адекватно реагирует: рисует красненький валидатор, показывает при наведении мышки ошибку. Эту тему можно грамотно решить вполне.

    M> Не факт, что пользователям нужно давать возможность настраивать интерфейс. Они вряд ли смогут сделать "удобный" интерфейс. Что-то подкрутить — да. А вот по-настоящему удобно вряд ли. О usability нужно хотя бы в общем виде что-то знать.

    usability — дело дизайнеров. А если ориентироваться на бестолковых, то им перенастройку можно просто отключить.

    M> У вас растет сложность ViewModel. Все необходимые поля приходится выносить в объект (или в описание объекта), явно писать текстом. Создавать класс на страничку/вид (view). Какая-то часть из этого кода точно необходима (привязка к нижележащим моделям и т.п.). А вот какая-то часть может не быть одним объектом. У меня, например, "моделей окна" в чистом виде обычно нигде нет. Для создания окна передаются одна или несколько моделей данных. По мере необходимости из них делаются еще несколько моделей (модель списка, отдельно взятые значения для индикаторов, полей данных и т.п.). Могут создаваться "временные" поля (для вводимых пользователем значений). При этом они создаются даже не на уровне класса, а на уровне метода, только там, где нужны. Плюс там же привязка прямо по месту действий к методам модели. Очень много применяется реактивное программирование (да, нижележащая модель как раз таки имеет состояние). Т.е. view model привязана к UI и формируется рядом с ним. Все равно с "другим" UI эта модель будет не совместима, так что особых проблем нет. Но и лишней писанины (SomeWindowModel...) нет, большая часть данных передается одним или несколькими "простыми" параметрами.

    Интересно. Про то что как лучше устроить я хотел бы побольше поузнавать, почерпнуть опыта у других. Сейчас книжку читаю Джимми Нильсона "Применение DDD и шаблонов проектирования".

    M>Необходимость формулировать четкую и законченную ViewModel не всегда является проблемой и зависит от того, сколько UI получится сгенерировать автоматически, а сколько — нет. Если вдруг в 90% все будет автоматически работать, тогда такой проблемы нет (все формулируется моделью без настройки view).

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

    M>Вот как-то так. Можно все эти проблемы решать. Но тут две крайности. В одних случаях "правильная настройка" интерфейса с учетом всех пунктов выше будет занимать столько же (а может и больше) времени, чем создание правильного интерфейса с нуля (даже поверх ваших же моделей). А в других у вас модель получится очень функциональная и фактически будет представлять собой тот самый UI. Да, renderer (отображение) у нее будет через "другие" компоненты UI. Но по функциональности там будет уже почти все то же самое. Та же валидация+подсказки+значение для поля ввода в вашей модели так и будут присутствовать. И будете вы интерфейс писать уже на ViewModel но уже почти в терминах UI (ну может без координат и еще каких-то мелочей)

    Надо отделять представление от логики. Подсказки — это настройки. А валидация — это логика — в модель её. А уже в модель организовывать разумно по принципу единственной ответственности. А мухи отдельно от закуски.
    А в настройках надо просто доделывать работу начатую генератором UI. Если хорошо организовать, прикрутить инструменты не хуже чем в дизайнере среды разработки, то не должно получиться дольше.

    M>И вот посередине между двумя крайностями выше, там, где указанные проблемы не актуальны, лежит и область применимости вашего подхода. Она есть, но слишком маленькая для создания широко распространенных продуктов. Скорее всего, там даже не одна область, а несколько несвязных областей. И вот в каждой такой миниобласти могут быть свои библиотеки/генераторы/etc..., но в широкое применение они не идут как раз в силу своей специфичности.

    Зависит от развитости системы. Для моего велосипедика придётся как-то ограничиться с областью. Хотя если в велосипедике использовать хорошие детали, мощные компоненты с удобными инструментами настройки их вида, то может не так это плохо. А если за реализацию возьмётся фирма побольше, аля Microsoft, то область подойдёт поширше.
  • Re[7]: Генерация пользовательского интерфейса
    От: Sinclair Россия https://github.com/evilguest/
    Дата: 22.09.12 02:17
    Оценка:
    Здравствуйте, d8m1k, Вы писали:

    D>В компонентах под названием грид (или датагрид) часто реализуют данный подход. При связывании грида с источником данных, грид автоматически создаёт колонки согласно источнику данных. Типы столбцов выбирается по типам свойств считанных из модели. Названия колонок по умолчанию — это имена свойств объектов. При удалении какого либо поля в модели исчезнет соответствующая колонка. Почему бы не распространить такой принцип дальше.

    D>Если в модели для представления отдан не список, а один объект. Почему бы не создать компонент, который может связаться с объектом как с источником данных, который так же считывает содержимое этого объекта подбирает типы вложенных компонентов, связывает их с соответствующими элементами объекта? А если тип элемента окажется списком, тогда нужен грид. А каждое поле строки списка может так же быть сложного типа.
    Вот именно таких автогенераторов — как грязи. Например, стандартный PropertyGrid является одной из реализаций этого подхода.
    Уйдемте отсюда, Румата! У вас слишком богатые погреба.
    Re[7]: Генерация пользовательского интерфейса
    От: maxkar  
    Дата: 23.09.12 18:45
    Оценка:
    Здравствуйте, d8m1k, Вы писали:

    M>>И по ViewModel как раз можно пытаться генерировать представление, она именно для этого предназначена и более-менее отражает структуру интерфейса и взаимодействия пользователя с программой.

    D>Почему только более-менее? Если что то будет не соответствовать, значит представление придётся подкручивать под ViewModel. Но подкручивать, состыковывать — это же задача ViewModel.
    D>Почему бы полностью интерфейс ViewModel не сделать однозначно соответствующим пользовательскому? В ViewModel набор возможностей — что можно сделать. А в пользовательском интерфейсе реализация этих возможностей — как делать. А если уж они полностью соответствуют, то установка связей это просто механическая работа, почему бы не автоматизировать?
    Это я осторожничаю (по более-менее). Связи можно и установить автоматически. Вопрос только в том, в каких случаях это будет приемлемо, а в каких — нет.

    M>>Генерация UI на этапе "дизайна" просто позволит проверить чуть больше (наличие нужных полей, нормальный вид интерфейсов и т.п.).

    D>В смысле проверить красоту пользовательского интерфейса. А я думал, что прежде всего рабочее приложение, а не красивый интерфейс.
    Не просто красоту пользовательского интерфейса, а еще его эргономику. Например, что при удалении трех опциональных полей у объекта А и добавлении 5 опциональных полей у объекта Б интерфейс не меняется непостижимым образом и большинство элементов управления остаются на своих местах. Да и "рабочее приложение" может включать в себя и требования к интерфейсу. Например, интерфейс для оператора интернет-магазина должен позволять выполнять стандартные действия очень быстро. Иначе нужно будет много операторов.

    M>>(когда наборы отображаемых данных плохо предсказуемы)

    D>так если они плохо предсказумы, я так понимаю на этапе дизайна, так как же можно удостовериться, что интерфейс пользователя сделан соответствующим?
    В любом случае придется формализовывать, и все всплывет. Под "Слабо предсказуемы" в данном случае я понимаю наличие большого набора опциональных полей (зависят от типа объекта) и нескольких таких объетков на одном экране (можно разных типов).

    D>>>Есть в моей системе несколько операций, которые не удалось привязать к какому либо объекту.

    D>>>У меня их получилось немного, но они есть:
    D>>>вопрос пользователю
    D>>>чтение из файла
    D>>>запись в файл
    D>>>ShellExecute
    M>>А примеры последних трех можно? Обычно чтение/запись — это операция вроде импорта/экспорта и загрузки/сохранения. Да, может потребоваться диалог, но это ваш первый пункт. ShellExecute вообще в чистом виде подразумевает действие, которое правда реализуется извне, а не внутри (обычный Action в большинстве случаев).

    D>Реализация диалогов на уровне модели не ясно, поэтому создал абстракцию

    D>В представлении абстракция реализована
    Поскипал детали реализации. Я у вас не увидел примера, где нужен ShellExecute. В терминах ваших примеров я хочу знать, где вообще в модели вам нужен ActionShellExecute? Пользователь о таком действии точно не знает и знать не может. Да, есть действия "открыть документ", "распечатать заявку", "сделать резеврную копию" и т.п. Это ровно те действия, которые есть в бизнес-модели и ViewModel. ShellExecute — всего лишь способ (низкоуровневый) его реализации. И к объекту привязывается именно исходное (открыть документ) действие. Которое вполне логично привязывается к документу (может быть — и не только). Ну и архитектурно все делается гораздо проще. Есть интерфейс DesktopIntegration с методом executeCommand. Возможно — еще какими-то способами интеграции с desktop environment (и точно без открытия файлов). Реализация протаскивается через конструкторы/параметризацию и т.п. В любом случае shellExecute — это действие логики (и интерфейса, и способ реализации бизнес-логики), так что все нормально.

    Что касается диалогов открытия/сохранения файлов и вообще диалогов. Это уже ограничения вашего подхода к "автогенерации" UI. На самом деле у вас в модели есть действие "exportDocument(File)", например. И вы хотите сделать ему UI. Только вот проблема — параметризованные Action не поддерживаются. Так вот, правильное решение — поддержать параметризованные действия, а не делать частные случаи. Очевидно же, что можно наавтогенерировать диалог открытия файла и не использовать системный. Да, он будет не слишком удобный, но ведь остальное — настройки. Заодно автогенирация решит проблемы и с другими параметрическими действиями (там, где вам нужна половина диалогов). Я даже знаю, какая модель интерфейса должна генерироваться:
    поле ввода (для параметра "file") и кнопка действия (Action, вызывающий exportDocument со значением этого поля). Если ваш генератор достаточно умный, для поля имени файла он еще сгенерирует кнопочку "browse", но это не обязательно

    Кстати, а что делать с чисто "информационными" диалогами? Аналог "detail view" для чего-то в общем экране? Это чисто UI действие.


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

    M>> Для большинства приложений настраивать придется 90-100% автосгенерированного интерфейса (процент зависит от класса приложений, но для больших приложений это обычно так). Начиная размерами/расположением полей и заканчивая tab order/горячими клавишами (акселераторами) и т.п. С учетом необходимого количества настроек там будет без разницы, подгонять все в настройках или сразу написать правильно.
    D>Зато не придётся
    D>Подбирать компоненты для каждого элемента
    Придется. Мы, например, меняли числовое значение вида "значение из ..." на обычный "gaugage" нечисловой. Решили, что конкретные цифры не нужны пользователям. Выбор из списка вариантов можно реализовать разными способами (radio/combo), и т.п.
    D>Настраивать их взаимную вложенность
    Ага. Вместо этого вам придется вместо одного объекта делать несколько для того, чтобы ваш генератор сгенерировал несколько панелей по различным аспектам. Например, для предмета (в магазине) набор "продажных" характеристик (описание и т.п. для каталога), набор характеристик логистики (размеры, масса) и т.п. В принципе, можно архитектурно разделить на группы атрибутов (в данном случае это может оказаться правильным), но я не уверен, что так будет всегда.
    D>Связывать компоненты с ModelView
    D>Удобно ведь, создал класс с полями разных типов, а интерфейс с контролами уже готов в любой момент.
    D>Да и просто удобно разработчику. Захотел отследить что у тебя в модели делается. Вывел это в интерфейс. А нужные компоненты уже создались и связались и можно экспериментировать сразу же не отвлекаясь на дизайн.
    Не не не... Этот номер здесь не пройдет. Для того, чтобы "просто вывести это в интерфейс", вам потребуется написать код, по которому автогенератор сгенерирует поле . И в чем разница с ручным биндингом? Или это можно будет делать для сущесвующего поля? Не понятно, правда, где тогда в интерфейсе искать объект (например, он по форме разманан на несколько полей) и насколько глубоко можно туда закопаться (вот в вашем докменте можно до сокета в ActiveRecord докопаться или нет?). И чем ваш код генерации свойств модели будет короче моего UI.add(container, x, y, UI.displayText(someField, width, height))? Для модифицируемого поля (допустим, оно поддерживает уведомления об изменении aka changeListener) будет UI.add(container, x, y, UI.inputField(model, "field", width, height)). Результат — гораздо более предсказуемый, чем при автораскладке. Если хочется укоротить, можно еще x, y, width, height удалить. Будет больше кошмаров. Но самое главное здесь — никаких новых классов и правок того, что уже есть.

    M>> Не понятно, реализуются ли (и насколько удобно) у вас "резиновые" интерфейсы. Это когда из-за изменения размера шрифта (например) не начинается полный бардак, а интерфейс более-менее разумно перераскладывается. В Java это верстка на LayoutManager'ах (а не в абсолютных координатах). Проблема в визуальном тюнинге — правильно указать "привязки" объекта, отступы и т.п.

    D>Это зависит от самих компонентов. Для ModelView вообще фиолетово, что там и как в представление твориться.
    D>Я использовал LayoutControl от DivExpress. Он следит за "резиновостью", хотя и не всё идеально.
    Вот как раз проблема в "не все идеально". Для сложных интерфейсов это большая проблема. Ваш генератор ведь не знает, какие поля можно и нужно растягивать, а какие — нет. Будет тянуться все. И поля для инициалов (однобуквенных), и информационные области (которые должны тянуться). В ручную все это контроллируется гораздо лучше.

    M>> Может быть неудобно делать локализацию и подписи вообще. У вас локализация (и названия полей) вероятно делается через view model, но это лишние методы для пробрасывания значений (строк) из ресурсов в UI. Плюс предыдущий пункт (резиновая верстка) становится очень важен, так как длины надписей будут различаться в разных языках. Да и вообще с надписями (label для полей ввода) не понятно. Может быть для каждого поля по названию ("Фамилия", "Имя"), а может для двух/трех полей быть одна надпись ("ФИО").

    D>По-хорошему сами названия Фамилия, Имя тоже в настройках. В модели универсальные для всех FirstName и LastName.
    Нельзя генерировать формы без надписей. Вот по классу Employee нагенерирует ваш визард в рантайме десяток полей без названий (фамилия, имя, отчество, отдел, зарплата и т.д.) и будете вы долго-долго разбираться, что и как там должно быть. Ладно еще если это вывод существующего работника, а если это создание нового?

    Кстати, даю очень полезный хинт. "Универсальные" FirstName и LastName не нужны. Нужен обычный TextAttribute. У Employee будет два атрибута одного типа, это нормально. Писать лишнего не надо.

    M>> Раз уж заговорили про надписи. Нужно от злых шутников как-то защищаться. А то поменяют такие подписи "фамилия" и "имя" друг с другом и потом остальные будут мучаться.

    D>Настройки по-умолчанию для всех и индивидуальные для каждого, каждый сам горазд пошутить над собой поменять фамилию и имя как ему вздумается.
    Вы пользователей плохо знаете. У них с обеспечением безопасности рабочего места все плохо. Причем плохо даже у компьютерщиков, не говоря уже о простых пользователях. Так что оставленный без присмотра компьютер с запущенной программой — вполне типичная ситуация. Сам то пользователь менять поля не будет. А вот кто-нибудь другой захочет пошутить и передвинуть поля сможет. Они же у вас к подписям пока гвоздями не прибиты.

    M>> С интерактивностью непросто будет. Например, валидация (подсвечивание полей). Всплывающие подсказки (зависящие, например, от значения поля — с какими-то подробностями об ошибке/дополнительной информацией, которая не всегда нужна и т.п.).

    D>Не сложно, надо только позаботиться об этом в генераторе UI. Примерно так. Модель решает что у поля Field1 возникла ошибка Error123. На уровне представления для Error123 настроено значение "Не правильно". Модель следит за валидацией: у какого поля когда какие ошибки показывать. Представление адекватно реагирует: рисует красненький валидатор, показывает при наведении мышки ошибку. Эту тему можно грамотно решить вполне.
    А такая валидация нужна относительно редко. Обычно хочется чего-то более сложного. Например, "максимальная стоимость заказа не может превышать XYZ рублей". И отдельного "неверного" поля нет. Да, через модель это все делается. Через нее можно выставить все необходимые свойства (и валидность отдельного поля, и валидность группы). Вопрос только в том, как и что показывать. "Простое информационное сообщение" — это не так просто. Оно должно быть заметным, напрмер. Так что будете сразу после модели опять настраивать поле для сообщения об ошибке.

    Кстати, а как у вас в вашем варианте сообщение настраивается? Там ведь должен быть текст обычного хинта и еще информация о том, что не так при валидации. Оставлять только что-то одно — не правильно.

    D>Интересно. Про то что как лучше устроить я хотел бы побольше поузнавать, почерпнуть опыта у других. Сейчас книжку читаю Джимми Нильсона "Применение DDD и шаблонов проектирования".

    Сразу предупрежу, что в строго статически типизированных языках без метапрограммирования придется писать много-много похожего кода. Я пишу на ActionScript (разновидность javascript), поэтому многие вещи там делаются совсем не так, как в том же C#. То же реактивное программирование выглядит вроде Reactive.apply(someFunction, value1, value2, value3). И получется значение, которое меняется при изменении аргументов. Функцию apply мне достаточно написать один раз (без перегрузок и т.п.). В сложных случаях сильно уменьшает объем кода тем, что не нужно писать "модели". На практике до 1/3 доходило применение (и куча выкинутых классов).

    M>>Необходимость формулировать четкую и законченную ViewModel не всегда является проблемой и зависит от того, сколько UI получится сгенерировать автоматически, а сколько — нет. Если вдруг в 90% все будет автоматически работать, тогда такой проблемы нет (все формулируется моделью без настройки view).

    D>По первости при каждой новой задаче приходиться что то допиливать в генераторе UI, но постепенно устаканивается.
    D>В любом случае оставить возможность сделать что то неукладывающееся в модель традиционным способом стоит.
    Да, это вам оставить придется. Кстати, еще один сценарий есть, который у вас плохо реализуется. "Интерактивность" интерфейса. Например, я при выборе товара в списке хочу какие-то его поля выделить. Например, "подозрительно высокую цену", низкий/большой остаток на складе, истечение срока годности. Это все не валидация, это все логика отображения. Да, она тоже через модель протаскивается. Но ее же нужно не "в поля" биндить, а в атрибуты (цвета текста в текстовом поле, например). Так что в результате появятся рендереры, "произвольные" биндинги, которые автоматически не генерируются (или ваш TextAttribute станет почти точной копией TextField со всеми его атрибутами).

    M>>Вот как-то так. Можно все эти проблемы решать. Но тут две крайности. В одних случаях "правильная настройка" интерфейса с учетом всех пунктов выше будет занимать столько же (а может и больше) времени, чем создание правильного интерфейса с нуля (даже поверх ваших же моделей). А в других у вас модель получится очень функциональная и фактически будет представлять собой тот самый UI. Да, renderer (отображение) у нее будет через "другие" компоненты UI. Но по функциональности там будет уже почти все то же самое. Та же валидация+подсказки+значение для поля ввода в вашей модели так и будут присутствовать. И будете вы интерфейс писать уже на ViewModel но уже почти в терминах UI (ну может без координат и еще каких-то мелочей)

    D>Надо отделять представление от логики. Подсказки — это настройки. А валидация — это логика — в модель её. А уже в модель организовывать разумно по принципу единственной ответственности. А мухи отдельно от закуски.
    Подсказки не только настройки. Подсказки вполне могут быть связаны с валидацией (и информировать об ошибках в форме, например). Подсказки могут быть связаны со значениями времени выполнения программы. Например. какую-то кнопку можно нажимать только в ночь с третьего понедельника месяца на вторник. И в подсказке я хочу вывести информацию о том,когда в следующий раз можно нажать кнопку (в случае, если она сейчас не нажимается). Ваш генератор вывалит это поле куда-нибудь текстом в интерфейс, что не всегда приемлемо (может, в том окне места и так мало).

    D>А в настройках надо просто доделывать работу начатую генератором UI. Если хорошо организовать, прикрутить инструменты не хуже чем в дизайнере среды разработки, то не должно получиться дольше.

    А если сравнивать редактор с рукопашным написанием кода? В мире Java не принято пользоваться дизайнерами интерфейсов. Очень часто код верстки окна/панели и т.п. пишется руками. При должном владении Layout'ами набрать настройки и позиционирования компонента получается не дольше, чем натыкать все то же самое по куче различных полей и навводить циферки.
    Подождите ...
    Wait...
    Пока на собственное сообщение не было ответов, его можно удалить.