понедельник, 30 июня 2014 г.

Построение контекстно-зависимого интерфейса. Part I.

Основными предпосылками построения контекстно-зависимого интерфейса является задача оградить конечного пользователя от действий которые им не могут быть выполнены в определенной фазе работы с программой. Типичным примером является меню управления файлами в проводнике Windows, предоставляющему пользователю операции: копировать, вырезать, вставить. Операция «вставить» доступна при наличии в буфере обмена ссылок на копируемые или вырезанные файлы, или если таких ссылок нет, то операция будет заблокирована или полностью скрыта. Таким образом интерфейс побуждает пользователя выполнить определенную последовательность действий для получения доступной операции. В частности, предварительно скопировать требуемые для переноса или копирования файлы в буфер обмена.

Игнорирование управлением доступными операциями, приводит конечного пользователя в замешательство, неясно какие операции доступны, а какие нет, непонятно в каком режиме работает приложение и пр. Представьте себе, вам пришла задача визирования документа, с перечнем доступных операций согласовать, утвердить, ознакомиться, исполнить. Вам с ходу не ясно что от вас требуются, и вы начинаете пробираться на ощупь. Вы пробуете выполнить поочередно операции выстроив тем самым общую картину, посредствам перечитывания сообщений о недопустимости действий: «нет прав», «не для Вашей роли», «не для этого этапа» и пр. И хорошо если вообще существует обратная связь в виде информационных сообщений, то её и вовсе не бывает, или программа попытается выполнить недопустимую операцию и вернет пользователю «Object reference not set to an instance of an object».

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

Задача

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

Спецификация команд

Создания нового заказа – по умолчанию доступна всегда, не управляется состоянием представления, и может быть заблокирована только презентером. Пример: пользователь не обладает достаточными правами.
Редактирование текущего заказа – операция доступна в случае если пользователь выбрал один заказ, данный заказ не заблокирован старшим менеджером. Во всех остальных случаях операция заблокирована.
Суммирование заказов -  операция доступна если пользователь выбрал более одного заказа, в списке выбранных заказов нет ни одного заблокированного заказа. Во всех отличных ситуациях операция заблокирована.
Удаление заказа - также, как и для редактирование заказа.
Блокировка заказа – пользователь обладает соответствующими правами, выбран один заказ, данный заказ не заблокирован. Во всех остальных случаях операция заблокирована.
Разблокировка - также, как и для блокировки заказа, только текущий заказ должен быть заблокирован.

Решение № 1. Привязка флаг-свойств модели представления

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

Структура модели представления формы списка заказов

Для решения данной задачи модель представления содержит 2 списка CurrentOrdersSelected и Orders, первый содержит список выбранных пользователем заказов, второй хранит весь список заказов текущего менеджера. Именно в CurrentOrdersSelected транслируется из View интересующие нас действия пользователя - выбор конкретных заказов. В дальнейшем состав данного списка будет показателем при принятии решения о возможности предоставления доступа к операции.


Управление доступностью операций

Далее необходимо зафиксировать правила выполнения, команд которые определены в спецификации, для этого во ViewModel для каждой операции определено флаг-свойство EnableНаименованиеОперации. В аксессоре get перечисляются условия, при выполнении которых флаг-свойство сигнализирует View, что команда может быть выполнена. Данные правила представляют собой совокупность фактов состояний ViewModel (в частности списка CurrentOrdersSelected) и действий презентера. Аксессор set доступен для презентера, он в праве опираясь на бизнес-логику или правила авторизации заблокировать любую операцию путем установки значения false в флаг-свойство.


Привязка данных ViewModel к целевым объектам View

После того как создана необходимая инфраструктура управления командами во ViewModel необходимо выполнить привязку данных к целевым объектам View. Привязка по традиции предыдущих постов осуществляется в методе BindData, который определен в базовом контракте представления и управляется презентером. В качестве целевых объектов VIew выступают компоненты UI интерфейса (форма, кнопки, меню, таблицы и пр.), а в качестве источника привязки ViewModel.

Для привязки флаг-свойств ViewModel используется режим DataSourceUpdateMode.Never, он аналогичен режиму OneWay в WPF. Данный режим при изменении наблюдаемого свойства источника приводит к обновлению свойства цели, но изменения свойства цели не передаются обратно свойству источника. Для обнаружения изменений в источнике данных, ViewModel реализует контракт уведомления об изменениях свойств INotifyPropertyChanged. Изменение свойств ViewModel вызывает событие OnPropertyChanged и в случае привязки свойства к View вызовет незамедлительное обновление свойства цели.

Для блокировки\разблокировки компонента UI ответственного за вызов исполнительных процедур используется свойство Enabled, оно связывается с соответствующим флаг-свойством ViewModel индицирующим возможность выполнения операции. Параллельно для компонентов UI регистрируются обработчики событий, в которых передается управление методам презентера ответственным за выполнение операций. Таким образом, изменение значений флаг-свойств ViewModel транслируется на состояния UI элементов.

Общая механика

После привязки ViewModel к объектам View, необходимо отслеживать какие заказы выбрал пользователь, для этого необходимо зарегистрировать событие «изменение выбранных значений GrigView» и в момент возникновения, передавать презентеру (можно стазу во ViewModel) список связанных объектов.

Презентер полученный список приводит к заданному формату и во ViewModel устанавливает свойство CurrentOrdersSelected. В момент установки списка выбранных заказов, метод RaiseOperationCanExecute вызывает поочередные события OnPropertyChange для всех свойств которые взаимодействуют CurrentOrdersSelected. Вызов события OnPropertyChange регистрируют наблюдатели компонентов UI и вызывают перечитывание get аксессора связанных свойств источника данных, т.е. во флаг-свойстве накладывается правило на контекст содержимого списка CurrentOrdersSelected, результат отправляется в свойство Enabled связанного элемента UI.

Таким нехитрым образом создается интерактивный, более дружественный интерфейс управления списком.

Заключение 

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

Исходный код демо-решения можно скачать здесь, группа Sample3.

Дополнительные ссылки

Практика применения шаблона MVP-VM (Winforms)
Проектировочный шаблон Model-View-Presenter-ViewModel для WPF

Комментариев нет:

Отправить комментарий