• Мыслите категориями устройств!
  • Один размер для всего не годится
  • Одна рука или две?
  • Возрастание роли навигационных средств при уменьшении экранного пространства
  • Списки или вкладки?
  • Пользовательские интерфейсы мобильных телефонов и важность соблюдения единообразия в использовании клавиш
  • Сенсорные экраны и важность использования крупных кнопок
  • Оптимизируйте ввод обычных данных
  • Убедитесь в том, что для механизмов автоматизированного ввода предусмотрены параллельные механизмы ввода вручную
  • Тестирование на эмуляторах и физических устройствах
  • Проектируйте код пользовательского интерфейса мобильного приложения таким образом, чтобы его можно было легко тестировать и модифицировать
  • Модель состояний для компоновки пользовательского интерфейса и управления им
  • Пример кода, демонстрирующий две различные модели компоновки для одного и того же приложения
  • Размещение элементов управления
  • Экранное пространство — ценная вещь
  • Разработка улучшенных пользовательских интерфейсов средствами .NET Compact Framework
  • Динамическое создание элементов управления
  • Создание пользовательских элементов управления и перекрытие поведения существующих элементов управления
  • Использование прозрачных областей растровых изображений
  • Встраивание изображений в виде ресурсов приложений
  • Резюме 
  • ГЛАВА 13

    Шаг 2: проектирование подходящего пользовательского интерфейса

    "Упрощение — это удаление ненужного, дабы нужное могло заговорить."

    (Ганс Гофман (Hans Hofmann) (1880–1966), американский художник и преподаватель, немец по происхождению) ((Encarta 2004. Quotations))

    Мыслите категориями устройств!

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

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

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

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

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

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

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

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

    Один размер для всего не годится

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

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

    Тот факт, что выпускаемые устройства отличаются друг от друга своими размерами и форм-факторами самым непосредственным образом влияет на применимость любого заданного пользовательского интерфейса на том или ином устройстве. Так, по своим физическим размерам визуальный пользовательский интерфейс смартфона значительно уже интерфейса Pocket PC. В то же время, каждый из этих интерфейсов значительно уже интерфейса планшетного компьютера. Эти различия вовсе не произвольны и обусловлены тем, какой именно способ ношения устройства предполагается (например, в кармане брюк, кармане пиджака, в рюкзаке или портфеле), и в каких ситуациях оно должно использоваться. Размеры экрана устройства оказывают заметное влияние на способ представления информации, который вы должны выбрать. Важно и то, что механизмы ввода также меняются от устройства к устройству. Такие устройства, как смартфоны, снабжены расширенной 12-клавишной клавиатурой, но экранные указатели для них не предусмотрены, тогда как устройства PDA (персональные помощники) в качестве основного механизма ввода оборудуются сенсорным экраном.

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

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

    Каждая категория устройств характеризуется своим оптимальным набором задач и моделей использования, для которых она предназначена. Мобильные телефоны используются главным образом для того, чтобы вести телефонные разговоры, просматривать ранее введенную информацию и вводить небольшие объемы новой информации, обычно — в виде одной-двух текстовых строк или простых наборов чисел. Интерфейсы устройств категории Pocket PC в состоянии предложить гораздо большие возможности дисплея для исследования информации, однако, так как в основном они выпускаются без встроенной клавиатуры, они не приспособлены для ввода текста в свободной форме. Устройства Pocket PC Phone достаточно удобны для ведения телефонных разговоров, но если такое устройство используется пользователем только для этого, то его выбор был неверным.

    Устройствами с форм-фактором планшетного компьютера часто можно пользоваться даже стоя, поскольку они обеспечивают свободный ввод информации с помощью пера, но ввод текста в подобных условиях все равно остается затруднительным. Лэптопы хорошо приспособлены для ввода и исследования информации, но не для быстрых телефонных звонков или немедленного извлечения информации. Это обусловлено относительно длительным периодом загрузки этих устройств и способом их транспортировки; вы не можете вынуть полнофункциональный лэптоп из кармана, включить его и набрать телефонный номер в течение восьми секунд, равно как и оставить его без присмотра на длительное время для приема телефонных звонков из-за ограниченного срока действия батарей без перезарядки. Очень важно, чтобы пользовательский интерфейс вашего приложения согласовывался с общими внешними характеристиками устройства, на котором оно выполняется.

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

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

    Одна рука или две?

    Важной характеристикой вашего мобильного приложения является то, какой способ его использования предполагается — с помощью одной руки или двух? Обычно этот выбор определяется типом оборудования мобильного устройства, на которое ориентировано ваше приложение.

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

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

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

    Если общее время сеанса работы с устройством составляет примерно 20 секунд, то скостить лишний пяток секунд только за счет предоставления простой и эффективной модели навигации — совсем неплохо.

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

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

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

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

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

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

    Возрастание роли навигационных средств при уменьшении экранного пространства

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

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

    В табл. 13.1 приводятся сравнительные характеристики дисплеев настольных компьютеров, а также устройств Pocket PC и Smartphone. Большинство современных дисплеев для настольных компьютеров имеют размеры свыше 1024×768 пикселей, тем самым предоставляя для отображения информации большое экранное пространство.


    Таблица 13.1. Относительные размеры экранов различных устройств

    Тип устройства Типичное разрешение Количество пикселей Относительный размер
    Настольные компьютеры/лэптопы 1024×768 786432 100%
    Pocket PC 240×320 76800 9,77%
    Смартфоны 176×220 38720 4,92%

    Размеры типичного дисплея Pocket PC обеспечивают менее 10% площади экрана, предоставляемой дисплеями настольных компьютеров с низким и средним разрешением или дисплеями лэптопов. Величина этого показателя для лэптопов составляет 5%.

    Это не столь ужасающе, как могло бы показаться, по следующим причинам:

    1. Нa дисплеях настольных компьютеров обычно используется больше кнопок, рисунки имеют большие размеры, а для представления информации отводится большее экранное пространство по сравнению с их мобильными собратьями. Как правило, в этом случае на любом информационном экране имеется больше элементов управления, а их группы располагаются на большем расстоянии друг от друга.

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

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

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

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

    ■ Продолжая предыдущую аналогию, можно сказать, что на экране Pocket PC приложение способно отображать только один абзац информации в любой заданный момент времени. Абзац состоит из шести-восьми предложений, каждое из которых аналогично расположенным на экране элементам управления. Навигация между различными абзацами осуществляется с помощью вкладок, находящихся в нижней части экрана. При проектировании пользовательских интерфейсов для экранов с размерами экрана Pocket PC очень важно распределить функциональность между логическими абзацами, определить, какие абзацы являются наиболее значимыми и должны быть представлены в первую очередь, и решить, каким образом пользователь будет переходить от одного абзаца к другому при помощи вкладок. В идеальном случае пользователю должно быть достаточно одного взгляда, чтобы оценить подробное содержимое одного из абзацев информации и суметь понять в общих чертах, какая информация содержится в остальных абзацах. Для переключения между различными "абзацами" информации пользователь должен принимать осознанное решение, обычно — путем выбора вкладки TabControl, расположенной в нижней части сенсорного экрана устройства.

    ■ Экран приложения на смартфоне отображает несколько "предложений" информации в любой заданный момент времени — вероятнее всего, от одного до двух "предложений". Этого объема достаточно для отображения коротких параграфов, но более крупные темы, требующие "абзацев" большего размера, часто приходится разбивать на два взаимосвязанных информационных экрана. Навигационная метафора основывается либо на одномерном перемещении между экранами с продвижением в прямом или обратном направлении, либо на явном списке экранов. Пользовательский интерфейс может находиться в одном из двух режимов:

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

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

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

    Списки или вкладки?

    Элементы управления List и TabView представляют две распространенные навигационные метафоры пользовательских интерфейсов для мобильных устройств, у каждой из которых есть свои достоинства.

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

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

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

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

    На рис. 13.1 представлен основанный на использовании вкладок интерфейс Pocket PC, позволяющий осуществлять навигацию по интерфейсу калькулятора для Pocket PC, предназначенного для выполнения научных расчетов; каждая вкладка предлагает определенную группу сходных по своему назначению функциональных средств, и пользователи могут легко переходить от одной вкладки к другой в зависимости от того, какие расчеты им нужно произвести.

    Рис. 13.1.  Предоставление доступа к большому количеству функциональных средств, объединенных в группы, за счет использования вкладок 

    Пользовательские интерфейсы мобильных телефонов и важность соблюдения единообразия в использовании клавиш

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

    Сенсорные экраны и важность использования крупных кнопок

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

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

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

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

    ■ Желание "уместить все на одном экране".  Попытки размещения на единственном экране чрезмерного количества информации приводят к скученности элементов управления и уменьшению их размеров. Чем более плотно располагаются элементы управления, тем выше вероятность их неправильного выбора.

    ■ Привлечение встроенных программных механизмов ввода. В таких устройствах, как Pocket PC, предлагается всплывающая программная клавиатура, которой пользователь может воспользоваться для ввода информации. Это средство доказало свою полезность, однако оно является универсальным, и поэтому в нем предусмотрено множество клавиш, которые должны быть все отображены на небольшом экранном пространстве. Если ваши запросы носят более специфический характер, то механизмы ввода, предусматриваемые пользовательским интерфейсом, вам придется оптимизировать.

    На рис. 13.2 показано то же самое приложение, в котором используется программная клавиатура (software input panel — SIP) Хотя программная клавиатура представляет собой в целом неплохой механизм для ввода букв, цифр и символов, укрупненные элементы управления пользовательского интерфейса, предназначенные специально для калькулятора, применяемого при проведении научных расчетов, предлагают более точный и легкий в использовании интерфейс. Типовая программная клавиатура удобна для ограниченного ввода информации общего назначения, но ее можно и необходимо улучшать в тех случаях, когда задаче требуется более специфический ввод. Проектируя пользовательские интерфейсы для устройств с сенсорными экранами, вы должны делать размеры элементов управления настолько большими, а их целевое назначение настолько специализированным, насколько это возможно.

    Оптимизируйте ввод обычных данных

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

    Рис. 13.2. Сравнение различных разновидностей механизмов ввода в пользовательском интерфейсе


    Один из способов организации ввода данных состоит в том, чтобы пользователь осуществлял ввод вручную, буква за буквой (например, "6 января 2006" или "6/1/2006"), однако такой ввод будет занимать слишком много времени и сопровождаться неминуемыми ошибками, не говоря уже о проблеме учета различий в национальных стандартах форматов дат. Лучше предоставить пользователю окна списков для выбора дня, месяца и года, тем самым избавляя его от необходимости вводить эти данные. Еще лучше использовать всплывающие элементы управления или диалоговые окна, отображающие календарь, что позволит быстро выбрать дату, которая и заполнит соответствующее поле ввода. (Примечание. С учетом того, что необходимость во вводе дат возникает очень часто, можно ожидать, что в будущих версиях .NET Compact Framework календарь будет добавлен, однако общая проблема ввода сложных данных будет по-прежнему оставаться актуальной.)

    При проектировании пользовательских интерфейсов для мобильных устройств иногда допускают ошибку, пытаясь сэкономить экранное пространство и память программы за счет использования элементов TextBox для ввода сложных данных; это вынуждает пользователя вручную вводить, например, такие сложные данные, как даты. В лучшем случае достижение такой "эффективности" — это пиррова победа; затраты экранного пространства, памяти программы и ваших усилий, обеспечивающие экономию времени пользователя и повышение точности ввода, никогда не будут напрасными.

    Описанный подход проиллюстрирован рис. 13.2, на котором можно видеть как кнопки, так и выпадающие списки, которые облегчают ввод данных для калькулятора, предназначенного для выполнения научных расчетов. Например, вместо того чтобы побуквенно вводить выражение sin(), обозначающее тригонометрическую функцию, пользователю достаточно просто выбрать нужную функцию в выпадающем списке. Часто встречающиеся переменные x, у и z представлены кнопками на форме вместе с другими распространенными математическими символами. Ввод сложных математических формул с помощью этого интерфейса выполняется гораздо быстрее, чем при вводе вручную посредством стандартной экранной клавиатуры. Несомненно, описанный интерфейс может быть дополнительно оптимизирован.

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

    В специализированных мобильных приложениях часто применяется нестандартное оборудование, позволяющее ускорить ввод данных. Хорошим примером такого оборудования может служить устройство для считывания штрих-кодов, подключенное к мобильному устройству, что позволяет очень быстро считывать данные с этикеток со штрих-кодами, наклеенных на физические объекты. Если мобильное приложение должно взаимодействовать с физическим окружением, то использование сканеров штрих-кодов и даже устройств для распознавания речи способно существенно расширить сферу применимости приложения и повысить производительность труда пользователя. Подобные механизмы ввода реальных данных следует изучать и использовать при любой возможности, если только это представляется интересным. Не следует, однако, впадать в крайность и полагаться только на такие механизмы. Этикетки, на которые нанесены штрих-коды, и считывающие устройства могут загрязняться или повреждаться, а использование устройств для распознавания речи может затрудняться повышенным уровнем шумов в окружающей среде и нерегулярными ежедневными изменениями характеристик человеческого голоса, что увеличивает вероятность погрешностей. Поэтому очень важно всегда предусматривать механизм ручной подстраховки, который можно применять для ввода данных вручную в тех случаях, когда автоматизированные механизмы ввода дают сбой. По той же причине, по которой кассирам в супермаркетах предоставляется возможность ввести код продукта вручную, если неоднократные попытки считывания кода сканером оказались безуспешными, в вашем мобильном приложении, отвечающем самому последнему слову техники, должен быть предусмотрен специальный пользовательский интерфейс, обеспечивающий быстрый ввод данных вручную в тех случаях, когда сделать это при помощи средства автоматизированного ввода не удается. О мобильном приложении, которое 90% времени работает нормально, но остальные 10% времени не в состоянии функционировать из-за неполадок, нельзя сказать, что оно работает нормально и надежно; вместе с тем, если 10% времени приложение вынуждено требовать ввода вручную, но прекраснейшим образом функционирует 90% времени, то оно может считаться вполне работоспособным.

    Тестирование на эмуляторах и физических устройствах

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

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

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

    ■ Мышь и клавиатура настольного компьютера/лэптопа могут ввести вас в заблуждение. Ввод букв в текстовом поле при помощи клавиатуры — это не то же самое, что ввод данных с использованием 12-кнопочной клавиатуры телефона. Выполнить щелчок мышью — это не то же самое, что надавить пальцем или пером в нужной точке экрана.

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

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

    ■ Эмулятор можно легко сбросить, и он не используется для других целей в промежутках между выполнением тестовых запусков вашего приложения. Эмулированный смартфон — это вовсе не тот самый телефон, который вы используете для телефонных переговоров и ведения расписания встреч. Тот факт, что физическое устройство часто предназначается не только для определенного способа его использования, но и для выполнения других функций, имеет очень большое значение. Вам необходимо убедиться в нормальной работе приложения в условиях, когда устройство функционирует в течение 24 часов 7 дней в неделю, а также попытаться понять, каким образом другие приложения, выполняющиеся на устройстве, могут влиять на ваше приложение. Добиться точной имитации такого рода реальных условий эксплуатации приложения очень трудно.

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

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

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

    Рис. 13.3. Сравнение удобства использования пользовательского интерфейса на эмуляторе и физическом устройстве


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

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

    Рис. 13.4. Более удобные альтернативные варианты компоновки экрана, основанные на результатах тестирования приложения на физическом устройстве 


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

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

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

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

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

    2. Использование косвенных функций для обновления пользовательского интерфейса. Изменение информации, отображаемой на экране мобильного устройства, может осуществляться двумя способами: 1) непосредственно, путем использования встроенного кода (например, Label1.Text = newText) и 2) косвенно (например, UpdateDownloadStatusText(newText);). Преимущество косвенного подхода заключается в том, что он позволяет вашему коду отделить фактически используемый элемент управления от того обновления, которое вы хотите выполнить. Косвенная функция UpdateDownloadStatusText(newText); сегодня может обновлять элемент Label1, но, возможно, завтра вы решите, что лучше будет отобразить текст поверх растрового изображения или вывести его в виде текста, прокручиваемого в узкой полоске экрана. На рис 13.5 показана стрелка, направленная вниз от пользовательского интерфейса (ПИ) к блоку функций обновления ПИ, а также стрелки, направленные вверх от блока логики приложения. Стрелка, направленная вниз от пользовательского интерфейса, представляет код обновления дисплея, который выполняется в результате обработки событий, генерируемых пользовательским интерфейсом. Например, обычной реакцией на щелчок на кнопке может быть обновление текста в ярлыке или окне списка. Вместо того чтобы выполнять это обновление непосредственно в обработчике событий кнопки, можно поступить гораздо более гибко и воспользоваться вызовом обобщенной функции UpdateXXXXXXX(), которая и выполнит всю работу. Введение промежуточной функции позволяет разорвать прочную связь между обоими элементами управления и при необходимости использовать другую реализацию одного из них. Проектируя пользовательский интерфейс своего мобильного приложения, вы должны избегать тесного связывания как элементов управления с логикой приложения, так и отдельных элементов управления, являющихся частью пользовательского интерфейса, между собой. Наличие единственного уровня косвенности позволяет устранить такое тесное связывание.

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

    Рис. 13.5. Надежное отделение логики приложения от пользовательского интерфейса 

    Модель состояний для компоновки пользовательского интерфейса и управления им

    Чтобы проиллюстрировать плодотворность концепции пользовательского интерфейса, управляемого конечным автоматом, целесообразно привести простой пример. Этот пример продемонстрирует эффективность подхода, основанного на конечном автомате, как фактора, облегчающего внесение изменений в пользовательский интерфейс по мере эволюции проекта. В продолжение сценариев, иллюстрируемых рисунками 13.3, 13.4 и 13.5, код рассматриваемого примера реализует основные компоненты пользовательского интерфейса приложения для Pocket PC, предназначенного для изучения в игровой форме слов иностранного языка. По мере того как пользователь приложения отвечает на вопросы, выбирая правильный, по его мнению, ответ из нескольких предложенных вариантов, в секторе игрового поля осуществляются некоторые действия, соответствующие тому, какой ответ был дан пользователем — правильный или неправильный.

    Сектор игрового поля занимает примерно 60% доступной поверхности экрана; его назначение состоит в том, чтобы развлекать пользователя и поддерживать в нем интерес к изучению иностранных слов. Чтобы упростить задачу, код пользовательского интерфейса приложения на данном этапе будет представлять игровое поле в виде растрового прямоугольника желтого цвета, отображаемого в рамке. Вместо этого вы можете заполнить рамку загружаемым растровым изображением, чтобы придать игровому полю более реалистический вид и наблюдать за происходящими в нем изменениями, вызываемыми взаимодействием пользователя с приложением. В данном приложении концепции пользовательского интерфейса иллюстрируются на примере игры с множественным выбором, но эти концепции в равной степени применимы к любому приложению, начинай от бизнес-приложений и заканчивая сценарными играми; модель пользовательского интерфейса на основе конечного автомата отличается поразительной универсальностью.

    Как показано на рис. 13.6, пользовательский интерфейс мобильного приложения имеет четыре состояния:

    1. Начальный экран. Мы хотим, чтобы при первоначальном запуске приложения отобразился экран, который приглашает их в игру, предоставляет необходимые инструкции и позволяет легко начать игру. Я остановился на очень простом варианте экрана, отображающем лишь "стартовую" кнопку и игровое поле. Возможно, вы захотите добавить текстовые инструкции или запустить анимацию на начальном экране, чтобы сразу же разжечь интерес пользователя к игре. В любом случае пользователь должен только щелкнуть на кнопке Start, после чего он сразу попадает в следующее состояние игры.

    2. Формулировка вопроса. В этом состоянии мобильное приложение предлагает пользователю перевести предложенное слово. В нашем коде мы решили отображать вопрос в элементе управления TextBox, но это вовсе не является обязательным. Например, вопрос может отображаться в элементе управления Label или выводиться поверх растрового изображения игрового поля, если оно имеется. Если целевое устройство поддерживает синтезатор речи, то вопрос может задаваться даже голосом. Мы решили использовать текстовое поле, работающее в режиме только для чтения, поскольку его возможности нас полностью устраивают и позволяют без труда отображать большие объемы информации за счет использования полос прокрутки, если это потребуется. В дополнение к отображению вопроса приложение предлагает пользователю выбрать один из двух способов ответа на него. Пользователь может выбрать вариант Challenge Me! (Ну-ка, спрашивай!), если считает, что знает правильный ответ, или Can't Think of It (Ума не приложу), если нуждается в дальнейших подсказках; для каждого из этих двух вариантов выбора могут быть предусмотрены различные правила подсчета очков. Чтобы не усложнять пробный код, в нашей реализации оба варианта обрабатываются по одному и тому же принципу и перемещают пользователя в следующее состояние приложения независимо от того, какой вариант был выбран; возможно, вы захотите добавить код, который будет по- разному подсчитывать очки для этих вариантов, или просто исключите одну кнопку, если решите этого не делать. В нашей реализации мобильного приложения для представления обоих вариантов было решено использовать элементы управления Button, но, как и ранее, такое решение не является обязательным; точно так же для предоставления этих вариантов выбора можно использовать переключатели или выпадающий список, если эти метафоры являются более подходящими для целевого устройства. Какую метафору пользовательского интерфейса выбрать, зависит от природы приложения, а также от типа устройства, на котором оно будет выполняться.

    3. Предоставление пользователю нескольких вариантов ответа. В этом состоянии приложения наша игра для мобильных устройств предлагает пользователю несколько вариантов ответа на заданный ему вопрос. Для пользовательского интерфейса Pocket PC мы вновь решили использовать элементы управления Button; для смартфонов более подходящими были бы, пожалуй, нумерованные окна списков. Кроме того, в этом состоянии мы решили динамически уменьшать размер элемента управления TextBox, чтобы кнопки всех вариантов выбора уместились на экране. Пользователи могут угадывать правильный ответ до тех пор, пока он не будет получен. В нашей реализации в случае выбора неправильного ответа соответствующая кнопка затеняется (для ее свойства Enabled устанавливается значение false). В логику нашего приложения также можно включить код для отображения того, что в действительности означает неверно выбранное вами слово. Поскольку, в силу ограниченности размеров текстового окна, для отображения этой информации места недостаточно, мы могли бы вывести этот текст поверх растрового изображения игрового поля; этот вариант реализации мы также оставляем на усмотрение читателя.

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

    Представленный в листинге 13.1 код демонстрирует несколько полезных концепций построения гибких пользовательских интерфейсов мобильных устройств. Проектируя этот пользовательский интерфейс, я начал с идеи размещения элементов поверх игрового поля. Зная, что окончательный выбор подходящего пользовательского интерфейса определится лишь после его тестирования в реальных условиях и внесения соответствующих изменений, при написании кода было желательно использовать принципы централизации и абстрагирования кода таким образом, чтобы процесс его модифицирования не вызывал особых затруднений и не заставлял вносить сложные изменения в разных местах кода пользовательского интерфейса или логики приложения. Именно поэтому я решил поместить основную часть кода динамической компоновки в функцию конечного автомата, которая называется StateChangeForGameUI(). Любые изменения размеров или позиционирование элементов управления управляются этим кодом. Если нам необходимо немного подправить компоновку элементов или даже радикально изменить ее, мы знаем, что все необходимое осуществляется здесь; благодаря этому снижается объем работы по отслеживанию сложной и разбросанной по всему коду логики компоновки, а приложение становится более надежным. Возложив всю ответственность за поддержание компоновки пользовательского интерфейса на одну функцию (конечный автомат), мы значительно облегчили выполнение экспериментов с различными возможными вариантами компоновки, поскольку теперь это не сможет дестабилизировать работу остальной части кода пользовательского интерфейса и логики приложения.

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

    Рис. 13.6. Отображение пользовательского интерфейса в верхней части формы


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

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

    Рис. 13.7. Отображение пользовательского интерфейса в нижней части формы


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

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

    Пример кода, демонстрирующий две различные модели компоновки для одного и того же приложения

    Приведенный в листинге 13.1 код вносится в форму в проекте Pocket PC. Для создания и запуска приложения потребуется выполнить следующие действия:

    1. Запустите Visual Studio .NET (2003 или более позднюю версию) и выберите в качестве типа приложения C# Smart Device Application.

    2. Выберите в качестве целевой платформы Pocket PC. (Для вас будет автоматически создан проект, и на экране появится окно конструктора форм Pocket PC.)

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

     • TextBox; переименуйте его в textBoxAskQuestion, установите значение true для его свойств MultiLine и ReadOnly.

     • PictureBox; переименуйте его в pictureBoxGameBoard.

     • Button; переименуйте его в buttonShowAnswers_AdvancedVersion.

     • Button; переименуйте его в buttonShowAnswers_SimpleVersion.

     • Button; переименуйте его в buttonAskQuestion.

     • Button; переименуйте его в buttonAnswer0. 

     • Button; переименуйте его в buttonAnswer1. 

     • Button; переименуйте его в buttonAnswer2.

     • Button; переименуйте его в buttonAnswer3.

     • Button; переименуйте его в buttonAnswer4. 

     • Button; переименуйте его в buttonAnswer5. 

    4. Дважды щелкните на пустом участке формы в окне конструктора форм и введите приведенный ниже код Form_Load в автоматически сгенерированную и подключенную функцию обработчика событий.

    5. Поочередно переходя от одной из вышеперечисленных кнопок Button к другой, щелкните на кнопке в окне конструктора форм. Введите приведенный ниже код button<ИмяКнопки>_Click в автоматически сгенерированную и подключенную функцию обработчика событий.

    6. Введите оставшуюся часть приведенного ниже кода.

    7. Установите для свойства MinimizeBox формы значение false. Благодаря этому во время выполнения приложения в верхней правой части формы появится кнопка OK, с помощью которой вы сможете легко закрыть форму и выйти из приложения. Эта возможность оказывается очень полезной при многократном тестировании приложения

    8. В самом верху файла кода формы введите в качестве первой строки #define PLAYFIELD_ON_BOTTOM. 

    9. Дважды запустите приложение: один раз с подключенной директивой условной компиляции #define PLAYFIELD_ON_BOTTOM, а второй — с предварительным отключением этой же директивы при помощи символов комментария (то есть //#define PLAYFIELD_ON_BOTTOM), и отметьте для себя различия между двумя моделями компоновки. Запустите оба варианта на физическом устройстве и выясните, какая модель является более предпочтительной с точки зрения внешнего вида приложения и удобства работы с ним, а также возможностей обзора игрового поля.

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

    Листинг 13.1. Использование конечного автомата для экспериментов с двумя различными вариантами компоновки пользовательского интерфейса

    //--------------------------------------------------------------------------

    //Конечный автомат, который управляет отображением кнопок, закрываемых рукой

    //--------------------------------------------------------------------------


    private enum GameUIState {

     startScreen = 1,

     waitForNextQuestion = 2,

     waitForUserToStateKnowledge = 4,

     waitForUserToAnswerMultipleChoice = 8

    }


    //Текущее состояние игры

    private GameUIState m_GameUIState;


    //==========================================================================

    //Конечный автомат, используемый для управления пользовательским интерфейсом

    //==========================================================================

    private void StateChangeForGameUI(GameUIState newGameUIState) {

     m_GameUIState = newGameUIState;

     switch (newGameUIState) {

     case GameUIState.startScreen:

      buttonAskQuestion.Visible = true;

      buttonAskQuestion.Text = "Start";

      //Скрыть текстовое окно

      textBoxAskQuestion.Visible = false;

      SetAnswerButtonVisibility(false);

      SetDifficultyButtonVisibility(false);

      break;

     case GameUIState.waitForNextQuestion:

      setQuestionText("List answer details here... \r\n" +

       "Lots of space to write...\r\n" +

       "Waiting for user to select next question...");

      textBoxAskQuestion.Visible = true;

      buttonAskQuestion.Text = "Next";

      buttonAskQuestion.Visible = true;


      //Убедиться в том, что кнопка отображается на переднем плане

      buttonAskQuestion.BringToFront();

      SetAnswerButtonVisibility(false);

      SetDifficultyButtonVisibility(false);

    #if PLAYFIELD ON_BOTTOM //ПОЛЕ ИГРЫ располагается под пользовательскими

                            //элементами управления

      textBoxAskQuestion.Height = pictureBoxGameBoard.Top - 2;

    #else //ПОЛЕ ИГРЫ располагается над пользовательскими

          //элементами управления

      textBoxAskQuestion.Top = pictureBoxGameBoard.Top + pictureBoxGameBoard.Height + 2;

      textBoxAskQuestion.Height = this.Height - textBoxAskQuestion.Top;

    #endif

      break;

     case GameUIState.waitForUserToStateKnowledge:

      SetTextForVocabularyQuestion();

      textBoxAskQuestion.Visible = true;

      buttonAskQuestion.Visible = false;

      SetAnswerButtonVisibility(false);

      SetDifficultyButtonVisibility(true);

    #if PLAYFIELD_ON_BOTTOM //ПОЛЕ ИГРЫ располагается под пользовательскими

                            //элементами управления

      textBoxAskQuestion.Height = buttonShowAnswers_AdvancedVersion.Top - 2;

    #else //ПОЛЕ ИГРЫ располагается над пользовательскими

          //элементами управления

      textBoxAskQuestion.Top = buttonShowAnswers_AdvancedVersion.Top + buttonShowAnswers_AdvancedVersion.Height + 2;

      textBoxAskQuestion.Height = this.Height - textBoxAskQuestion.Top;

    #endif

      break;

     case GameUIState.waitForUserToAnswerMultipleChoice:

      buttonAskQuestion.Visible = false;

      SetDifficultyButtonVisibility(false);

      //Сделать кнопки доступными, чтобы пользователь мог щелкать на них

      SetAnswerButtonEnabled(true);

      SetAnswerButtonVisibility(true);

    #if PLAYFIELD_ON_BOTTOM //ПОЛЕ ИГРЫ располагается под пользовательскими

                           //элементами управления

      textBoxAskQuestion.Height = buttonAnswer0.Top - 2;

    #else //ПОЛЕ ИГРЫ располагается над пользовательскими

          //элементами управления.

      //Разместить текстовое окно таким образом, чтобы экран использовался

      //эффективно

      textBoxAskQuestion.Top = buttonAnswer5.Top + buttonAnswer5.Height + 2;

      textBoxAskQuestion.Height =this.Height - textBoxAskQuestion.Top;

    #endif

      break;


    //======================================================================

    //Задать статическую компоновку нашего пользовательского интерфейса.

    //Сюда входят все элементы, позиции которых остаются фиксированными.

    //Изменения в остальные свойства внесет конечный автомат

    //пользовательского интерфейса

    //======================================================================

    private void SetStartControlPositionAndState() {

     pictureBoxGameBoard.Width = 240;

     pictureBoxGameBoard.Height = 176;

     //Установить размеры кнопок множественного выбора вариантов ответов

     const int answerButtons_dx = 117;

     const int answerButtons_dy = 18;

     buttonAnswer0.Width = answerButtons_dx;

     buttonAnswer0.Height = answerButtons_dy;

     buttonAnswer1.Size = buttonAnswer0.Size;

     buttonAnswer2.Size = buttonAnswer0.Size;

     buttonAnswer3.Size = buttonAnswer0.Size;

     buttonAnswer4.Size = buttonAnswer0.Size;

     buttonAnswer5.Size = buttonAnswer0.Size;

     buttonShowAnswers_AdvancedVersion.Width = answerButtons_dx;

     buttonShowAnswers_AdvancedVersion.Height = 24;

     buttonShowAnswers_SimpleVersion.Size = buttonShowAnswers_AdvancedVersion.Size;


     //Расстояние (в пикселях) между соседними кнопками

     const int dx_betweenButtons = 3;

     const int dy_betweenButtons = 2;

     const int answerbuttons_beginX = 3;


     //Создать задний план для нашего изображения, чтобы мы видели

     //его в процессе тестирования

     System.Drawing.Bitmap gameBoard;

     gameBoard = new System.Drawing.Bitmap(pictureBoxGameBoard.Width, pictureBoxGameBoard.Height);

     System.Drawing.Graphics gameboard_gfx;

     gameboard_gfx = System.Drawing.Graphics.FromImage(gameBoard);

     gameboard_gfx.Clear(System.Drawing.Color.Yellow);

     System.Drawing.Pen myPen = new System.Drawing.Pen(System.Drawing.Color.Blue);

     gameboard_gfx.DrawRectangle(myPen, 2, 2, gameBoard.Width-4, gameBoard.Height-6);

     myPen.Dispose();

     gameboard_gfx.Dispose();

     pictureBoxGameBoard.Image = gameBoard;


     //Разместить текстовое окно, в котором содержатся задаваемые вопросы,

     //a также подробные ответы для пользователей

     textBoxAskQuestion.Left = 0;

     textBoxAskQuestion.Width = 240;

     buttonAskQuestion.Width = 64;

     buttonAskQuestion.Height = 20;


    #if PLAYFIELD_ON_BOTTOM //ПОЛЕ ИГРЫ располагается под пользовательскими

                            //элементами управления

     const int answerbuttons_beginY = 42;

     const int showanswers_beginY = 77;


     //--------------------------------------------------------

     //Задать кнопки выбора вариантов Easy или Hard режима игры

     //--------------------------------------------------------

     buttonShowAnswers_AdvancedVersion.Top = showanswers_beginY;

     buttonShowAnswers_SimpleVersion.Top = showanswers_beginY;


     //------------------------------

     //Задать набор вариантов ответов

     //------------------------------


     //Задать элемент управления, по которому будут выравниваться

     //все остальные элементы управления

     buttonAnswer0.Top = answerbuttons_beginY;


     //Поместить PictureBox под элементами управления

     pictureBoxGameBoard.Top = (answerButtons_dy + dy_betweenButtons) * 3 + answerbuttons beginY;

     buttonAskQuestion.Top = 0;

     buttonAskQuestion.Left = 174;

     textBoxAskQuestion.Top = 0;

    #else //ПОЛЕ ИГРЫ располагается над пользовательскими

          //элементами управления

     const int answerbuttons_beginY = 174;

     //--------------------------------------------------------

     //Задать кнопки выбора вариантов Easy или Hard режима игры

     //--------------------------------------------------------

     buttonShowAnswers_AdvancedVersion.Top = answerbuttons_beginY;

     buttonShowAnswers_SimpleVersion.Top = answerbuttons_beginY;


     //-----------------------------

     //Задать набор вариантов ответа

     //-----------------------------


     //Задать элемент управления, по которому будут выравниваться

     //все остальные элементы управления

     buttonAnswer0.Top = answerbuttons_beginY;

     pictureBoxGameBoard.Top = 0;

     buttonAskQuestion.Top = answerbuttons_beginY;

     buttonAskQuestion.Left = 174;

    #endif

     buttonShowAnswers_AdvancedVersion.Left = answerbuttons_beginX;

     buttonShowAnswers_SimpleVersion.Left = buttonShowAnswers_AdvancedVersion.Left + answerButtons_dx + dx betweenButtons;

     pictureBoxGameBoard.Left = 0;

     pictureBoxGameBoard.Width = 240;

     pictureBoxGameBoard.Height = 172;

     buttonAnswer0.Left = answerbuttons_beginX;

     buttonAnswer1.Left = buttonAnswer0.Left + answerButtons_dx + dx_betweenButtons;

     buttonAnswer1.Top = buttonAnswer0.Top;


     //следующий ряд

     buttonAnswer2.Left = buttonAnswer0.Left;

     buttonAnswer2.Top = buttonAnswer0.Top + answerButtons_dy + dy_betweenButtons;

     buttonAnswer3.Left = buttonAnswer2.Left + answerButtons_dx + dx_betweenButtons;

     buttonAnswer3.Top = buttonAnswer2.Top;


     //следующий ряд

     buttonAnswer4.Left = buttonAnswer2.Left;

     buttonAnswer4.Top = buttonAnswer2.Top + answerButtons_dy + dy_betweenButtons;

     buttonAnswer5.Left = buttonAnswer4.Left + answerButtons_dx + dx_betweenButtons;

     buttonAnswer5.Top = buttonAnswer4.Top;

    }


    //-------------------------------------------------------------

    //Вспомогательная функция, которая позволяет задавать состояние

    //видимости кнопок, отображающих ответы из словаря

    //-------------------------------------------------------------

    private void SetAnswerButtonVisibility(bool visibleState) {

     buttonAnswer0.Visible = visibleState;

     buttonAnswer1.Visible = visibleState;

     buttonAnswer2.Visible = visibleState;

     buttonAnswer3.Visible = visibleState;

     buttonAnswer4.Visible = visibleState;

     buttonAnswer5.Visible = visibleState;

    }


    //-----------------------------------------------------------------

    //Вспомогательная функция, вызываемая для задания свойств видимости

    //некоторых элементов управления

    //-----------------------------------------------------------------

    private void SetDifficultyButtonVisibility(bool visibleState) {

     buttonShowAnswers_AdvancedVersion.Visible = visibleState;

     buttonShowAnswers_SimpleVersion.Visible = visibleState;

    }


    //-------------------------------------------------------------

    //Вспомогательная функция, которая позволяет задавать состояние

    //видимости кнопок, отображающих ответы из словаря

    //-------------------------------------------------------------

    private void SetAnswerButtonEnabled(bool enabledState) {

     buttonAnswer0.Enabled = enabledState;

     buttonAnswer1.Enabled = enabledState;

     buttonAnswer2.Enabled = enabledState;

     buttonAnswer3.Enabled = enabledState;

     buttonAnswer4.Enabled = enabledState;

     buttonAnswer5.Enabled = enabledState;

    }


    //-----------------------------------------------------------------

    //Задает текст в текстовом окне и кнопках,

    //необходимых для формулирования вопросов.

    //

    //B случае практической реализации эта функция должна просматривать

    //вопросы динамически

    //-----------------------------------------------------------------

    private void SetTextForVocabularyQuestion() {

     setQuestionText("What is the English word for 'der Mensch'?");

     buttonAnswer0.Text = "Four";

     buttonAnswer1.Text = "Person";

     buttonAnswer2.Text = "Three";

     buttonAnswer3.Text = "To Jump";

     buttonAnswer4.Text = "Newspaper";

     buttonAnswer5.Text = "Brother";

    }


    //Вызывается для оценки варианта ответа, выбранного пользователем

    private void evaluateMultipleChoiceAnswer(Button buttonClicked, int selection) {

     //Примечание: в практической реализации правильный номер ответа

     //определяется динамически и не всегда соответствует "кнопке № 1"

     //Если выбранный пользователем вариант ответа не является правильным,

     //отменить доступ к нажатой кнопке

     if  (selection ! = 1) {

      //Выбранный вариант ответа является неправильным

      buttonClicked.Enabled = false;

     } else {

      //Пользователь выбрал правильный ответ, продолжить игру

      StateChangeForGameUI(GameUIState.waitForNextQuestion);

     }

    }


    //Абстракция, задающая текст вопросов

    void setQuestionText(string textIn) {

     textBoxAskQuestion.Text = textIn;

    }


    //----------------------------------------------------------------

    //ОБРАБОТЧИК СОБЫТИЙ: Пользователь желает увидеть следующий вопрос

    //----------------------------------------------------------------

    private void buttonAskQuestion_Click(object sender, System.EventArgs e) {

     SetTextForVocabularyQuestion();

     StateChangeForGameUI(GameUIState.waitForUserToStateKnowledge);

    }


    //---------------------------------------------------------------------

    //ОБРАБОТЧИК СОБЫТИЙ:

    //Пользователь желает ответить на отображенный вопрос и сообщить, какой

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

    //---------------------------------------------------------------------

    private void buttonShowAnswers_AdvancedVersion_Click(object sender, System.EventArgs e) {

     //Установить состояние игры для отображения вариантов выбора

     StateChangeForGameUI(GameUIState.waitForUserToAnswerMultipleChoice);

    }


    //---------------------------------------------------------------------

    //ОБРАБОТЧИК СОБЫТИЙ:

    //Пользователь желает ответить на отображенный вопрос и сообщить, какой

    //наиболее легкий уровень является для него приемлемым

    //---------------------------------------------------------------------

    private void buttonShowAnswers_SimpleVersion_Click(object sender, System.EventArgs e) {

     //Установить состояние игры для отображения вариантов выбора

     StateChangeForGameUI(GameUIState.waitForUserToAnswerMultipleChoice);

    }


    //ОБРАБОТЧИК СОБЫТИЙ: Был выполнен щелчок на кнопке выбора варианта ответа

    private void buttonAnswer0_Click(object sender, System.EventArgs e) {

     evaluateMultipleChoiceAnswer(buttonAnswer0, 0);

    }


    //ОБРАБОТЧИК СОБЫТИЙ: Был выполнен щелчок на кнопке выбора варианта ответа

    private void buttonAnswer1_Click(object sender, System.EventArgs e) {

     evaluateMultipleChoiceAnswer(buttonAnswer1, 1);

    }


    //ОБРАБОТЧИК СОБЫТИЙ: Был выполнен щелчок на кнопке выбора варианта ответа

    private void buttonAnswer2_Click(object sender, System.EventArgs e) {

     evaluateMultipleChoiceAnswer(buttonAnswer2, 2);

    }


    //ОБРАБОТЧИК СОБЫТИЙ: Был выполнен щелчок на кнопке выбора варианта ответа

    private void buttonAnswer3_Click(object sender, System.EventArgs e) {

     evaluateMultipleChoiceAnswer(buttonAnswer3, 3);

    }


    //ОБРАБОТЧИК СОБЫТИЙ: Был выполнен щелчок на кнопке выбора варианта ответа

    private void buttonAnswer4_Click(object sender, System.EventArgs e) {

     evaluateMultipleChoiceAnswer(buttonAnswer4, 4);

    }


    //ОБРАБОТЧИК СОБЫТИЙ: Был выполнен щелчок на кнопке выбора варианта ответа

    private void buttonAnswer5_Click(object sender, System.EventArgs e) {

     evaluateMultipleChoiceAnswer(buttonAnswer5, 5);

    }


    //-------------------------------------------------

    //ОБРАБОТЧИК СОБЫТИЙ: Вызывается при загрузке формы

    //-------------------------------------------------

    private void Form1_Load(object sender, System.EventArgs e) {

     //Задать статические свойства нашего визуального интерфейса

     SetStartControlPositionAndState();


     //Задать динамические свойства, исходя из того, в какое состояние

     //игры мы входим

     StateChangeForGameUI(GameUIState.startScreen);

    }

    Размещение элементов управления

    В случае мобильных приложений расположение элементов пользовательского интерфейса играет большую роль как в отношении удобства пользования ими, так и в отношении возможности представить полностью всю информацию, которая необходима. Лишь в редких случаях имеет смысл простой перенос пользовательского интерфейса с настольного компьютера на мобильное устройство; лично я не могу привести ни одного примера, когда бы это сработало на практике. Экранное разрешение и модели использования в этих двух случаях настолько радикально отличаются друг от друга, что попытки втиснуть пользовательский интерфейс и модель использования приложения, свойственные настольным компьютерам, в форм-фактор мобильного устройства — это рецепт неудачи. В лучшем случае вы получите неуклюжее посредственное приложение. Гораздо целесообразнее проанализировать, ранжировать по приоритетам и явно выписать перечень всех сценариев настольного компьютера, выполнение которых на мобильном устройстве является необходимым, а также сценариев, уникальных для мобильного устройства, которые вы хотели бы поддерживать, и лишь после этого создавать пользовательский интерфейс приложения, подчинив его вытекающим из всего вышесказанного требованиям.

    To же самое можно сказать и о простом переносе приложения с одного устройства на другое, если их форм-факторы различны; такой "буквальный" перенос пользовательского интерфейса имеет смысл осуществлять лишь в редких случаях. Даже если большая часть логики приложения и может быть перенесена в неизменном виде, гораздо лучше составить явный перечень ключевых сценариев и специфических для устройства моделей использования и начать создавать пользовательский интерфейс, который удовлетворяет этим запросам, с нуля.

    Экранное пространство — ценная вещь

    Чем меньше экран устройства, тем больше внимания необходимо уделять его эффективному использованию. Рациональное управление экранным пространством не имеет ничего общего с попытками разместить как можно больше элементов управления в пределах экрана; на самом деле почти всегда следует стремиться к обратному.

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

    ■ Каков тот минимальный набор информации, которая должна отображаться на экране, и тот минимальный набор элементов управления, которые требуются для навигации? Можно ли дополнительно уменьшить этот набор путем разделения доступного экрана между несколькими менее насыщенными состояниями пользовательского интерфейса, которые могут отображаться по отдельности?

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

    ■ Не будет ли эффективнее привлечь пользовательский элемент управления для отображения той информации, которая в настоящее время представляется несколькими элементами управления?  Такое решение должно быть тщательно взвешено. Создание специализированного элемента управления может оказаться неплохим способом повышения эффективности использования пространства пользовательского интерфейса, но при этом следует проанализировать, насколько такое решение будет удовлетворительным с учетом следующих факторов: 1) разработка пользовательского элемента управления требует намного больших усилий, чем использование уже существующих и протестированных элементов управления, и 2) создание новой метафоры пользовательского интерфейса может затруднить его использование. Конечные пользователи уже знают, как использовать существующие элементы управления, тогда как к любым вновь введенным вами концепциям им придется привыкать. Тем не менее, в некоторых случаях такая работа является оправданной и может давать впечатляющие результаты.

    Местоположение, местоположение и еще раз местоположение

    Не все участки экрана равнозначны. Элемент управления, размещенный вверху, внизу, слева, справа или посередине экрана будет обладать различными свойствами применимости. Эти свойства часто меняются при переходе от одного класса устройств к другому. Так, элементы управления Pocket PC, принимающие текстовый ввод, следует размещать в верхней части экрана, поскольку в этих устройствах предусмотрена всплывающая программная клавиатура (SIP), которая, если вам необходимо ввести текст, отображается в нижней части экрана. Расположением элементов управления, которые вы размещаете на экране, должны управлять следующие два фактора:

    1. Стиль и практика применения, существующие для вашего целевого мобильного устройства. Для большинства программируемых мобильных устройств существуют рекомендации, относящиеся к стилю компоновки пользовательского интерфейса. Эти рекомендации важно учитывать как для того, чтобы ваше приложение хорошо сочеталось с методами ввода, предусмотренными в устройстве (например, кнопки, сенсорный экран, всплывающая клавиатура), так и для того, чтобы все используемые на устройстве приложения имели унифицированный внешний вид и вели себя одинаковым образом с точки зрения пользователя. Если вы еще не успели прочитать эти рекомендации, обязательно сделайте это. Подобные рекомендации будут иметь для вас гораздо более высокую ценность, чем любой другой совет, касающийся общих вопросов компоновки интерфейсов.Примечание. Тот факт, что для каждого из различных классов устройств существуют свои рекомендации относительно стиля компоновки пользовательского интерфейса, является дополнительной причиной того, что модели, основанные на принципе "пишется однажды — выполняется везде", обычно не срабатывают. Все дело в том, что очень трудно создать единую унифицированную среду пользовательского интерфейса, которая бы автоматически и одинаково хорошо соответствовала стилевым рекомендациям для различных устройств.

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

    Выбирайте соответствующие элементы управления для соответствующих устройств

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

    Когда целесообразнее использовать различные формы, а когда — осуществлять смену элементов управления

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

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

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

    Если экран вашего целевого мобильного устройства достаточно большой, то в качестве промежуточного решения, позволяющего добиться определенного компромисса, могут использоваться вкладки. Обычно каждую отдельную вкладку следует концептуально рассматривать как отдельную форму. Использование вкладок обладает тем замечательным преимуществом, что при этом вы не должны отображать или скрывать каждый экран вручную; это автоматически делает за вас среда выполнения. Рассмотрите возможность помещения кода обработчиков событий для содержимого каждой из вкладок в отдельные классы. Тем самым будет обеспечена надежная инкапсуляция кода, делающая его менее запутанным.

    Разработка улучшенных пользовательских интерфейсов средствами .NET Compact Framework

    .NET Compact Framework предлагает некоторые усовершенствованные средства, представляющие интерес для разработчиков. Поскольку эти понятия специфичны для .NET Compact Framework, они сознательно вынесены в отдельный раздел. Аналогичные возможности могут предлагать и другие среды выполнения для мобильных устройств, поэтому данный материал стоит прочитать, даже в тех случаях, когда вы работаете в одной из них.

    Динамическое создание элементов управления

    Возможность динамического создания элементов управления может оказаться полезной. Как показывают рис. 13.8 и листинг 13.2, в .NET Compact Framework сделать это не сложно. Динамические элементы управления удобно использовать в нескольких ситуациях:

    ■ Когда форма загружается очень медленно.  Если форма содержит множество элементов управления или она достаточно тяжеловесна, то для ее инициализации требуется проделать большой объем работы. При создании и загрузке формы инициализируются все элементы управления, размещенные на ней во время проектирования. Все, что для этого необходимо, выполняется в функции InitializeComponent(); содержимым этой функции управляет конструктор форм, и при желании вы можете исследовать упомянутую функцию. Если вы хотите ускорить загрузку формы, то можете отложить создание элемента управления до тех пор, пока в нем не возникнет необходимости. Примечание. Вы также можете воспользоваться кодом, автоматически сгенерированным для вас в функции InitializeComponent(), и попытаться вручную оптимизировать его. Поступая таким образом, вы должны поместить собственный код в функцию с отдельным именем, которое исключит вероятность того, что по случайности она будет перекрыта функцией среды разработки. Вы также должны понимать, что теперь при проектировании своей формы вы можете лишиться помощи конструктора форм. Как и в случае любой оптимизации, вы обязательно должны получать количественную оценку ее эффективности, чтобы быть уверенным в том, что добились с ее помощью те результаты, на которые рассчитывали.

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

    ■ Когда для элемента управления, который вы хотите использовать, не существует реализации на стадии проектирования.  При работе с .NET Compact Framework создание экземпляров пользовательских элементов управления на стадии проектирования может представлять значительные трудности; на разработку этого часто может уходить больше времени, чем если воспользоваться для элементов управления их версиями времени выполнения. Если вы создаете пользовательский элемент управления для собственных нужд разработки, то может оказаться нецелесообразным затрачивать усилия на создание его версии, используемой только на этапе разработки, только ради того, чтобы она появилась в окне конструктора форм. Создавая данный элемент управления во время выполнения, вы избавляете себя от затраты этих усилий.

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

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

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

     а. Вам нужна функция, которая является приемником события (то есть вызывается при его запуске).

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

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

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

    newButton.Click += new System.EventHandler(this.ClickHandlerForButtons);

    Более подробно, по составляющим:

     а. this.ClickHandlerForButtons — это функция приемника событий.

     б. new System.EventHandler() — это делегат, указывающий на приемник событий.

     в. newButton.Click +=… — добавляет обработчик событий в список обработчиков событий, которые вызываются при запуске события.

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

    3. Задание, путем использования свойства Parent нового элемента управления, в качестве родительской той формы, на которой этот элемент должен отображаться. Фактически, в результате выполнения именно этого последнего шага элемент управления создается и размещается на форме. Если значением свойства Parent элемента управления является null, то элемент управления не принадлежит форме.

    Пример создания динамического элемента управления

    На рис. 13.8 и в листинге 13.2 представлен пример приложения, иллюстрирующего создание кнопок Button и подключение к ним обработчиков событий Click. Вы можете легко адаптировать пример для создания любых желаемых элементов управления. 

    Рис. 13.8. Динамическое создание элементов управления во время выполнения


    Представленный в листинге 13.2 код принадлежит форме в проекте Pocket PC. Для создания и запуска приложения потребуется выполнить следующие действия:

    1. Запустите Visual Studio .NET (2003 или более позднюю версию) и создайте приложение C# Smart Device Application.

    2. Выберите в качестве целевой платформы Pocket PC. (Для вас будет автоматически создан проект, и на экране появится окно конструктора форм Pocket PC.)

    3. Добавьте в форму кнопку Button и переименуйте ее в buttonCreateNewButtons.

    4. Дважды щелкните на кнопке, которую вы только что добавили в окне конструктора форм. На экране отобразится окно редактора кода вместе со скелетом функции privatevoidbuttonCreateNewButtons_Click(object sender, System.EventArgs e). Введите в эту функцию приведенный ниже код.

    5. Перейдя в окно редактора кода, введите оставшуюся часть приведенного ниже кода, включая те его части, которые расположены выше и ниже кода функции, который вы только что ввели.

    6. Установите для свойства MinimizeBox формы значение false. Благодаря этому во время выполнения в верхней правой части формы появится кнопка OK, с помощью которой вы легко сможете закрыть форму и выйти из приложения. Эта возможность оказывается очень полезной при многократном тестировании приложения.

    7. Запустите приложение. Вы должны заметить, что каждый раз, когда вы щелкаете на кнопке buttonCreateNewButtons, на форме появляется новая кнопка (как показано на рис. 13.8). Щелчки на любой из вновь созданных кнопок должны приводить к запуску приведенного ниже кода обработчика событий и отображению окна сообщений с текстом, соответствующим той кнопке, на которой был выполнен щелчок

    Листинг 13.2. Динамическое создание элементов управления на форме во время выполнения

    //-------------------------------------

    //Счетчик количества создаваемых кнопок

    //-------------------------------------

    private int m_nextNewButtonIndex;


    //---------------------------------------------------------------

    //ОБРАБОТЧИК СОБЫТИЙ: Обработчик щелчка на кнопке, которая

    // имеется на нашей форме.

    //

    //Эта функция создает новую кнопку, присоединяет ее к нашей форме

    //и подключает обработчик события щелчка для нее

    //---------------------------------------------------------------

    private void buttonCreateNewButtons_Click(object sender, System.EventArgs e) {

     //Впоследствии мы начнем создавать новые кнопки, начиная

     //снизу экрана, поэтому ограничиваем их количество восемью

     if (m_nextNewButtonIndex > 8) {

      return;

     }


     //----------------------------------------------------

     //Создать кнопку (еще не присоединенную к нашей форме)

     //установить ее местоположение, размеры и текст

     //----------------------------------------------------

     const int newButtonHeight = 15;

     System.Windows.Forms.Button newButton;

     newButton = new System.Windows.Forms.Button();

     newButton.Width = 100;

     newButton.Height = newButtonHeight;

     newButton.Left = 2;

     newButton.Top = (newButtonHeight + 2) * m_nextNewButtonIndex;

     newButton.Text = "New Button " + m_nextNewButtonIndex.ToString();


     //--------------------------------------------------

     //Присоединить обработчик к событию щелчка на данном

     //элементе управления.

     //--------------------------------------------------

     newButton.Click += new System.EventHandler(this.ClickHandlerForButtons);


     //-----------------------------------------

     //Присоединить эту кнопку к форме. По сути,

     //это создаст кнопку на форме!

     //-----------------------------------------

     newButton.Parent = this;


     //Увеличить счетчик в соответствии с созданием очередной кнопки

     m_nextNewButtonIndex++;

    }


    //-----------------------------------------------------

    //Обработчик событий, который мы динамически подключаем

    //к нашим новым кнопкам

    //-----------------------------------------------------

    private void ClickHandlerForButtons(object sender, System.EventArgs e) {

     Button buttonCausingEvent;

     buttonCausingEvent = (System.Windows.Forms.Button)sender;

     //Вызвать окно сообщений, извещающее о том,

     //что мы получили событие

     System.Windows.Forms.MessageBox.Show("Click event from: \n\r'" + buttonCausingEvent.Text + "'");

    }

    Создание пользовательских элементов управления и перекрытие поведения существующих элементов управления

    В .NET Compact Framework допускается два вида наследования элементов управления: 1) создание пользовательского элемента управления с нуля, и 2) перекрытие поведения существующих элементов управления System.Windows.Forms.*, не связанного с их перерисовкой/визуализацией.

    Прежде всего, следует сказать несколько слов о том, чего .NET Compact Framework (версия 1.1) не поддерживает: она, в отличие от .NET Framework, не позволяет разработчикам перекрывать визуальные характеристики перерисовки стандартных элементов управления. (Например, вы не можете использовать для наследования элементы управления Button, TreeView, TextBox или другие стандартные элементы управления, перекрывая при этом способ их перерисовки.) Это сделано в интересах функционирования внутренних механизмов.

    Разработчик, желающий придать нестандартный внешний вид элементу управления в .NET Compact Framework, должен породить его от базового класса Control (System.Windows.Forms.Control), который допускает пользовательскую визуализацию элементов управления. В наибольшей степени такая возможность полезна в случае тех элементов управления, которые предлагают совершенно новые возможности взаимодействия с пользователем, а не тех, которые обеспечивают видоизмененное поведение существующих элементов управления. Элементарный пример того, как создать с нуля элемент управления с нестандартной визуализацией, приводится в главе 11; этот пример послужит вам отличной отправной точкой для создания аналогичного собственного элемента управления, если в этом возникнет необходимость.

    Несмотря на то что .NET Compact Framework не поддерживает перекрытие поведения внутренних элементов управления, связанного с их визуализацией, она поддерживает перекрытие их функционального поведения. Существует два способа расширения возможностей внутренних элементов управления: 1) добавление дополнительных методов, свойств и событий, которые обеспечивают предоставление добавочной высокоуровневой функциональности, и 2) перекрытие существующих свойств и методов, для создания специфических возможностей. Хорошим примером, иллюстрирующим использованием обоих способов, является создание элемента TextBox, который осуществляет фильтрацию, принимая лишь входные данные определенного формата. Пример решения такой задачи представлен в листингах 13.3 и 13.4.

    Пример элемента управления TextBox с фильтром

    Если при вводе данных должны быть выдержаны определенные требования форматирования, то часто оказывается полезным создать пользовательский элемент управления, который вынуждает соблюдать необходимые критерии. В качестве типичного для США примера можно привести ввод номера карточки социального страхования. Эти номера имеют формат ###-##-####, представляющий три цифры, разделитель в виде дефиса, за которым следуют еще две цифры, дефис и последние четыре цифры. Существует много других случаев, когда навязывание формата ввода данных оказывается полезным, например, почтовые коды (ZIP-коды). В разных странах предусмотрены свои форматы кодов, одни из которых — цифровые, а другие — буквенно-цифровые. Так, в почтовые коды Канады и Великобритании входят как цифры, так и буквы. Во всех подобных случаях, когда требуется строго определенный ввод, возможность включения фильтра в элемент управления TextBox представляет большую ценность. Было бы очень кстати, если бы у этого элемента управления было свойство, позволяющее информировать пользователя о том, соответствует ли введенный в настоящее время текст требованиям определения достоверного и завершенного ввода. В нашем примере кода будет реализована как фильтрация входных данных, так и проверка их достоверности.

    На рис. 13.9 показано, как выглядит приложение во время выполнения. Имеющаяся на форме кнопка предназначена для создания экземпляра элемента управления TextBox с фильтром.

    Рис. 13.9. Текстовое окно с фильтрацией во время выполнения


    Ввод текста в текстовом окне приводит к запуску кода фильтрации и форматирования, предусмотренного для этих целей в классе SocialSecurityTextBox. В этом классе вызываются две функции:

    1. Первой из вызываемых функций является перекрытый метод SocialSecurityTextBox.OnKeyPress(). Это дает нам возможность перехватывать и осуществлять предварительную фильтрацию поступающих событий нажатий клавиш. В нашем случае, поскольку мы не хотим, чтобы среди входных данных присутствовали буквы, мы должны отфильтровывать соответствующие символы при их вводе пользователем. Благодаря тому, что мы не передаем их методу OnKeyPress() базового класса, текстовое окно вообще "не видит" нажатий этих клавиш. Мы могли бы использовать более строгие критерии фильтрации, исключив, например, любой возможный дополнительный цифровой ввод, который пользователь пытался бы выполнить после ввода обязательных цифр, однако не будем усложнять этим пример. Стоит подчеркнуть, что при фильтрации событий нажатий клавиш необходимо следить за тем, чтобы не переусердствовать и не потерять при этом нажатия таких клавиш, как символ забоя, который используется для удаления предыдущего символа.

    2. Второй из вызываемых функций является перекрытый метод SocialSecurity — TextBox.OnTextChanged(). Этот метод вызывается тогда, когда содержимое свойства Text претерпевает изменения, например, когда было зарегистрировано нажатие клавиши. При этом у нас появляется возможность применить наш форматирующий код и принудительно согласовать любой введенный текст с определенным нами форматом. Из введенных символов мы оставляем лишь цифры, одновременно гарантируя наличие символов дефиса (-) между третьей и четвертой, а также пятой и седьмой по счету цифрами. При этом следует предпринять некоторые меры предосторожности, ибо если будет происходить обновление свойства Text текстового окна внутри метода OnTextChanged, то это приведет к тому, что наш метод OnTextChanged будет вызываться рекурсивно. В данном случае нам не нужны осложнения, поэтому в самом начале функцию мы проверяем, не является ли данный вызов рекурсивным, и если это так, то осуществляется выход из функции без выполнения каких-либо действий. Далее мы проверяем длину обрабатываемого текста; если она составляет 11 символов, то номер карточки социального страхования получен полностью, иначе — нет. Для указания этого факта используется обновление внутреннего состояния. Наконец, мы вызываем метод OnTextChanged нашего базового класса текстового окна; в результате этого будут вызываться все обработчики событий, прослушивающие события TextChanged.

    Приведенный в листинге 13.3 код представляет собой независимый класс, и его можно ввести в том виде, как он есть. Код, приведенный в листинге 13.4, принадлежит форме проекта Pocket PC. Для создания и запуска приложения необходимо выполнить следующие действия:

    1. Запустите Visual Studio .NET (2003 или более позднюю версию) и выберите в качестве типа приложения C# Smart Device Application.

    2. Выберите в качестве целевой платформы Pocket PC. (Для вас будет автоматически создан проект, и на экране появится окно конструктора форм Pocket PC.)

    3. Добавьте в форму элемент управления Button. (Ему будет присвоено имя button1.)

    4. Добавьте в форму элемент управления Label. (Ему будет присвоено имя label1.)

    5. Добавьте в проект новый класс. Присвойте ему имя SocialSecurityTextBox, удалите весь предшествующий код, который отображается в окне текстового редактора для этого класса, и введите код, представленный в листинге 13.3.

    6. Вернитесь к форме Form1 в окне конструктора форм.

    7. Дважды щелкните на кнопке, которую вы добавили в окне конструктора форм. На экране отобразится окно редактора кода вместе со скелетом функции private void button1_Click(object sender, System.EventArgs е).Введите в эту функцию ее код, представленный в листинге 13.4.

    8. Перейдя в окно редактора кода, введите оставшуюся часть приведенного ниже кода, включая те его части, которые расположены выше и ниже кода функции, который вы только что ввели.

    9. Установите для свойства MinimizeBox формы значение false. Благодаря этому во время выполнения в верхней правой части формы появится кнопка OK, с помощью которой легко сможете закрыть форму и выйти из приложения. Эта возможность оказывается очень полезной при многократном тестировании приложения.

    10. Запустите приложение. Вы должны заметить, что после щелчка на кнопке button1 в верхней части формы появляется новое текстовое окно. Это текстовое окно разрешает вводить лишь цифры, форматируя их по шаблону ###-##-####. По мере ввода надпись на экране обновляется, сообщая о том, ввели ли вы требуемое количество цифр.

    Данный пример легко видоизменить для поддержки других форматов ввода. Кроме того, в него может быть добавлен код, поддерживающий пользовательские события; например, наш унаследованный элемент управления TextBox может возбуждать событие после ввода всех необходимых данных, согласующихся с шаблоном ввода.

    Листинг 13.3. Фильтрующее текстовое окно, принимающее текст в формате ###-##-####

    using System;

    //----------------------------------------------------------------------

    //Этот класс является элементом управления, производным от TextBox.

    //Он наследует все графические свойства TextBox, но добавляет фильтрацию

    //содержимого текстового окна, тем самым гарантируя,

    //что вводимый текст будет соответствовать следующему формату:

    //###-##-####.

    //Этот формат соответствует формату номеров карточек социального

    //страхования, которые используются в США.

    //----------------------------------------------------------------------

    public class  SocialSecurityTextBox: System.Windows.Forms.TextBox {

     private bool  m_inputIsFullValidEntry;

     //------------------------------------

     //Указывает, получен ли номер карточки

     //социального страхования полностью

     //------------------------------------

     public bool  IsFullValidInput {

      get {return m_inputIsFullValidEntry;}

     }

     //Объект StringBuilder, который мы будем часто использовать

     System.Text.StringBuilder m_sb;

     //Максимальная длина обрабатываемых строк

     const int  SSNumberLength = 11;

     //-----------

     //Конструктор

     //-----------

     public  SocialSecurityTextBox() {

      //Распределить память для нашего объекта StringBuilder и предоставить

      //место для нескольких дополнительных рабочих символов по умолчанию

      m_sb = new System.Text.StringBuilder(SSNumberLength + 5);

      m_inputIsFullValidEntry = false;

     }

     //---------------------------------------------------------------------

     //Форматировать поступающий текст с целью установления его соответствия

     //нужному формату:

     //

     // Формат номера карточки социального страхования : ###-##-####

     // символы: 01234567890

     //

     // [in] inString : Текст, который мы хотим форматировать

     // [in/out] selectionStart: Текущая точка вставки в тексте;

     // она будет смещаться в связи с удалением

     // и добавлением нами символов

     //----------------------------------------------------------------------

     private string formatText_NNN_NN_NNNN(string inString, ref int selectionStart) {

      const int  firstDashIndex = 3;

      const int secondDashIndex = 6;

      //Удалить старые данные и поместить входную строку

      //в объект StringBuilder, чтобы мы могли с ней работать.

      m_sb.Length = 0;

      m_sb.Append(inString);

      //------------------------------------------------------------

      //Просмотреть каждыйсимвол в строке, пока не будет

      //достигнута максимальная длина нашего форматированного текста

      //------------------------------------------------------------

      int  currentCharIndex;

      currentCharIndex = 0;

      while ((currentCharIndex < m_sb.Length) && (currentCharIndex < SSNumberLength)) {

       char  currentChar;

       currentChar = m_sb[currentCharIndex];

       if ((currentCharIndex == firstDashIndex) || (currentCharIndex == secondDashIndex))

       //-------------------------------

       //Данным символом должен быть "-"

       //-------------------------------

       {

        if (currentChar != '-') {

         //Вставить дефис

         m_sb.Insert(currentCharIndex, "-");

         //Если мы добавили символ перед точкой вставки,

         //она должна быть смещена вперед

         if (currentCharIndex <= selectionStart) {

          selectionStart++;

         }

        }

        //Этот символ годится, перейти к следующему символу

        currentCharIndex++;

       } else

       //-------------------------

       //Символ должен быть цифрой

       //-------------------------

       {

        if (System.Char.IsDigit(currentChar) == false) {

         //Удалить символ

         m_sb.Remove(currentCharIndex, 1);

         //Если мы добавили символ перед точкой вставки,

         //она должна быть смещена назад

         if (currentCharIndex < selectionStart) {

          selectionStart--;

         }

         //He увеличивать значение счетчика символов, ибо мы должны

         //просмотреть символ, занявший место того символа,

         //который мы удалили

        } else {

         //Символ является цифрой, все нормально.

         currentCharIndex++;

        }

       }

      }

      //Если превышена длина строки, усечь ее

      if (m_sb.Length > SSNumberLength) {

       m_sb.Length = SSNumberLength;

      }

      //Возвратить новую строку

      return m_sb.ToString();

     }


     bool m_in_OnChangeFunction;


     protected override void OnTextChanged(EventArgs e) {

      //------------------------------------------------------------------

      //Если мы изменим свойство .Text, то будет осуществлен повторный

      //вход в обработчик. В этом случае мы не хотим предпринимать никаких

      //действий и должны просто выйти из функции без передачи события

      //куда-то еще.

      //------------------------------------------------------------------

      if (m_in_OnChangeFunction == true) {

       return;

      }


      //Заметьте, что сейчас мы находимся в функции OnChanged,

      //поэтому мы можем обнаружить повторное вхождение (см. код выше)

      m_in_OnChangeFunction = true;


      //Получить текущее свойство .Text

      string oldText = this.Text;


      //Получить текущий индекс

      SelectionStart int selectionStart = this.SelectionStart;


      //Форматировать строку, чтобы она удовлетворяла нашим потребностям

      string newText = formatText_NNN_NN_NNNN(oldText, ref selectionStart);


      //Если текст отличается от исходного, обновить

      //свойство .Text

      if (System.String.Compare(oldText, newText) != 0) {

       //Это приведет к повторному вхождению

       this.Text = newText;

       //Обновить местоположение точки вставки

       this.SelectionStart = selectionStart;

      }


      //Мы принудительно обеспечили соответствие введенного текста

      //правильному формату, поэтому, если длина строки согласуется с длиной

      //номера карточки социального страхования, то мы знаем что она имеет

      //формат ###-##-####.

      if (this.Text.Length == SSNumberLength) {

       //Да, мы имеем полный номер карточки социального страхования

       m_inputIsFullValidEntry = true;

      } else {

       //Нет, мы пока не получили полный номер карточки социального страхования

       m_inputIsFullValidEntry = false;

      }

      //Вызвать наш базовый класс и сообщить всем объектам, которых это

      //может интересовать, что текст изменился

      base.OnTextChanged(e);


      //Заметьте, что сейчас мы покидаем наш код и хотим отключить

      //проверку повторных вхождений в него.

      m_in_OnChangeFunction = false;

     }


     protected override void OnKeyPress(System.Windows.Forms.KeyPressEventArgs e) {

      //Поскольку нам известно, что никакие буквы при вводе нам не нужны,

      //то просто игнорировать их, если они встречаются.

      char keyPressed = e.KeyChar;

      if (System.Char.IsLetter(keyPressed)) {

       //Сообщить системе о том, что событие обработано

       e.Handled =true;

       return;

      }


      //Обработать нажатие клавиши обычным способом

      base.OnKeyPress(e);

     } //Конец функции

    } //Конец класса

    Листинг 13.4 Код формы для создания пользовательского элемента управления TextBox

    //-----------------------------------------------------------------

    //Переменная для хранения нашего нового элемента управления TextBox

    //-----------------------------------------------------------------

    SocialSecurityTextBox m_filteredTextBox;


    //--------------------------------------------------------------

    //ОБРАБОТЧИК СОБЫТИЙ: Создать экземпляр нашего пользовательского

    // элемента управления и поместить его в форму

    //--------------------------------------------------------------

    private void  button1_Click(object sender, System.EventArgs e) {

     //Создать, позиционировать и разместить элемент управления

     m_filteredTextBox = new SocialSecurityTextBox();

     m_filteredTextBox.Bounds = new  System.Drawing.Rectangle(2, 2, 160, 20);


     //Подключить обработчик событий

     m_filteredTextBox.TextChanged += new  EventHandler(this.textBox_TextChanged);


     //Задать родительский объект

     m_filteredTextBox.Parent =this;


     //Выделить элемент управления

     m_filteredTextBox.Focus();


     //Сделать данную кнопку недоступной, чтобы поверх данного объекта

     //не был создан второй объект

     SocialSecurityTextBox button1.Enabled = false;

    }


    //----------------------------------------------------------------

    //ОБРАБОТЧИК СОБЫТИЙ: Этот обработчик подключается динамически при

    // создании элемента управления

    //----------------------------------------------------------------

    private void textBox_TextChanged(object sender, System.EventArgs e) {

     if  (m_filteredTextBox.IsFullValidInput == true) {

      label1.Text = "FULL SOCIAL SECURITY NUMBER!!!";

     } else {

      label1.Text = "Not full input yet...";

     }

    }

    Использование прозрачных областей растровых изображений

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

    Растровые изображения, или битовые образы, являются, по сути, двумерными массивами целых чисел, где каждое число представляет цвет пикселя в определенной точке. В силу этого растровые изображения по самой своей природе имеют прямоугольную форму. Представление не являющегося прямоугольным изображения на прямоугольной битовой карте можно обеспечить, объявляя один цвет фоновым и заполняя пиксели, лежащие за пределами интересующего нас непрямоугольного изображения, этим цветом. Работа с правильными прямоугольными массивами обладает целым рядом преимуществ, не самым последним из которых является легкость копирования части одного изображения на часть другого; в случае прямоугольных областей для этого требуются самые простые алгоритмы. В результате копирования прямоугольной части одного растрового изображения на другое происходит простая замена информации об изображении, относящейся к соответствующей прямоугольной области. Это означает, что прямоугольные битовые карты, содержащие непрямоугольные изображения с единственным цветом фона, копируются в место назначения в виде непрозрачных прямоугольников изображений переднего и заднего планов. Результат выглядит совсем не привлекательно. В качестве альтернативного решения можно исходить из фонового изображения и создать на нем рисунок всех непрямоугольных изображений, используя функции рисования (например, DrawLine(), FillCircle()) или устанавливая пиксельные данные по одному пикселю за один раз. Оба эти способа приводят к хорошим визуальным результатам, но работают медленно и требуют написания сложных функций. Что нам необходимо сделать — так это скопировать одно растровое изображения на другое, не копируя фоновый цвет.

    На рис. 13.10 представлено простое растровое изображение для игры; битовая карта является прямоугольной, но само изображение, которое мы хотим нарисовать — нет. Чтобы нарисовать это изображение без его прямоугольного фона, мы должны сообщить функции, осуществляющей копирование, о том, что копировать пиксели, цвет которых совпадает с установленным нами фоновым цветом, не следует. В .NET Compact Framework это достигается за счет использования класса ImageAttributes.

    Класс ImageAttributes имеет метод SetColorKey(), используя который ваш код может устанавливать цвет маскирования. Указанный класс может передаваться в качестве параметра в одну из перегруженных версий функции Graphics.DrawImage(); получение этого параметра поддерживается только одной из перегруженных версий этой функции.

    Функция DrawImage() получает в качестве параметров исходную битовую карту изображения, которое мы хотим копировать, размеры и местоположения областей, из которых и в которые должно осуществляться копирование, и объект ImageAttributes, указывающий, какая цветовая маска будет определять прозрачность в исходном изображении. Далее, на битовую карту назначения копируется информация обо всех пикселях исходного растрового изображения за исключением тех, цвет которых согласуется с цветом маски, определенным в объекте ImageAttributes.

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

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


    Если известно, что необходимость в рисунках возникнет вновь или будет возникать регулярно, то имеет смысл глобально кэшировать объект ImageAttributes, который может использоваться по всему приложению в вашем коде рисования изображений неправильной формы с прозрачным фоном; это избавит вас от необходимости многократно распределять память для объектов ImageAttributes, а затем освобождать ее, и избавит от накопления "мусора" в памяти вашего приложения. 

    Пример использования растровых изображений с областями прозрачности

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

    Рис. 13.11. Приложение, иллюстрирующее процесс рисования с использованием прозрачного фона


    Приложение, представленное на рис. 13.11, можно создать с помощью кода, приведенного в листинге 13.5. Для создания и запуска приложения потребуется выполнить перечисленные ниже действия:

    1. Запустите Visual Studio .NET (2003 или более позднюю версию) и выберите в качестве типа приложения C# Smart Device Application.

    2. Выберите в качестве целевой платформы Pocket PC. (Для вас будет автоматически создан проект, и на экране появится окно конструктора форм Pocket PC.)

    3. Добавьте в форму элемент управления Button (ему будет присвоено имя button1) и переименуйте его в buttonDrawBackground.

    4. Дважды щелкните на кнопке в окне конструктора форм и введите для нее код функции buttonDrawBackground_Click(), листинг которой приводится ниже.

    5. Добавьте в форму элемент управления Button и переименуйте его в buttonDrawForeground.

    6. Дважды щелкните на кнопке в окне конструктора форм и введите для нее код функции buttonDrawForeground_Click(), листинг которой приводится ниже.

    7. Вернитесь к форме Form1 в окне конструктора форм.

    8. Добавьте в форму элемент управления Button и переименуйте его в buttonDrawBackgroundPlusForeground.

    9. Дважды щелкните на кнопке в окне конструктора форм и введите для нее код функции buttonDrawBackgroundPlusForeground_Click(), листинг которой приводится ниже.

    10. Введите оставшуюся часть кода, приведенного в листинге ниже.

    11. Вернитесь в окно конструктора форм.

    12. Установите для свойства MinimizeBox формы значение false. Благодаря этому во время выполнения в верхней правой части формы появится кнопка OK, с помощью которой вы легко сможете закрыть форму и выйти из приложения. Эта возможность оказывается очень полезной при многократном тестировании приложения

    13. Полученные вами результаты должны воспроизводить те, которые показаны на рис. 13.11.

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

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

    Листинг 13.5. Код формы, демонстрирующий использование прозрачности

    //----------------------------------------------------------------

    //Размеры наших битовых образов и экранного изображения PictureBox

    //----------------------------------------------------------------

    const int bitmap_dx = 200;

    const int bitmap_dy = 100;


    //-------------------------------------------------

    //Создает и прорисовывает изображение заднего плана

    //-------------------------------------------------

    System.Drawing.Bitmap m_backgroundBitmap;

    void CreateBackground() {

     if  (m_backgroundBitmap == null) {

      m_backgroundBitmap =new Bitmap(bitmap_dx, bitmap_dy);

     }


     //Делаем битовую карту белой

     System.Drawing.Graphics gfx;

     gfx = System.Drawing.Graphics.FromImage(m_backgroundBitmap);

     gfx.Clear(System.Drawing.Color.White);


     //Рисуем текст черным

     System.Drawing.Brush myBrush;

     myBrush = new System.Drawing.SolidBrush(System.Drawing.Color.Black);

     for (int у = 0; у < bitmap_dy; у = у + 15) {

      gfx.DrawString("I am the BACKGROUND IMAGE...hello", this.Font, myBrush, 0, у);

     }


     //Очистить

     myBrush.Dispose();

     gfx.Dispose();

    }


    //-------------------------------------------------

    //Создает и прорисовывает изображение заднего плана

    //-------------------------------------------------

    System.Drawing.Bitmap m_foregroundBitmap;

    void CreateForeground() {

     if (m_foregroundBitmap == null) {

      m_foregroundBitmap = new Bitmap(bitmap_dx, bitmap_dy);

     }


     //Делаем всю битовую карту синей

     System.Drawing.Graphics gfx;

     gfx = System.Drawing.Graphics.FromImage(m_foregroundBitmap);

     gfx.Clear(System.Drawing.Color.Blue);


     //Рисуем несколько фигур желтым

     System.Drawing.Brush yellowBrush;

     yellowBrush = new System.Drawing.SolidBrush(System.Drawing.Color.Yellow);

     gfx.FillEllipse(yellowBrush, 130, 4, 40, 70);

     gfx.FillRectangle(yellowBrush, 5, 20, 110, 30);

     gfx.FillEllipse(yellowBrush, 60, 75, 130, 20);


     //Очистить

     yellowBrush.Dispose();

     gfx.Dispose();

    }


    //-----------------------------------------------------------------

    //Устанавливает размеры и местоположение PictureBox с левой стороны

    //-----------------------------------------------------------------

    private void SetPictureBoxDimensions() {

     pictureBox1.Width = bitmap_dx;

     pictureBox1.Height = bitmap_dy;

     pictureBox1.Left = 20;

    }


    //---------------------------------------------------------------------

    //ОБРАБОТЧИК СОБЫТИЙ: Отобразить изображение ЗАДНЕГО ПЛАНА в PictureBox

    //---------------------------------------------------------------------

    private void buttonDrawBackground_Click(object sender, System.EventArgs e) {

     SetPictureBoxDimensions();

     CreateBackground();

     pictureBox1.Image = m_backgroundBitmap;

    }


    //-----------------------------------------------------------------------

    //ОБРАБОТЧИК СОБЫТИЙ: Отобразить изображение ПЕРЕДНЕГО ПЛАНА в PictureBox

    //-----------------------------------------------------------------------

    private void buttonDrawForeground_Click(object sender, System.EventArgs e) {

     SetPictureBoxDimensions();

     CreateForeground();

     pictureBox1.Image = m_foregroundBitmap;

    }


    //-----------------------------------------------------------------------

    //ОБРАБОТЧИК СОБЫТИЙ: Наложить изображение ПЕРЕДНЕГО ПЛАНА на изображение

    // ЗАДНЕГО ПЛАНА. Использовать МАСКУ ПРОЗРАЧНОСТИ, чтобы желтый

    // цвет в изображении ПЕРЕДНЕГО ПЛАНА стал прозрачным и через

    // него можно было видеть содержимое изображения

    // ЗАДНЕГО ПЛАНА

    //-----------------------------------------------------------------------

    private void buttonDrawBackgroundPlusForeground_Click(object sender, System.EventArgs e) {

     SetPictureBoxDimensions();

     CreateForeground();

     CreateBackground();


     //Получить объект Graphics изображения ЗАДНЕГО ПЛАНА, поскольку

     //именно поверх него мы собираемся рисовать. System.Drawing.Graphics gfx;

     gfx = System.Drawing.Graphics.FromImage(m_backgroundBitmap);


     //-------------------------------------------------------

     //Создать класс ImageAttributes. Этот класс позволяет нам

     //задать прозрачный цвет на наших операций рисования

     //-------------------------------------------------------

     System.Drawing.Imaging.ImageAttributes trasparencyInfo = new System.Drawing.Imaging.ImageAttributes();


     //----------------------

     //Задать прозрачный цвет

     //----------------------

     trasparencyInfo.SetColorKey(System.Drawing.Color.Yellow, System.Drawing.Color.Yellow);


     //Задать прямоугольник рисунка

     System.Drawing.Rectangle rect = new System.Drawing.Rectangle(0, 0, m_backgroundBitmap.Width, m_backgroundBitmap.Height);


     //-----------------------------------------------------------------------

     //Нарисовать изображение ПЕРЕДНЕГО ПЛАНА поверх изображения ЗАДНЕГО ПЛАНА

     //и использовать прозрачный цвет в ImageAttributes для создания окна

     //прозрачности, через которое виден задний план

     //-----------------------------------------------------------------------

     gfx.DrawImage(m_foregroundBitmap, rect, 0, 0, m_foregroundBitmap.Width,

      m_foregroundBitmap.Height, System.Drawing.GraphicsUnit.Pixel, trasparencyInfo);


     //Очистить

     gfx.Dispose();


     //Показать результат в виде растрового изображения

     pictureBox1.Image = m_backgroundBitmap;

    }

    Встраивание изображений в виде ресурсов приложений

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

    Чтобы использовать двоичные ресурсы, встроенные в приложение, вам необходимо следующее:

    1. Изображения должны быть скомпилированы и включены в состав вашего приложения. Это можно указать во время проектирования в среде разработки.

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

    Далее мы остановимся на обоих этих моментах.

    Как встроить изображение в приложение

    В Visual Studio .NET процесс включения двоичных ресурсов в приложение осуществляется сравнительно несложно:

    1. Запустите Visual Studio .NET.

    2. Создайте проект C# Smart Device Application.

    3. Выберите в меню Project (Проект) команду Add Existing ltem (Добавить существующий элемент).

    4. В открывшемся диалоговом окне Add Existing Item измените установку фильтра Files of Type (Тип файлов) на Image Files (Файлы изображений).

    5. Найдите файл изображения, который вы хотите добавить, и выделите его. (Примечание. Я настоятельно рекомендую выбирать файлы размером менее 300 Кбайт, иначе вы будете создавать в своем приложении гигантские файлы изображений, которые по своим размерам, скорее всего, будут превышать само приложение; растровые изображения с чрезмерно большим разрешением могут приводить к нехватке памяти устройства при их загрузке во время выполнения.) В данном примере мы будем полагать, что загружается файл MyImage.PNG.

    6. Перейдя в окно Visual Studio .NET Solution Explorer, выделите только что добавленный файл изображения (MyImage.PNG), щелкните на нем правой кнопкой мыши и выберите в открывшемся контекстном меню пункт Properties (Свойства).

    7. В открывшемся диалоговом окне Properties найдите свойство Build Action (Действие при сборке). По умолчанию для него устанавливается значение Content (Содержимое); это означает, что файл изображения будет копироваться на устройство вместе с приложением при его развертывании.

    8. Измените значение свойства Build Action на Embedded Resource (Встроенный ресурс); это означает, что двоичное содержимое файла MyImage.PNG будет встраиваться в исполняемый образ приложения всякий раз, когда оно будет компилироваться.

    Имена встроенных ресурсов чувствительны к регистру

    Независимо от того, чувствителен ли к регистру используемый вами язык программирования (для C# регистр имеет значение, для VB.NET — нет), в именах встроенных ресурсов строчные и прописные буквы различаются. Тщательно следите за правильным использованием регистра букв в именах файлов, которые вы делаете встроенными ресурсами; при поиске потоков ресурсов во время выполнения ваше приложение будет использовать буквы в том же регистре, в котором вы их указали. Если потоки ресурсов не удается найти из-за несовпадения имен, то во время выполнения генерируется исключение. Ошибки подобного рода являются распространенными, и их поиск может заставить вас понервничать, если вы не знаете о том, что надо сразу же проверять, правильно ли указаны регистры букв.

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

    Приведенный в листинге 13.6 код демонстрирует, как загрузить растровое изображение из потока встроенного ресурса в ваше приложение. В соответствии с предыдущим изложением в качестве изображения для примера используется файл MyImage.PNG. Этот код следует поместить в форму с элементами управления Button и PictureBox. Как и в предыдущих примерах, для подключения события button1_click к кнопке button1 следует дважды щелкнуть на кнопке в окне конструктора форм, в результате чего будет автоматически сгенерирован костяк функции.

    Листинг 13.6. Код формы, демонстрирующий загрузку встроенных ресурсов

    System.Drawing.Bitmap m_myBitmapImage;


    //------------------------------------------------------------------

    //Загрузить изображение, которое хранится в виде встроенного ресурса

    //в нашей сборке

    //------------------------------------------------------------------

    public void LoadImageFromResource() {

     //Если изображение уже загружено,

     //то не имеет смысла делать это повторно.

     if (m_myBitmapImage !=null) {

      return;

     }


     //----------------------------------------------------

     //Получить ссылку на двоичную сборку нашего приложения

     //----------------------------------------------------

     System.Reflection.Assembly thisAssembly = System.Reflection.Assembly.GetExecutingAssembly();


     //-------------------

     //Получить имя сборки

     //-------------------

     System.Reflection.AssemblyName thisAssemblyName = thisAssembly.GetName();

     string assemblyName = thisAssemblyName.Name;


     //-----------------------------------------------------------------------

     //Извлечь поток изображения из нашей сборки и создать соответствующую

     //ему битовую карту в памяти.

     //ПРИМЕЧАНИЕ: Имя потока ресурса ResourceStream ЧУВСТВИТЕЛЬНО К РЕГИСТРУ,

     // поэтому имя изображения должно В ТОЧНОСТИ совпадать с именем

     // файла изображения, который вы добавили в проект

     //-----------------------------------------------------------------------

     m_myBitmapImage = new System.Drawing.Bitmap(thisAssembly.GetManifestResourceStream(assemblyName + ".MyImage.PNG"));

    }


    //-----------------------------------------------------------

    //Загрузить изображение и отобразить его в объекте PictureBox

    //-----------------------------------------------------------

    private void button1_Click(object sender, System.EventArgs e) {

     LoadImageFromResource();

     pictureBox1.Image = m_myBitmapImage;

    }

    Форматы хранения изображений и прозрачность растровых изображений

    При использовании изображений в приложении очень важно правильно выбрать для них формат хранения. Для крохотных битовых образов (например, 8×8 пикселей) применение сжатия смысла не имеет, поскольку они и так малы. В случае более крупных изображений можно сэкономить довольно много места по сравнению с несжатыми форматами (например, *.BMP), используя сжатые форматы с потерями (например, *.JPG) или без потерь (например, *.PNG). Несжатые фоновые изображения с размерами порядка размеров экрана могут значительно увеличить общий размер приложения. Особое внимание следует уделять выбору форматов хранения для изображений, в которых один цвет будет считаться прозрачным во время выполнения; в таких случаях необходимо использовать только форматы со сжатием без потерь. Сжатие с потерями часто позволяет сэкономить много места, но этого удается достигнуть лишь ценой ослабления контроля за каждым отдельным пикселем изображения; вы получаете лишь приближенное изображение. Растровые изображения с областями прозрачности требуют точного указания цвета каждого пикселя.

    Резюме 

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

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

    Огромное влияние на проект будут оказывать размеры экрана, являющегося целевым для вашего мобильного приложения. Для небольших экранов целесообразно использовать пользовательские интерфейсы, основанные на списках; в случае экранов большего размера, при работе с которыми применяются сенсорная площадка или мышь, лучшим решением для навигации между экранами будет метафора диалоговых окон-вкладок. Распределите функциональность своего мобильного приложения между несколькими специализированными экранами. Тщательно продумайте, какую информацию вы хотите отобразить на каждом из экранов и как облегчить навигацию между ними. Приложения для настольных компьютеров обычно отображают несколько абзацев информации одновременно. В приложениях для Pocket PC чаще встречается вариант отображения одного абзаца информации с одновременным отображением ссылок на другие абзацы. В смартфонах обычно отображаются сводные списки, с помощью которых пользователь может углубляться в структуру списков и просматривать каждую порцию информации по отдельности.

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

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

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

    .NET Compact Framework предлагает некоторые улучшенные возможности пользовательского интерфейса, которые могут пригодиться вам при разработке интерфейсов для многофункциональных мобильных приложений. .NET Compact Framework предоставляет разработчикам возможность создавать пользовательские элементы управления двух видов: 1) пользовательские элементы управления, которые являются производными от класса System.Windows.Forms.Control и реализуют собственные возможности визуализации с нуля, и 2) пользовательские элементы управления, которые реализуют соответствующие свойства поверх существующих элементов управления путем произведения этих свойств от таких не являющихся абстрактными элементов управления, как System.Windows.Forms.Control.TextBox, и расширения свойств последних. Полезны обе разновидности пользовательских элементов управления. Чрезвычайно полезна возможность динамического создания пользовательских элементов управления во время выполнения; создание версии элемента управления .NET Compact Framework во время проектирования может оказаться трудоемкой задачей, и если только создание пользовательских элементов не является одним из сегментов вашей деятельности на рынке, то лучше отказаться от этого шага и просто создавать экземпляры пользовательских элементов управления динамически во время выполнения.

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

    Ключом к созданию рисунков с использованием прозрачности при работе с .NET Compact Framework является использование класса System.Drawing.Imaging.ImageAttributes; этот класс позволяет вашему коду устанавливать ключевой цвет, который будет трактоваться как прозрачный при копировании исходного растрового изображения в изображение назначения. Области прозрачности могут оказаться полезными при работе со всеми разновидностями растровых изображений, от текста и фигур, динамически рисуемых на битовых картах, до заранее подготовленных изображений, загружаемых во время выполнения. Используемые растровые изображения могут храниться в виде части двоичного образа вашего приложения; разворачивать приложение значительно легче, если количество файлов, от которых оно зависит, невелико.

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







     


    Главная | В избранное | Наш E-MAIL | Добавить материал | Нашёл ошибку | Наверх