Re[8]: Генерация пользовательского интерфейса
От: d8m1k Россия  
Дата: 27.09.12 12:24
Оценка:
Здравствуйте, maxkar, Вы писали:

M>Здравствуйте, d8m1k, Вы писали:


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

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

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

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

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

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

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

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

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

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

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

M>поле ввода (для параметра "file") и кнопка действия (Action, вызывающий exportDocument со значением этого поля). Если ваш генератор достаточно умный, для поля имени файла он еще сгенерирует кнопочку "browse", но это не обязательно

Этот диалог можно рассматривать как внешний сервис, который вызывает модель. Модели нужно просто получить файл, какого то типа. А реализация этого уже запрограммирована, причём в каждой версии ОС по-своему. Там уже есть своя модель с кнопочкой "browse" и т.п. и своё представление. Да, если бы была бы цель создать подобный диалог, но нестандартный, делал бы может быть так, как Вы описываете.

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

Да есть ещё вопросы пользователю типа "Вы уверены что хотите удалить выделенный объект?". Тоже реализуется подобным образом как вызов из модели сервиса.

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

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

D>>Настраивать их взаимную вложенность

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

D>>Связывать компоненты с ModelView

D>>Удобно ведь, создал класс с полями разных типов, а интерфейс с контролами уже готов в любой момент.
D>>Да и просто удобно разработчику. Захотел отследить что у тебя в модели делается. Вывел это в интерфейс. А нужные компоненты уже создались и связались и можно экспериментировать сразу же не отвлекаясь на дизайн.
M>Не не не... Этот номер здесь не пройдет. Для того, чтобы "просто вывести это в интерфейс", вам потребуется написать код, по которому автогенератор сгенерирует поле . И в чем разница с ручным биндингом? Или это можно будет делать для сущесвующего поля? Не понятно, правда, где тогда в интерфейсе искать объект (например, он по форме разманан на несколько полей) и насколько глубоко можно туда закопаться (вот в вашем докменте можно до сокета в 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 удалить. Будет больше кошмаров. Но самое главное здесь — никаких новых классов и правок того, что уже есть.
Для того что бы увидеть объект на экране и смочь ввести значения так что бы оно предалось в модель достаточно объявить, грубо говоря, свойство public в соответствующем объекте и нужный компонент создастся где нужно и свяжется. Это не нужно будет делать каждый раз. Не нужно будет каждый раз писать код, который отслеживает изменения в этом public объекте.

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

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

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

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

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

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

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

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

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

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

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

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

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

M>Сразу предупрежу, что в строго статически типизированных языках без метапрограммирования придется писать много-много похожего кода. Я пишу на ActionScript (разновидность javascript), поэтому многие вещи там делаются совсем не так, как в том же C#. То же реактивное программирование выглядит вроде Reactive.apply(someFunction, value1, value2, value3). И получется значение, которое меняется при изменении аргументов. Функцию apply мне достаточно написать один раз (без перегрузок и т.п.). В сложных случаях сильно уменьшает объем кода тем, что не нужно писать "модели". На практике до 1/3 доходило применение (и куча выкинутых классов).
Насчёт языка полностью согласен. Под .Net много хорошо настраиваемых компонентов. Но C# тупой. Реализовать на С# можно, но по сути внутри C# приходиться реализовать свой DSL, писать кучу кода для поддержки возможность подписывания. Ruby наверное подошёл бы, но что то IronRuby не развивается совсем. Застрял между версией 1.8 и 1.9. Надеюсь, что макросы Nemerle для моих целей вполне подойдут.
А для html+javascript мои идеи совсем не подходят. Там не представляю как всё это можно было бы реализовать.

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

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

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

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

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

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

Спасибо большое за уделённое внимание. Ваши замечания помогают мне лучше осознать свои идеи.
Подождите ...
Wait...
Пока на собственное сообщение не было ответов, его можно удалить.