Как создать хороший интерфейс пользователя?
Нужен ли нам специальный метод разработки пользовательского интерфейса?
Каждый день разработчики программного обеспечения создают интерфейс своих программ без применения каких-либо специальных методов. Нужен ли нам вообще метод разработки пользовательского интерфейса? Я думаю нужен, и вот почему:
- Пользователи думают, что интерфейс - это и есть программа.
- Чтобы пользователи работали более продуктивно, программа должна быть простой в использовании.
- Достижения технологии значительно увеличили количество решений, которые необходимо принимать во время разработки интерфейса
- Общеплатформенные стандарты пользовательского интерфейса решают только 15% вопросов разработки в типичном проекте.
- Большинство программных проектов ограничены во времени.
- Пользователи становятся все более привередливыми.
- Хороший интерфейс может стать преимуществом против конкурентов, плохой - послужить причиной неудачи всего проекта.
Разработчики программ могут последовать простому прагматическому методу, кратко описанному в этой статье. Более подробно этот метод описан в моей книге "GUI Design for Dummies".
Выяснение целей и ограничений проекта
Начните процесс создания интерфейса с определения целей проекта а также внутренних и внешние обстоятельств, которые вы должны принять во внимание. Убедитесь, что клиенты или руководители проекта согласны с вашим анализом ситуации, и все остальные участники проекта по крайней мере проинформированы.
Рекомендую вам уделить одинаковое внимание следующим пунктам:
- Пользователи: их опыт работы с компьютером, мотивы, размер/важность групп пользователей, образцы (типовые ситуации) использования
- Задачи: что послужило причиной создания проекта, этапы создания проекта, какие результаты должны быть получены, какая информация необходима и когда
- Технология разработки и платформа, на которой будут работать пользователи
- Среда, в которой будет создаваться и использоваться проект (физическая, рыночная, организационная и культурная)
Используйте эту информацию для определения и расстановки приоритетов. Вот пара простых примеров:
- Когда группа пользователей постоянно меняет свой состав и предполагаемый образец использования используется нечасто, акцентируйте внимание на простоте понимания интерфейса
- Когда одна и та же задача повторяется многократно, и группа пользователей довольно большая, самой важной целью должна быть эффективность использования.
Если вы пропускаете шаг выяснения целей в своем процессе разработки, вы рискуете получить:
- Неожиданное или неконтролируемое повторение процесса разработки, когда некоторые важные факторы становится известны вам слишком поздно в процессе разработки. "Что? У пользователей будут экраны с разрешением 800х600? Окна нашей программы просто не поместятся на экране!"
- Много дискуссий без значительного прогресса
- Вы не оправдаете ожиданий спонсоров вашего проекта (людей у которых есть причины забоится о доходе)
Начальная фаза разработки: концептуальный дизайн
В этой фазе разработки вы должны решить какой интерфейс лучше всего будет подходить для достижения ваших целей - текстовый, графический или мультимедиа. Затем выберите наиболее подходящую структуру взаимодействия. Например, для графического интерфейса пользователя выбор следующий:
- Множественные окна
- MDI (много-документный интерфейс)
- Множественные фреймы
- Неструктурированное взаимодействие: экраны с гиперссылками
Различные структуры взаимодействия обеспечивают разные степени гибкости для пользователей. Обычно, чем гибче структура, тем больше она требует от пользователя обучения, понимания, и времени на работу с окнами (открыть, закрыть, разместить и т.д.). Основывайте ваш выбор на той собранной вами информации о пользователях и ситуациях, в которых они работают, а не просто делайте вашу программу похожей на среду разработки. В конце концов, среда разработки была разработана для вас, а не для пользователей.
Затем создайте концептуальную модель программы. Под концептуальной моделью я имею в виду чертеж, схему, которая показывает главные элементы, процессы и связи в программе с точки зрения пользователя. Вам нужно другое определение концептуальной модели? Как насчет такого: "абсолютный минимум, который пользователь может понять о программе, и тем не менее успешно пользоваться ею". Ваш дизайн - ваши окна, меню и текст - должен передать это понимание пользователю. Вот почему вы должны разработать детальную концептуальную модель. Если у вас есть только нечеткая модель того, что вы пытаетесь сказать пользователю, он в конце концов останется с еще более нечеткой идеей того что делает ваша программа и как она работает.
Для выполнения начальной фазы разработки погрузитесь целиком в задачи пользователей и создайте бумажный прототип навигационной модели. Навигационная модель показывает, как вы планируете распределять функции или задачи между окнами вашей программы. Навигационная модель определяет как пользователям смогут перемещаться как между различными задачами так и внутри отдельной задачи. Например, можно ли будет оставить частично завершенную задачу и начать другую.
Для того чтобы создать хороший интерфейс, на каждой стадии разработки необходима обратная связь от пользователей. Чтобы оценить концептуальную модель программы, вы можете просто показать ее схему пользователям и попросить объяснить ее вам. Если у них возникнут трудности, значит вы еще не достигли точки зрения пользователя в понимании проблемы.
Если вы пропустите раннюю фазу разработки, ваш интерфейс может страдать от следующих проблем:
- Несоответствие группам пользователей
- Сложность в обучении
- Неадекватная степень гибкости
- Нецелостное или непредсказуемое использование разных типов окон
Разработка на основе задач пользователя
Итак, ваши окна пока не имеют содержания. Вы знаете для чего нужно каждое, но для того чтобы наполнить их элементами управления, вам необходимо четкое понимание задач пользователя. Когда вы проводите интервью, задавайте открытые вопросы, чтобы пользователь мог дать вам ответ на основе своих действий. "Пожалуйста покажите мне какие типы документов вы храните на своем компьютере" - гораздо лучший вопрос чем "Используете ли вы текстовый редактор для заметок?".
Для создания дизайна, представьте в деталях, как бы пользователь выполнил данную задачу от начала до конца. Отметьте те места, в которых ваша программа может помочь ему в его работе. Затем сконструируйте окна, основываясь на своих заметках. И снова, проверьте результаты, проведя простые тесты с окнами или их набросками.
Например, прорабатывая задачи для программы планирования расписаний я обнаружила, что учителя обычно создают планы уроков для нескольких предметов даже если они еще не знают в какой день и в какое время будут проходить занятия. Исходная программа, которую я оценивала, имела ориентированный на расписание подход, встроенный в базу данных, так что учителя могли планировать уроки только назначив их дату и время.
Если вы не будете использовать этот подход, вы рискуете создать программу, в которой пользователи будут перемещаться от одного окна к другому для того, чтобы выполнить свою задачу. Процесс разработки, основанный на задачах пользователей особенно важен, когда ваша программа предназначена для того, чтобы помогать пользователям работать быстрее и эффективнее.
Визуальный дизайн: использование компонентов
На этом этапе вы должны правильно использовать компоненты визуального дизайна чтобы показать пользователю для чего нужно каждое окно, и как им пользоваться.
Хорошо выполненный дизайн выглядит чистым, простым и аккуратным. Его можно понять одним взглядом. Пользователь должен сразу распознавать какие данные можно редактировать, какие нет; по каким объектам можно щелкать мышью и какие объекты можно перетаскивать.
Чтобы выполнить этот этап, вы должны понимать, как пользователь воспринимает различные элементы управления. Существует понятие "предоставленная возможность" (affordance), хотя сам термин выглядит довольно неуклюже. "Предоставленные возможности" это визуальные характеристики объектов, которые сигнализируют о том, что с ними можно сделать. Например гребни на крышке банки с овощами говорят мне о том, что крышка отвинчивается, а не открывается открывашкой. Поля ввода, например, приглашают пользователя ввести любое текстовое или числовое значение. Если же набор вводимых величин ограничен, то лучше использовать выпадающий список.
Вы также должны понимать принципы визуальных сообщений. Размер, цвет, яркость, местоположение, форма и текстура - все это средства, которые используются для того чтобы сгруппировать элементы вместе по важности или по похожести. Если у вас есть возможность воспользоваться услугами профессионального визуального дизайнера, не пренебрегайте ею.
Если вы пропустите эту стадию, вы рискуете получить программу, которая выглядит непрофессионально и непривлекательно. Если вы будете пренебрегать визуальным дизайном, ваша программа будет посылать хаотические сигналы, что приведет к увеличению ошибок, путанице и раздражению пользователя.
И снова, если вы хотите знать, какие сигналы вы подаете, не спрашивайте "Что я сказал?". Вместо этого спросите свою аудиторию, как они восприняли это.
Проверка на пользователях
Половина процесса разработки - это анализ и создание; вторая половина - получение обратной связи, и применение полученной информации.
Если вы хотите быть уверенным что ваша программа понравится пользователям, собирайте мнения потенциальных пользователей во время процесса разработки. Тестирование на пользователях даст вам наиболее верную информацию. Так же как и в визуальном дизайне существуют люди, чья профессиональная работа - проводить тестирование на пользователях. Если вы сможете привлечь специалиста по usability или human factors - это замечательно. Если нет, учитесь проводить тесты самостоятельно.
Тестирование на пользователях это не обсуждение дизайна с пользователями. В тестировании вы предлагаете пользователю выполнить определенное задание на некоторой версии вашей программы (рисунок одного окна, бумажный прототип, или рабочая система). Затем вы должны молча наблюдать за тем, что происходит. Вы можете даже снять весь процесс на видео или просто делать заметки.
Тестируя бумажные прототипы на нескольких разных пользователях, вы можете выявить множество серьезных usability-проблем еще до того, как приступите к кодированию. Тестируя готовую программу до ее официального выпуска вы можете найти и подчистить небольшие проблемы, которые могут стать причиной раздражения пользователя.
Запомните - все системы тестируются на пользователях. Вопрос только в том, хотите ли вы, чтобы это случилось когда вы еще можете исправить найденные проблемы. Думайте о тестировании как о способе получить новую информацию. Наблюдение пользователей в процессе тестирования вашей программы это самый лучший и самый дешевый способ образования.
Автор: Laura Arlov
Грамотный интерфейс: поля ввода
Очень часто в программах данные вводятся в формах, содержащих множество полей: поле фамилии, имени, адреса, номера паспорта, размера обуви... Проблема состоит в том, что внешне эти поля очень похожи друг на друга и рано или поздно оператор (из-за усталости, по неопытности или по какой другой причине) вобьет фамилию в поле номера паспорта или адрес в поле отчества.
Отследить программно такого рода ошибки бывает довольно сложно: попробуйте написать модуль который бы смог отличить имя человека от названия населенного пункта или номер телефона от номера паспорта. Частично решить эту проблему позволяют контролы, принимающие ввод по маске (MaskEdit или MaskEditBox), но, честно говоря, им не хватает гибкости. Да и оператору с ними работать не очень удобно ибо капризны они.
Гораздо лучше изначально создать такие условия ввода данных, чтобы подобные ошибки просто не возникали.
Рассмотрим варианты.
Решений тут несколько. Наиболее популярное на данный момент - разбить форму с десятком полей ввода на несколько форм, по два-три поля в каждом. Такое решение можно часто встретить при регистрации на всяких интернет-сайтах. Для интернета это оказывается удобным, но в оффлайновых программах, где вводимых данных гораздо больше, возникает масса проблем:
- необходимо предусмотреть в каждой следующей форме отображение введенных данных из предыдущих форм, чтобы пользователь мог видеть, что он уже ввел ранее;
- для изменения ранее введенных данных (например неверно введенной фамилии) необходимо сделать в формах навигацию "вперед - назад";
- проблема "раздутия проекта" из-за создания множества по-сути бесполезных форм;
- достаточно сложно управлять таким интерфейсом, используя только клавиатуру - время от времени пользователю все же придется хватать в руки мышь, а это не есть хорошо;
- и т.д. и т.п.
Программировать такой интерфейс - сплошная морока, да и пользователи от него не в восторге. Значит будем искать другое решение.
Нам нужно, чтобы интерфейс каким-то образом подсказывал пользователю, что именно от него (пользователя) требуется в данный момент. И очень желательно, чтобы эти подсказки не отвлекали и в тоже время были достаточно заметными, чтоб пользователь не смог их проигнорировать.
Всплывающие подсказки (ToolTips) тут не годятся, т.к. после нескольких часов работы они начинают дико раздражать. Кроме того, чтоб понять что именно нужно программе, подсказку нужно прочесть - а это приводит к рассеиванию внимания. Сообщения в статус-строке (StatusBar) вещь вобщем-то не плохая, но не привлекают должного внимания, поэтому они могут использоваться лишь как вспомогательное средство. Я предлагаю другой вариант.
Раскрасим поля ввода.
На мой взгляд для решения вышеописанной проблемы необходимо разукрасить фон полей ввода. Но не стоит делать их яркими как новогодняя елка, а нужно просто слегка изменить цвет, чтобы каждое поле приобрело свой уникальный легкий цветовой оттенок. Поработав пару часов с таким интерфейсом, человек уже на уровне подсознания будет знать, что в розовое поле нужно вводить фамилию, а в голубое - адрес.
Наше сознание устроено таким образом, что даже малейшее несоответствие вызывает у нас в мозгу стоп-сигналы, заставляющие нас остановиться и проверить все ли верно мы делаем. Обратите внимание как просто мы находим нужную кнопку на пульте от телевизора (ориентируясь не на рисунок-пиктограмму, а только на цвет) и как сложно бывает воспользоваться пультом от другого телевизора, в котором кнопки расположены по-другому и имеют другую раскраску.
Окрашивание полей имеет и еще один плюс - если разработчики в будущем поменяют расположение полей, но при этом сохранят их цветовое соответствие (фамилия - розовый, адрес - голубой), то пользователям будет намного легче приспособиться новому виду, т.к. для поиска нужного поля они будут ориентироваться по цвету, а не читать все подряд надписи на форме.
Раскрасим формы
В продолжение идеи предлагаю раскрашивать и формы, в зависимости от выполняемых действий. Суть состоит в том, что для ввода новой записи в базу и для редактирования уже введенной записи как правило используется одна и та же форма. Так вот, если во время редактирования придать фону формы какой-нибудь оттенок (например сделать ее слегка красноватой), то пользователь, занимающийся только вводом новых данных, сразу же заметит это изменение цвета и не введет по ошибке новые данные поверх старых.
Ну и чтоб поставить жирную точку в вопросе путаницы при вводе и редактировании данных опишу еще одно простое, но очень полезное улучшение интерфейса.
Раскрасим текст.
Когда на форме расположено множество полей, то из-за усталости глаз, не очень хорошего зрения, слишком высокого разрешения монитора и многих других проблем возникает ситуация, когда пользователь не может сразу отпределить позицию коретки ввода - моргающая полоска может быть не заметна. Обычно в таких случаях пользователь хватает мышь и "тычет" ей в нужное поле.
Помочь пользователям в этом случае можно раскрасив текст в текущем поле ввода. Программируется такое поведение сверх-просто: как только фокус ввода передается какому-то контролу, значение его свойства "цвет текста" (ForeColor, TextColor) меняется на "красный" (Color.Red), а при потере фокуса контролом цвет текста восстанавливается на стандартный (SystemColors.WindowText). Это простое дополнение даст возможность пользователю всегда точно знать, где именно находится коретка ввода и что именно нужно сейчас вводить.
Залепа №13. О грамотном проектировании и неграмотном IDE.
Эту статью без мата в адрес мелкомягких писать сложно :( Но я все же постараюсь.
Есть некая программа, работающая с базами данных. Естественно в ней куча таблиц, большинство из которых представляют собой достаточно простые справочники. Например, это могут быть справочники телефонных кодов, типов продукции, видов доставки, да чего угодно. Для простоты будем рассматривать только один из таких справочников - справочник пользователей.
Каждая запись справочника содержит данные об одном пользователе: его логин, пароль для входа в систему и какие-то дополнительные атрибуты. Например его права доступа. В программе, кроме основных функций, должна быть возможность как редактирования списка пользователей так и изменения параметров отдельных пользователей.
Задача, согласитесь, тривиальна и встречается на каждом шагу. Попробуем ее решить средствами C#. Вариантов решений может быть масса, но нас интересует грамотный подход, т.е. такое решение, при котором наша программа не превратится в кашу из кучи классов, лишних открытых методов, свойств и форм.
Здравый смысл подсказывает, что справочник должен быть представлен неким классом (назовем его CUserMgr), который позволяет основной программе получить все необходимые данные о пользователе и скрывает всю внутреннюю структуру внутри себя.
Опять же для простоты решим, что программе нужно получать только лишь логин и пароль пользователя по его идентификатору. Ну и конечно вызывать редактор пользователей. Таким образом у нас получается примерно такой открытый интерфейс нашего класса:
public class CUserMgr
{
// получить идентификатор по логину
public int GetID(string login) {...}
// получить логин по идентификатору
public string GetLogin(int id) {...}
// получить пароль по идентификатору
public string GetPassword(int id) {...}
// вызвать редактор пользователей
public void ShowEditor() {...}
}
Так же класс должен содержать еще набор закрытых методов для редактирования пользователей: добавление нового пользователя, удаление существующего, изменение параметров пользователя, загрузку и сохранение списка пользователей и т.д.
Редактор пользователей должен отобразить на экране окно списка пользователей, в котором можно добавить нового пользователя и удалить или отредактировать существующего. Для целей добавления/редактирования будет использоваться еще одно окошко - форма редактирования пользователя. Итак, получаем как минимум две формы, которые должны напрямую обращаться к внутренней структуре класса CUserMgr, т.е. к его закрытым методам.
Решение "в лоб", т.е. создание глобальных классов нужных нам форм ни к чему хорошему не приведет:
- о существовании этих форм должен знать только CUserMgr, остальные о них не должны даже подозревать, т.к. никому кроме CUserMgr они не нужны. У нас же они получаются глобальными, т.е. видимыми всеми классами. Это плохо.
- Чтобы формы могли обратиться к внутренней структуре класса CUserMgr придется внутреннюю структуру сделать открытой, т.к. друзья (friend) в C# не предусмотрены (об этом уже писалось здесь). Открытая структура класса менеджера приводит к путанице и соблазну использовать эти методы (которые по логике должны быть закрытыми) не по назначению. А как же инкапсуляция??? В этом случае ее просто невозможно реализовать! Ужас!
Все это можно перетерпеть если вы создаете класс, который будет использоваться только в вашей программе и код которого никогда не будет использован где-либо еще. А как же быть, если вы создаете коммерческую библиотеку? Неужели придется вот так вывалить на всеобщее обозрение потроха ваших классов? Это уже очень плохо. Получается, что в "самом объектно-ориентированном языке современности" (а именно так microsoft позиционирует его на рынке) использовать всю мощь этой самой объектно-ориентированности и не получится!
Изрядно поломав голову, я смог найти еще два решения: первое в стиле майкрософта - корявое и громоздкое, второе - единственно верное для C# и правильное с точки зрения ООП, но имеющее изрядный косяк при использовании IDE Microsoft Visual Studio. Разберем оба варианта.
Вариант номер раз (стиль программирования a'la Microsoft)
Суть заключается в избавлении от необходимости использовать одним классом внутренней структуры другого класса. Решение сводится к созданию классов, представляющих сущности используя в качестве базы класс формы. Мудрено? Да!
Нам необходимо создать отдельный класс, представляющий собой запись из БД (информацию об отдельном пользователе). Причем этот класс должен быть порожден от класса Form, чтобы он мог отобразить себя для редактирования данных пользователя. Набросаем примерную структуру:
public class CUser : Form
{
private string Login;
private string Password;
private int ID;
...
// конструктор
public CUser(int id, string login, string passw) {...}
public string GetLogin() { return Login; }
public string GetPassword() { return Password; }
public int GetID() { return ID; }
// вызов редактора пользователя
public void Edit() {...}
// загрузка и сохранение данных о пользователе
public bool Load() {...}
public bool Save() {...}
}
В общем нечто такое. Теперь нашему окну редактирования пользователя (классу CUser) уже не нужно обращаться к классу CUserMgr за информацией - все необходимое он содержит в себе.
Идем далее. Наш класс менеджера также должен быть наследником класса формы (Form). В этом случае он сможет так же отобразить себя для редактирования общего списка пользователей. И ему также не нужно будет обращаться к внутренней структуре других классов, т.к. список пользователей (объектов класса CUser) он содержит внутри себя. Он их будет создавать и удалять (редактирование списка пользователей), получать из них информацию и передавать ее в основную программу.
Вот примерно такой подход.
Согласитесь, что такая убогая симуляция дружественности классов имеет массу недостатков:
- Нормальной инкапсуляции все равно не получится, т.к. в реальной программе некоторые закрытые методы как CUser так и CUserMgr все же придется открыть.
- Класс CUser все еще глобален и видим любым и каждым со всеми вытекающими отсюда отрицательными последствиями.
- Для справочника пользователей, которых не больше сотни этот подход будет работать более-менее нормально. Но если это будет справочник артикулов продукции, которых может быть несколько десятков тысяч, то мы получи дикий перерасход памяти, ведь для каждого объекта будет создано отдельное (пусть и скрытое) окно. Это вызовет не только расход памяти, но и будет существенно тормозить программу.
Короче, это не выход, а скорее его имитация. Выкрутас, временная отмазка. В реальных крупных программах не советую его использовать.
Вариант номер два (реально объектно-ориентированный)
Второй вариант использует возможность C# создавать вложенные классы. Видимо их ввели в язык только с одной целью - как решение при отказе от дружественности классов. Тут надо сделать некоторые пояснения.
В C# введена возможность определить класс внутри другого класса. При этом встроенный класс получает статус "члена класса-оболочки", т.е. все объекты вложенного класса (кем бы и где бы они не были созданы) имеют ПОЛНЫЙ доступ к членам своего класса-оболочки: они могут использовать и защищенные и даже закрытые методы класса-оболочки. Поразмыслите над эти на досуге и найдете эту возможность достаточно удобной, хотя и несколько путанной в применении.
Как же будет выглядеть решение нашей задачи при использовании вложенных классов? А примерно вот так:
public class CUserMgr
{
class CUser
{
private string Login;
private string Password;
private int ID;
...
}
class CUserEdit : Form
{
public void Edit();
...
}
class CUserList : Form
{
public void ShowList() {...}
...
}
// закрытые методы и свойства
...
// "внешний интерфейс"
public bool Load() {...}
public bool Save() {...}
public void ShowEditor() {...}
}
Итак, вот что мы получаем при таком подходе:
- программа "видит" только класс CUserMgr, о существовании внутренних классов она даже не подозревает и естественно не может получить к ним доступ. Она может использовать только открытый "внешний интерфейс" класса CUserMgr. Внутренняя структура нашего менеджера пользователей полностью скрыта (инкапсуляция торжествует!).
- Можно как угодно изменять внутреннюю структуру CUserMgr и вложенных классов. Если внешний интерфейс не изменится, то эти модификации никак не скажутся на работе основной программы, использующей наш класс.
- Встроенные классы могут без проблем обращаться к внутренним (закрытым) методам класса CUserMgr, что позволяет в ряде случаев сильно упростить программирование этих классов.
В общем мы добились нормального решения поставленной задачи. Поздравляю.
"А где же косяк?" - спросите вы - "неужели microsoft наконец-то сделала все правильно?" :)
А косяк все же есть. Но на этот раз он не в языке, а в IDE Visual Studio.
Классы, созданные по описанной выше "технологии" будут нормально компилироваться и работать (а как же иначе - все ведь сделано верно, в соответствии с требованиями и возможностями языка C#). Но в IDE вы не сможете использовать визуальный редактор для форм, которые являются вложенными в другие классы (в нашем случае это CUserEdit и CUserList) - IDE вместо отображения формы просто будет материться по-английски. Поэтому проектирование пользовательского интерфейса превратится при описанном подходе в настоящую муку. :(
Почему так происходит, ведь с точки зрения языка все верно и никакого "криминала" в таком вложении нет? Этот вопрос нужно адресовать разработчикам Visual Studio, которые (как обычно) лучше нас с вами знают что и как должно работать.
