Текст
                    ПРОГРАММИРОВАНИЕ
КПК и СМАРТФОНОВ
на .NET Compact Framework
Переход
на .NET Compact Framework 2.0
Поэтапный процесс создания
приложения
 Особенности
программирования
для смартфонов
Создание игр
для мобильных устройств
ПИТЕР

Климов Александр Программирование КПК и смартфонов на .NET Compact Framework Заведующий редакцией Ведущий редактор Технический редактор Литературный редактор Корректоры Верстка А. Кривцов А. Юрченко С. Романов И. Шапошников Н. Рощина. А. Моносов С. Романов ББК 32.973.23-018 УДК 004.382.7 Климов А. П. К49 Программирование КПК и смартфонов на .NET Compact Framework. — СПб/ Питер, 2007. — 320 с.: ил. ISBN 978-5-91180-270-7 5-91180-270-8 Мобильные устройства все активнее вторгаются в нашу жизнь. Карманные компьютеры и смартфоны давно утратили статус дорогих игрушек и перекочевали в разряд необходимых устройств. Значит, для них необходимо создавать программное обеспечение. Так почему вы должны оставаться в стороне? Прочитав эту книгу, вы получите знания, которые позволят вам уверенно создавать программы для карманных компьютеров и смартфонов. А огромное коли- чество разнообразных примеров поможет быстро совершенствоваться и развивать- ся, обогащая свой опыт работы. Книга предназначена для начинающих программистов. © ООО «Питер Пресс», 2007 Все права защищены. Никакая часть данной книги не может быть воспроизведена в какой бы то ни было форме без письменного разрешения владельцев авторских прав. Информация, содержащаяся в данной книге получена из источников, рассматриваемых издательством как надежные. Тем не менее, имея в виду возможные человеческие или технические ошибки, издательство не может гарантировать абсолютную точность и полноту приводимых сведений и не несет ответственности за возможные ошибки, связанные с использованием книги. ISBN 978-5-91180-270-7 ООО «Питер Пресс», 198206, Санкт-Петербург, Петергофское шоссе, д. 73, лит. А29. Налоговая льгота — общероссийский классификатор продукции ОК 005-93, том 2; 95 3005 — литература учебная Подписано в печать 13.10.06. Формат 60*90/16. Усл. п л. 20. Тираж 2500 экз. Заказ № 2873. Отпечатано с готовых диапозитивов в ООО «Типография Правда 1906». 195299, Санкт-Петербург, Киришская ул., 2 Тел.: (812) 531-20-00, (812) 531-25-55.
Краткое содержание Введение........................................11 Глава 1. Знакомство с .NET Compact Framework....14 Глава 2. Первое приложение для .NET Compact Framework .. 19 Глава 3. Элементы управления...................27 Глава 4. Улучшаем элементы управления..........72 Глава 5. Мышь и клавиатура.....................83 Глава 6. Графика...............................89 Глава 7. Разработка приложений............... 115 Глава 8. Эмулятор и другие утилиты........... 154 Глава 9. Программирование для смартфонов..... 166 Глава 10. Windows Mobile 5.0 ................ 177 Глава 11. Создание игр....................... 210 Глава 12. Связь.............................. 261 Глава 13. Использование неуправляемого кода...272 Глава 14. Кирпичики .NET Compact Framework....314 Послесловие...................................318
Содержание Введение................................................11 На кого рассчитана эта книга............................11 Требования...............................................И Примеры к книге.........................................12 Благодарности...........................................12 Отзывы и предложения....................................13 От издательства.........................................13 Глава 1. Знакомство с .NET Compact Framework............14 Мобильные устройства....................................14 Общие сведения..........................................15 Развитие .NET Compact Framework.........................15 Что нового в .NET Compact Framework 2.0.................16 Что нового в Visual Studio NET 2005.....................18 Глава 2. Первое приложение для .NET Compact Framework.... 19 Первые шаги.............................................19 Кнопки минимизации и закрытия формы.....................23 Размеры и позиция формы.................................23 Меню....................................................24 Панель ввода SIP........................................25 Стилус вместо мыши......................................26 Глава 3. Элементы управления............................27 Сходство и различия.....................................27 Урезанная функциональность элементов управления..:......30 Элемент Form............................................30 Свойство FormBorderStyle .............................30 Свойство ControlBox ..................................31 Свойства MinimizeBox и MaximizeBox....................31 Свойство WindowsState.................................31 Размеры и расположение формы..........................31 Элементы управления.....................................32 Элемент Button........................................32 Элемент TextBox.......................................33 Элемент Label.........................................34 Элемент RadioButton...................................34
Содержание 5 Элемент Panel..............................................36 Элемент CheckBox...........................................36 Элемент ComboBox...........................................37 Элемент ListBox............................................38 Элемент NumericUpDown......................................39 Элемент DomainUpDown.................................... 41 Элемент ProgressBar........................................42 Элемент StatusBar..........................................43 Элемент ТгаскВаг...........................................44 Элемент Tool Ваг...........................................45 Элемент MainMenu...........................................47 Элемент ContextMenu........................................48 Элемент Timer..............................................48 Элементы OpenFileDialog и SaveFileDialog...................49 Элементы Н Scroll Ваг и VScrollBar.........................50 Список рисунков (ImageList)................................51 Элемент PictureBox.........................................52 Элемент ListView...........................................53 Элемент TabControl.........................................55 Элемент TreeView...........................................57 Элемент InputPanel.........................................58 Элемент управления DataGnd.................................61 Элемент Splitter...........................................63 Элемент MonthCalendar......................................64 Элемент DateTimePicker.....................................64 Элемент DocumentList...................................... 65 Элемент Notification.......................................68 Элемент HardwareButton.....................................69 Глава 4. Улучшаем элементы управления........................72 Текстовые поля...............................................72 Управление полосой прокрутки ................................73 Многострочный текст в кнопке.................................74 Увеличение ширины выпадающего списка ComboBox................77 ListBox......................................................79 ListView.....................................................80 Создание кнопки, содержащей изображение......................81 Список с расширенными возможностями..........................81 Текстовое поле для ввода чисел...............................81 Сортировка элементов ListView................................82 Использование элемента DateTimePicker........................82 Глава 5. Мышь и клавиатура...................................83 Мышь и стилус................................................83 Курсоры......................................................83
6 Содержание Песочные часы...............................................83 Обработка события Tap-and-Hold..............................84 Клавиатура..................................................85 Клавиши навигации...........................................86 Выключение устройства.......................................87 Дополнительные материалы....................................88 Глава 6. Графика............................................89 Классы для программирования графики.........................89 Класс Реп...................................................89 Класс Brush.................................................90 Класс Solid Brush...........................................90 Класс TextureBrush..........................................90 Класс Color.................................................91 Класс Font..................................................92 Класс Icon..................................................92 Класс Bitmap................................................92 Структура Point.............................................93 Структура Rectangle.....................................;...93 Графические методы..........................................93 Создание собственных методов DrawPie и FillPie..............95 Создание фонового рисунка для формы.........................99 Копирование рисунка........................................100 Поддержка прозрачности.....................................102 Округленные прямоугольники.................................105 Создание экранных снимков..................................107 Метод Lockbits.............................................110 Графический редактор.......................................111 Дополнительные материалы...................................114 Глава 7. Разработка приложений............................ 115 Активация и деактивация формы..............................115 Закрыть или свернуть окно...............................116 Пиктограмма приложения ....................................117 Создание собственных диалоговых окон.......................118 Создание заставки Splash Screen............................120 Поворот экрана.............................................122 Рекомендации по дизайну форм...............................123 Готовые приложения........................................123 Файловый менеджер для смартфона............................124 Зачем нужен файловый менеджер............................124 Графический интерфейс программы..........................125 Код программы............................................126 Диспетчер задач.......................................... 135 Графический интерфейс программы..........................135
Содержание 7 Код программы..........................................136 Активация и закрытие приложения........................138 Перечисление процессов.................................139 Закрытие процесса......................................141 Маленький блокнот........................................141 Первые шаги............................................142 Стандартные операции с текстом.........................143 Распространение приложений...............................148 Создание саЬ-файла.....................................149 Создание проекта.......................................150 Дополнительные материалы.................................153 Глава 8. Эмулятор и другие утилиты...................... 154 Программы для отладки приложений.........................154 Эмулятор.................................................154 Запуск эмулятора.........................................155 Настройка эмулятора......................................156 Эмуляция карточки памяти.................................158 Изменение ориентации экрана..............................160 Выход в Интернет через эмулятор..........................161 Изменение внешнего вида эмуляторов.......................163 Эмулятор как отдельный продукт...........................163 Новая версия эмулятора...................................163 Набор утилит Visual Studio Remote Tools..................164 Remote Zoom-in............................................ 164 Remote File Viewer.......................................165 Remote Registry Editor...................................165 Глава 9. Программирование для смартфонов................ 166 Особенности программирования для смартфонов..............166 Создание приложения для смартфона........................167 Создание меню............................................168 Элементы управления......................................169 Режимы ввода.............................................172 Переопределение клавиш Soft Key..........................174 Прокручивание формы......................................175 Глава 10. Windows Mobile 5.0 ........................... 177 Первый взгляд............................................177 Улучшенная продуктивность..............................177 Поддержка мультимедиа..................................178 Поддержка управляемого кода............................178 Windows Mobile 5.0 API.................................178 Взаимодействие с ActiveSync............................179 Новые возможности системы..............................180
8 Содержание Подготовка к работе.........................................180 Microsoft. WindowsMobile.PocketOutlook......................182 Встречи (Appointment).......................................182 Работа с адресной книгой....................................185 Электронная почта...........................................188 SMS-сообщения...............................................189 Прием и обработка SMS-сообщений.............................191 Телефония...................................................194 State and Notifications Broker..............................195 Мультимедиа.................................................200 Выбор изображения...........................................200 Работа с фотокамерой........................................202 Повторение пройденного......................................204 Встречи.....................................................204 Отсылка письма..............................................206 Мелочь, а приятно...........................................209 Метод Directory.Exists....................................209 Метод Bitmap.Save().......................................209 Глава И. Создание игр.......................................210 Игры на мобильных устройствах...............................210 Продуктовая аркада..........................................211 Начало работы.............................................212 Добавление изображения в программу........................212 Использование встроенных ресурсов.........................213 Вывод картинки на экран...................................213 Создание анимации.........................................214 Отражения.................................................216 Управление скоростью движения объекта.....................218 Добавляем новый объект....................................220 Устранение мерцания.......................................221 Хлеб — всему голова.......................................223 Обнаружение столкновений..................................225 Столкновения батона и мяча................................228 Новые объекты.............................................230 Размещение помидоров......................................231 Уничтожение томатов.......................................234 Счет игры.................................................235 Ведение счета.............................................236 Звуковые эффекты..........................................237 Дальнейшие улучшения......................................241 Тестирование..............................................245 И опять добавляем новые объекты...........................246 Управление таблицей результатов...........................250 Переключение между формами................................251
Содержание 9 Отображение дочернего окна..............................252 Получение имени игрока..................................253 Хранение лучших результатов.............................254 Улучшение графики........................................ 256 Спрайты................................................ 259 Другие игры............................................. 260 Глава 12, Связь...........................................261 Инфракрасное соединение...................................261 История и теория........................................261 Класс IrDAChent...........................................262 Создание программы для работы с ИК-связью.................262 Обнаружение устройств...................................264 Передача данных.........................................265 Технология Bluetooth......................................269 Несколько слов о связи....................................271 Глава 13. Использование неуправляемого кода.............. 272 Вызов функций Windows API.................................272 Определение платформы.....................................273 Пароли....................................................275 Перезагрузка КПК..........................................277 Еще раз о перезагрузке....................................278 Поворот экрана............................................278 Прячем кнопку Start.......................................281 Панель задач..............................................283 Запуск других приложений..................................284 Названия специальных файлов...............................286 Использование звуковых файлов.............................288 Системные звуки.........................................293 Системное время...........................................293 Создание ярлыка.......................................... 294 Количество ст рок в текстовом поле........................295 Реестр....................................................296 Наличие внешней клавиатуры..............................307 Информация о пользователе...............................309 Наличие дополнительной клавиатуры.........................310 Виброзвонок...............................................311 Глава 14. Кирпичики .NET Compact Framework............... 314 Узнать версию .NET Compact Framework......................314 Узнать версию операционной системы........................314 Получить путь к запущенному приложению....................315 Специальные папки.........................................315 Узнать имя устройства.....................................315
10 Содержание Узнать ориентацию экрана...................................316 Открытие файлов программой по умолчанию....................316 Создание и отправка письма.................................316 Кнопки навигации...........................................317 Послесловие................................................318 Что дальше?................................................318 Полезные ресурсы...........................................319 .NET Compact Framework 2.0 Redistributable...............319 Microsoft ActiveSync 4.1.................................319 Русская версия эмулятора для Windows Mobile 5.0 Smartphone.319 Русская версия эмулятора для КПК под управлением Windows Mobile 5.0 Pocket PC.............319 Сайт Роба Майлза.........................................320 Сайт Кристиана Форсберга.................................320 OpenNETCF.org............................................320 Google...................................................320
Введение Сейчас очень трудно найти книги, в которых описываются пробле- мы программирования для карманных компьютеров и смартфонов при помощи технологии .NET Compact Framework. Одним из глав- ных источников получения информации является сама документа- ция, входящая в состав Visual Studio 2005. Но одной документации явно недостаточно. Именно поэтому и появилась на свет книга, ко- торую вы держите в руках. Ни в коем случае не стоит рассматривать данное произведение как полный и исчерпывающий учебник, в котором можно найти отве- ты на все вопросы. В книге объясняются базовые принципы про- граммирования для компактных устройств. И автор надеется, что книга послужит стимулом для дальнейшей работы в этой области. На кого рассчитана эта книга Книга рассчитана в первую очередь на программистов, уже имею- щих опыт программирования на NET Framework, которые хотят расширить свой кругозор за счет освоения .NET Compact Framework. После освоения новой технологии у разработчика по- явится возможность перенести некоторые программы с обычных компьютеров на другую платформу, тем самым увеличивая число своих потенциальных клиентов. Требования Чтобы работать с примерами из этой книги, необходимо иметь на компьютере пакет программ Visual Studio 2005. Обратите внимание на то, что некоторые облегченные версии Visual Studio 2005 (в част- ности, Express-версии) не поддерживают разработки программ для мобильных компьютеров. Часть примеров можно использовать и в ста-
12 Введение рой версии Visual Studio 2003, однако в этом случае разработчику придется вручную переписывать код из-за несовместимости фор- матов разных версий Visual Studio. Впрочем, настоящего програм- миста этим не испугаешь! Примеры к книге Практически любая книга по программированию снабжается сопут- ствующими примерами. Не стала исключением и книга, которую вы держите в руках. Каждая глава сопровождается массой приме- ров, а найти их код можно на сайте издательства «Питер». В качестве основного языка программирования был выбран язык С#, но в третьей главе были также добавлены примеры программи- рования на языке Visual Basic.NET. Если среди читателей книги найдется много приверженцев этого языка, то на сайте автора бу- дут выложены все необходимые примеры, написанные на Visual Basic.NET. Благодарности Любую работу в наше время тяжело делать в одиночку. Хорошо, когда находятся люди, готовые поддержать инициативу. Поэтому хотелось бы назвать тех, без которых эта книга могла не появиться на свет. В первую очередь надо поблагодарить издательство «Питер» (www.piter.com), которое согласилось выпустить данную книгу. Хочу также выразить благодарность тем людям, которые любезно разрешили использовать исходные коды своих чудесных программ в качестве учебных примеров. Это Кристиан Форсберг (Christian Forsberg), Алекс Яхнин (Alex Yakhnin) и Роб Майлз (Rob Miles). Без примеров этих авторов книга получилась бы скучной и неинте- ресной. Так получилось, что во время написания книги мне пришлось по- менять работу. В этом процессе участвовали несколько человек, которые способствовали переходу. Поэтому хочется поблагодарить и этих людей, благодаря которым у меня появились дополнитель- ные возможности для работы над книгой. В первую очередь хоте- лось бы отметить генерального директора гостиничного комплекса «Вега» Воробьева Алексея Петровича, главного инженера Миклу-
От издательства 13 шова Владимира Павловича и начальницу АХО Егоркину Галину Владимировну. Хочу поблагодарить также всех сотрудников отде- ла информационных технологий, возглавляемого Рогулиным Вик- тором Васильевичем, которые делились своими знаниями на но- вой работе: Храмцову В., Пузикову Н. (особенно), Фетисову Е., Шумова Е., Князева Л., Алдохина А., Дробота В., Терехова А., Не- чеухина Н. Отзывы и предложения Все свои отзывы и критические замечания вы можете посылать на электронный адрес автора rusproject@maiLru. Также стоит почаще за- глядывать на сайт http://rusproject.narod.ru, на котором я постараюсь размещать новые дополнительные материалы по тематике книги. От издательства Ваши замечания, предложения и вопросы отправляйте по адресу электронной почты comp@piter.com (издательство «Питер», ком- пьютерная редакция). Мы будем рады узнать ваше мнение! Все исходные тексты, приведенные в книге, вы можете найти по адресу http://www.piter.com/download. Подробную информацию о наших книгах вы найдете на веб-сайте издательства: http://www.piter.com.
Глава 1 Знакомство с .NET Compact Framework Мобильные устройства Мобильные устройства все активнее вторгаются в нашу жизнь. Все чаще можно встретить в метро молодых людей, увлеченно работа- ющих с карманным компьютером. Я сам несколько раз был свиде- телем того, что обладателями КПК были девушки. Это говорит о том, что данные устройства уже утратили статус дорогой игруш- ки технократов и рассматриваются как необходимое устройство, которое вскоре будет таким же доступным, как обычный сотовый телефон. В последнее время на рынок активно выходят смартфоны под уп- равлением операционной системы Windows Mobile 5.0. Пока в этом сегменте рынка прочные позиции удерживают смартфоны под уп- равлением Symbian, производимые фирмами Nokia и Sony Ericsson. Но умение Microsoft завоевывать себе место под солнцем давно ста- ло общеизвестным. Достаточно вспомнить противоборство браузе- ров Netscape и Internet Explorer, а также КПК Palm и PocketPC. Эта тенденция позволяет считать, что и «умные» телефоны под уп- равлением Windows Mobile скоро потеснят своих конкурентов. И в этой ситуации очень ярко проявляется преимущество изуче- ния .NET Compact Framework. Если вы знакомы с программирова- нием для .NET Framework, то вам не составит труда перейти к ос- воению особенностей программирования для КПК и мобильных телефонов под управлением Windows Mobile. Ведь писать програм- мы придется в уже знакомой вам среде Visual Studio .NET. Более того, вам даже не обязательно иметь сам карманный компьютер или смартфон для проверки написанного кода, так как в Visual Studio .NET уже имеются эмуляторы для этих мобильных устройств. С помощью этой книги читатель сможет научиться самостоятель- но писать программы для мобильных устройств. Это позволит рас- ширить круг своих знаний, а также улучшить сбыт программ, если вы занимаетесь программированием коммерческих приложений.
Развитие .NET Compact Framework 15 В данной книге все примеры написаны на новых языках семейства .NET, таких как C# и Visual Basic .NET. Общие сведения Главная страница, посвященная NET Compact Framework, нахо- дится по адресу http://msdn.microsoft.com/netframework/programming/ netcf/default.aspx. Там можно найти все последние новости о рас- сматриваемой технологии, обновления программ, ссылки на дру- гие полезные сайты, примеры. Технология .NET Compact Framework поддерживается операцион- ными системами Pocket PC 2000, Pocket PC 2002, Windows Mobile 2003, Windows Mobile 2005 и Windows CE .NET 4.1. Конечно, технология .NET Compact Framework несколько отлича- ется от .NET Framework. Подробную информацию о различиях между этими технологиями можно найти на странице по адресу msdn.microsoft.com/library/default.asp7url-/library/en-us/dv_evtuv/ html/etconComparisonsWithNETFramework.asp. Считается, что .NET Compact Framework является частью полной библиотеки .NET Framework. Действительно, между двумя этими платформами очень много общего. Но все же говорить о .NET Compact Framework как о подмножестве полной .NET Framework не совсем корректно. Дело в том, что .NET Compact Framework поддерживает серию классов, которых нет в полной библиотеке классов. Эти классы созданы спе- циально для мобильных устройств и позволяют поддерживать, на- пример, программную клавиатуру, возможности инфракрасной свя- зи и отправки SMS. Библиотека .NET Compact Framework действительно компактна. Вместо 20 Мбайт полного пакета .NET Framework, устанавливаемо- го на настольные компьютеры, она занимает около 2 Мбайт. Полная версия .NET Framework содержит 18 700 классов и 80 000 методов, a .NET Compact Framework — всего лишь 4 700 классов и 13 000 ме- тодов. Но следует помнить, что это лишь приблизительная оценка. Развитие .NET Compact Framework Поначалу .NET Compact Framework устанавливалась в карманные компьютеры Pocket PC отдельно. Это порождало определенные проблемы для разработчиков. Не каждый пользователь хотел уста- навливать пакет .NET Compact Framework, необходимый для рабо-
16 Глава 1. Знакомство с .NET Compact Framework ты программы, когда оперативной памяти и так не хватает. Впер- вые библиотека .NET Compact Framework стала встраиваться в пор- тативные устройства под управлением Windows Mobile 2003 (Pocket PC 2003). На борту компьютеров под управлением Windows Mobile Second Edition уже находился пакет .NET Compact Framework 1.0 SP2). По уверениям Microsoft, работа с ресурсами стал быстрее на 600%, работа с XML с помощью класса XMLTextReader стала быстрее на 40%, а работа с ADO.NET — на 20%. Естественно, с выходом библиотеки .NET Compact Framework 2.0 создатели сно- ва стали говорить о повышении быстродействия и надежности. Но следует учитывать, что устройства с предустановленной библиоте- кой .NET Compact Framework 2.0 еще не выпускаются, и пользова- тель должен сам установить необходимый пакет Возможно, когда книга выйдет из печати, в мире уже появятся устройства встроен- ной версией .NET Compact Framework 2.0. , Что нового в .NET Compact Framework 2.0 Список основных изменений в .NET Compact Framework 2.0 при- веден на странице msdn.microsoft.com/netframework/programming/ netcf/default.aspx?puU=/library/en-us/dnnetcomp/html/whats_ new_netcf2.asp. К основным улучшениям библиотеки .NET Compact Framework 2.0 относятся усовершенствованные возможности созда- ния пользовательского интерфейса, новая мобильная база данных, существенные усовершенствования эмулятора, усиленная поддерж- ка COM Interop и Managed Interfaces для D3D. Библиотека .NET Compact Framework 2.0 расширила существующую функциональ- ность в .NET CF 1.0 новыми возможностями. Также разработчики добавили поддержку новых классов, которые ранее были доступны только в полной .NET Framework. В этой книге обязательно будут рассмотрены наиболее значительные новинки. А сейчас можно лишь упомянуть основные моменты. Пакет .NET Compact Framework 2.0 в Visual Studio 2005 стал поддер- живать новые элементы управления, позволяющие создавать очень сложные приложения без написания громоздкого кода. В частности, появилась поддержка элементов управления MonthCalendar и DateTime Picker, позволяющих создавать интерфейс календаря. Также появил- ся новый элемент для уведомлений Notification. Кроме него разра- ботчики получили доступ к элементам Document Li st и НаrdwareButton. Более подробно они будут рассматриваться в главе, посвященной эле- ментам управления. Новое свойство ScreenOrientation позволяет без
Что нового в .NET Compact Framework 2.0 17 использования неуправляемого кода вращать экран устройства. Кро- ме того, стало значительно проще создавать собственные элементы управления, как это делается в полной версии .NET Framework. Элементы пользовательского интерфейса в .NET Compact Frame- work 2.0 теперь поддерживают присоединение (docking). При пе- ремещении элемента управления к определенной стороне контей- нера он всегда заполняет эту сторону контейнера. Некоторые элементы управления стали поддерживать свойство AutoSeal eMode. Свойство AutoScal eMode показывает, как нужно пере- рисовывать элемент при изменении разрешения экрана. Также фор- мы стали поддерживать свойство AutoScroll. Помимо этого класс Control теперь поддерживает методы SuspendLayout и ResumeLayout. Также в .NET Compact Framework появилась полно- ценная поддержка буфера обмена. Класс Graphics тоже получил новые возможности, и теперь при по- мощи свойств DpiX и Dpi Y разработчик может узнать размеры экра- на. Помимо этого сейчас можно отображать текст под различными углами при помощи класса LogFont. Также разработчик может созда- вать перья заданных цвета и размера. Значительно улучшена работа с растровыми изображениями. Про- граммисты получили новые возможности для создания изображе- ний и сохранения их в файле или потоке. В приложениях стало проще манипулировать изображениями при помощи комбинации методов LockBits и UnlockBits в сочетании с новым классом BitmapData. Приложения, использующие при работе с изображе- ниями неуправляемый код, теперь могут получать дескриптор объекта Bitmap через метод GetHbitmap. Новые возможности позволяют разрабатывать приложения с исполь- зованием управляемого кода для Windows Mobile 5.0 при помощи классов из пространства имен Microsoft.Windows.DirectX. Поддержка DirectX позволяет писать игры с использованием управляемого кода, обеспечивая более быструю разработку приложений, чем при исполь- зовании неуправляемого DirectX. Так как на рынке все чаще стали появляться устройства со встро- енной клавиатурой, то в библиотеку .NET Compact Framework 2.0 была введена поддержка клавиатуры. Также следует отметить, что элементы управления теперь распоз- нают события KeyUp, KeyDown и KeyPress. Объект Form теперь имеет свойство KeyPreview.
18 Глава 1. Знакомство с .NET Compact Framework Тестировать программы тоже стало намного проще, так как эмуля- тор в .NET Compact Framework 2.0 подвергся значительной пере- работке. Перечень функциональных возможностей эмуляторов Pocket PC и смартфонов был расширен, что облегчает создание, проверку и развертывание приложений. Особое внимание было уделено поддержке сетевого взаимодействия. Кроме того, было улучшено быстродействие эмулятора. Появилась поддержка про- граммы ActiveSync, можно работать с общими папками и исполь- зовать четыре COM-порта. Также эмулятор стал поддерживать ра- боту с портретным и альбомным режимами отображения. Кроме того, эмулятор теперь эмулирует работу ARM-процессора. Технология Smart Device CAB Project упростила развертывание мобильных приложений. Эта технология позволяет использовать в визуальном редакторе перемещение файлов, создавать папки и записи в реестре. Теперь создавать САВ-проект для установочно- го пакета так же просто, как при создании проекта Windows Installer для настольных компьютеров. Новый компилятор теперь создает еще лучший и более быстрый код. Если в предыдущей версии использовались два JIT-компиля- тора, то теперь .NET CF 2.0 применяет единый компилятор для всех поддерживаемых процессоров. В области безопасности добавлена поддержка идентификации NTLM и Kerberos. Также улучшена работа с технологией XML, и в распоря- жение программистов поступил новый класс Xml Seri al izat ion. Намного удобнее стало разрабатывать дизайн форм в среде разра- ботки Visual Studio .NET 2005. Процесс создания программы стал еще более наглядным. Программист может создавать собственные элементы управления так же, как и для обычных приложений. Что нового в Visual Studio .NET 2005 Если у вас уже был опыт программирования под Visual Studio .NET 2003, то вы заметите, что на панели инструментов появились новые элементы управления. Они будут рассматриваться в главе, посвящен- ной элементам управления. При разработке дизайна приложения бу- дет заметно, как Windows Forms Designer помогает выравнивать эле- менты и предлагает выбрать минимальное расстояние между элементами. Автоматически проявляющиеся линии выравнивания по- могают создавать аккуратные интерфейсы за очень короткий срок. Так- же появилась возможность разработки приложений, которые могут переключаться между портретным и альбомным режимами экрана.
Глава 2 Первое приложение для .NET Compact Framework Первые шаги Практика — это самый лучший способ научиться программировать для мобильных устройств под .NET Compact Framework. Чтобы по- верить в свои силы, нужно создать простейшее приложение. На его примере можно будет изучить различия между .NET Compact Framework и обычной .NET Framework. Прежде всего нужно запустить среду разработки Microsoft Visual Studio 2005 и создать новый проект. Первые различия в процессе разработки можно увидеть уже на этой стадии. Если для создания обычных приложений надо было выбрать раздел Windows, то на этот раз необходимо выбрать раздел Smart Device. В этом разделе содер- жатся подразделы, которые отвечают за создание приложений для КПК, смартфонов и устройств под управлением операционной си- стемы Windows СЕ. Итак, нужно указать язык программирования Visual С#, перейти в раздел Smart Device и выбрать подраздел Pocket PC 2003 (рис. 2.1). ПРИМЕЧАНИЕ---------------------------------------- Список подразделов на рисунке может отличаться от списка под- разделов на вашем компьютере. Например, пункты Windows Mobile 5.0 Pocket PC и Windows Mobile 5.0 Smartphone появились после уста- новки соответствующих пакетов SDK. В выбранном подразделе присутствуют несколько шаблонов для ре- ализации различных задач. Как правило, используется шаблон Device Application. Нужно отметить, что существует еще один похожий шаб- лон с названием Device Application (1.0). Эти два шаблона различают- ся применяемой версией.ИЕТ Compact Framework. По умолчанию в Visual Studio 2005 используется .NET Compact Framework версии 2.0. Если выделить первый шаблон, то в строке состояния можно уви- деть сообщение A project for creating a .NET Compact Framework 2.0 forms
20 Глава 2. Первое приложение для .NET Compact Framework application for Pocket PC 2003 and later. В примерах будет использовать- ся, как правило, версия 2.0, так как она имеет ряд преимуществ. Рис. 2.1. Выбор типа платформы После того как будет выбран шаблон для приложения, требуется изменить имя проекта. По умолчанию используется название DeviceApplicationl, но наше первое приложение получит имя FirstPocketPCApp_CS. После нажатия кнопки ОК откроется окно сре- ды разработки с необычным видом формы. Если при программиро- вании программ для настольных компьютеров отображается толь- ко форма, то в данном случае на экране будут показаны не только форма, но и внешний вид целевого устройства. При желании раз- работчик может даже изменить внешний вид карманного компью- тера, создав специальные файлы. Если вы предпочитаете работать с классическим видом формы, то можно отключить отображение устройства, оставив на экране только форму. Для этого нужно щелк- нуть правой кнопкой мыши на форме и в появившемся контекст- ном меню выбрать пункт Show Skin. Повторный выбор этого пункта вернет на экран стандартный вид формы. Обычно в качестве первого примера создается стандартная про- грамма, которая выводит приветствие на экран. На форме надо рас- положить кнопку Button и элемент Label для отображения надписи. Также потребуется написать код для обработчика события Click созданной кнопки. Этот код приведен в листинге 2.1.
Первые шаги 21 Листинг 2.1 private void butSayHello_Click(object sender, EventArgs e) { IblHello.Text = "Здравствуй, мир!"; } Теперь можно запустить проект при помощи команды Start Debugging или клавиши быстрого вызова F5. При этом на экране появится диалоговое окно Deploy (рис. 2.2). Рис. 2.2. Диалоговое окно Deploy В основном списке окна перечислены устройства, на которых может выполняться написанная программа. Проверять работу приложения можно как на реальном устройстве, так и при помощи эмулятора. Как правило, при отладке программы используют эмуляторы и толь- ко в финальной части тестируют программу на реальном устройстве. Легко заметить, что создание программ для КПК совсем не требует наличия карманного компьютера. Автор не раз был свидетелем того, как разработчик на своем сайте признавался, что написал приложе- ние, не имея КПК. А пользователи благодарили автора за хорошую программу и подтверждали ее работоспособность на своих реальных моделях. Практически все примеры из этой книги сначала запускались на эмуляторе. Поэтому можно выбрать любой эмулятор из предложен- ного списка. Чаще всего применяется эмулятор Pocket PC 2003 SE. После выбора соответствующего значения в списке нужно нажать кнопку Deploy. Сначала на экране монитора будет отображен сам эмулятор (рис. 2.3), а спустя некоторое время в эмуляторе будет запущена созданная программа.
22 Глава 2. Первое приложение для .NET Compact Framework Рис. 2.3. Первый запуск эмулятора Мышью можно щелкнуть на кнопке с надписью Поздороваться. В ре- зультате на форме появится строка Здравствуй, мир! (рис. 2.4). Рис. 2.4. Отображение сообщения Теперь, когда вы поверили в свои силы, вам захочется начать пере- писывать свои старые программы, написанные на C# или Visual Basic .NET, для карманных компьютеров. Но торопиться все же не стоит. Между полной версией .NET Framework и .NET Compact
Размеры и позиция формы 23 Framework существует довольно много различий, которые придет- ся последовательно устранять. Кнопки минимизации и закрытия формы При создании пустой формы можно заметить, что отображается всего одна кнопка минимизации вместо привычных трех кнопок свертыва- ния, восстановления и закрытия формы. Причем кнопка минимиза- ции в виде крестика очень похожа на кнопку закрытия формы в обыч- ных настольных приложениях. У начинающих программистов это поначалу вызывает недоумение. Стандартная модель поведения про- грамм на карманном компьютере устроена таким образом, что когда пользователь нажимает кнопку с крестиком, то он сворачивает про- грамму, а не закрывает ее. Об этом говорит значение True свойства формы MimmizeBox. Если для этого свойства задать значение False, то вместо кнопки с крестиком будет отображаться кнопка ОК (рис. 2.5). При нажатии на эту кнопку программа завершит свою работу. Рис. 2.5. Приложение с кнопкой ОК Размеры и позиция формы По умолчанию любая форма занимает весь экран. Ее верхний левый угол находится в точке с координатами (0, 26). Если попробовать вручную изменить значения свойства Location, то среда разработки проигнорирует эти попытки и вернет значения. Что же касается раз- меров формы, то при желании все же можно изменить высоту и ши- рину формы. Но на практике подобная необходимость встречается редко, поэтому мы не будем заострять на этом внимание.
24 Глава 2. Первое приложение для .NET Compact Framework Меню По умолчанию в создаваемой форме уже содержится элемент управ- ления Main Menu. Однако в первом примере он не применялся. Чтобы использовать этот элемент управления, нужно создать простое меню с одним пунктом. В области Component tray нужно выделить мышью элемент mainMenul. На форме появится надпись Type here. В этой об- ласти нужно ввести слово «Поздороваться». Для обработчика собы- тия menuIteml_CHck будет применяться тот же код, который вызы- вался при нажатии на кнопку. Код обработчика события приведен в листинге 2.2. Листинг 2.2 private void menuIteml_Click(object sender, EventArgs e) { 1blHello.Text = "Здравствуй, мир!": } После запуска программы можно заметить, что созданный пункт меню располагается в нижней части экрана, в отличие от настоль- ных приложений, в которых меню располагается в верхней части окна (рис. 2.6). Рис. 2.6. Меню в нижней части окна Следует отметить, что меню в приложениях для Pocket PC распо- лагается не на форме, а на панели задач. Также на панели задач на- ходится значок виртуальной клавиатуры SIP для ввода информа- ции. Когда пользователь запускает приложение, то его меню появляется на панели задач. Но если удалить меню из формы, то при запуске программы панель задач вообще не будет отображать- ся (рис. 2.7).
Панель ввода SIP 25 Рис. 2.7. Экран без панели задач Панель ввода SIP В этой главе уже упоминалась виртуальная клавиатура. Большин- ство карманных компьютеров не имеют встроенных клавиатур для ввода информации. Вместо клавиатуры в этом случае использует- ся специальная панель ввода SIP (Software Input Panel), которая позволяет вводить текст (рис. 2.8). Рис. 2.8. Активированная панель ввода SIP Для работы с виртуальной клавиатурой в .NET Compact Framework используется класс InputPanel. Так как панель ввода находится на па- нели задач, то необходимо, чтобы панель задач была видимой. А ранее уже говорилось что, если форма не имеет меню, то панель задач будет невидима. В результате при попытке создания экземпляра класса InputPanel на форме, не имеющей меню, будет отображено сообщение об ошибке.
26 Глава 2. Первое приложение д ля .NET Compact Framework Стилус вместо мыши Подавляющее число пользователей настольной версии Windows пользуются мышью. В карманных компьютерах роль мыши выпол- няет стержень из пластика, называемый стилусом. Конечно, у сти- луса нет правой кнопки для вызова контекстного меню. У него во- обще кнопок нет. Вместо кнопок в карманных компьютерах применяется технология Tap-and-Hold. Для выделения элемента управления пользователь должен точно попасть в него кончиком стилуса. По аналогии с мы- шью, можно легко щелкнуть по экрану (Click), а можно нажать на экран и удерживать стилус на месте (Press).
Глава 3 Элементы управления Сходство и различия Несмотря на свою схожесть, .NET Compact Framework уступает в функциональности базовой библиотеке .NET Framework. Это относится и к элементам управления. К счастью, кнопки, списки и текстовые поля все же присутствуют в мобильной версии. Кро- ме того, в NET Compact Framework 2.0 была добавлена под- держка еще нескольких элементов управления, которые отсут- ствовали в .NET Compact Framework 1.0. * йяй) STWeMOS -MKtQjon aoepment ftptotws , 0'X !• ’Л» V» 'i мкта e vju»Sasc.Log?ty 4i user. This field reads the Windows registry base key HKEY_CURRENTCONFIG Contains information about the current user prefo>< nets This field reads the Windows registry base key HKEYCURRENT.USER Eh'.n.Pata Regtty Members • ftfegttry Fticu ft ftegfctry Me£tod$ ft RegstryKeyGaes RegstryKeyPtrntisaonO RegstryVabewnd Enw*» i Reg^yVabeOpitonsEnt- # Sw^onEndedEvent^.'^ Contains dynamic registry data. This field reads the Windows registry base key HKEY_DYN DATA. toc^lMafhlng Contains the configuration data for the local machine. This field reads the Windows registry base key HKEY_LOCAL_MACHINE. Conta.ns performance Information for software components. This field reads the Windows registry base key HKEY PERFORMANCE-DATA. Contains .nformation about the default user r»nfioi.tr»t:nn...Thl«firMdreflrts.the.Windows ...................Ix Ф'ОМ ЛГ-Я» Рис. 3.1. Свойства, поддерживаемые в NET Compact Framework Нужно помнить, что даже поддерживаемые элементы управления имеют порой ограниченные возможности. Чтобы узнать, какие свойства, методы или события не поддерживаются элементом уп-
28 Глава 3. Элементы управления равления, нужно запустить справочную систему, найти нужный класс и просмотреть все члены класса. Если нужное свойство под- держивается в .NET Compact Framework, то у его описания будет присутствовать значок мобильного устройства (рис. 3.1). В каче- стве примера можно открыть страницу с описанием класса Registry. Легко заметить, что поля Currentuser, Local Machine и Users поддерживаются в .NET Compact Framework, а поля DynData и PerfomanceData — нет. Но даже если необходимый объект поддерживается в .NET Compact Framework, то все равно следует внимательно ознакомиться с его опи- санием. Например, поле Local Machine поддерживается только в .NET Compact Framework 2.0, поэтому при разработке нужно решить, стоит ли использовать это поле (рис. 3.2). Рис. 3.2. Просмотр поддержки версий .NET Compact Framework В следующем списке перечислены элементы управления, которые не входят в состав классов библиотеки .NET Compact Framework 1.0: □ CheckedListBox; □ ColorDialog; □ ErrorProvider;
Сходство и различия 29 □ FontDialog; □ GroupBox; □ HelpProvider; □ LinkLabel (поддерживается в .NET Compact Framework 2.0); □ NotlflcatlonBubble; □ Notify I con; □ элементы управления, связанные с печатью, □ RIchTextBox; □ Spl 1 tter (поддерживается в .NET Compact Framework 2.0). В Compact .NET Framework 2.0 были добавлены новые элементы управления, которые перечислены в следующем списке. □ MonthCal endar — месячный календарь, позволяющий в наглядном виде выбрать необходимую дату. □ Da tel 1 mePi eke г — элемент для выбора даты и времени. Он достаточ- но компактен, что позволяет широко использовать его в приложе- ниях. □ WebBrowser — элемент, который реализует функциональность браузера. □ Noti f 1 catl on — элемент, с помощью которого приложение может посылать пользователю различные уведомления без остановки текущей запущенной программы. Уведомления могут отобра- жаться как обычным текстом, так и в формате HTML. □ DocumentLi st — элемент управления, обеспечивающий стандартный механизм для управления файлами. Пример работы данного эле- мента можно увидеть при открытии файлов в приложениях Excel Mobile и Word Mobile. Элемент DocumentLI st позволяет перемещать- ся по файловой системе и выполнять стандартные файловые опе- рации. □ DataGrid — элемент для отображения данных в табличном виде. Теперь может использоваться и в приложениях для смартфонов. □ LinkLabel — элемент управления для создания гипертекстовых ссылок. □ Spl 1 tter — элемент управления, позволяющий изменять разме- ры других элементов. □ НаrdwareButton — элемент управления, позволяющий управлять кнопками карманного компьютера.
30 Глава 3. Элементы управления Урезанная функциональность элементов управления Кроме отсутствия некоторых элементов управления, в .NET Compact Framework также была урезана функциональность имею- щихся элементов. Наиболее часто употребляемые элементы управ- ления с урезанной функциональностью приведены в следующем списке: □ AcceptButton; □ Cancel Button; □ AutoScrol 1 (поддерживается в .NET Compact Framework 2.0); □ Anchor (поддерживается в .NET Compact Framework 2.0); □ элементы Multiple Document Interface (MDI); □ KeyPreview (поддерживается в .NET Compact Framework 2.0); □ Tabindex (поддерживается в .NET Compact Framework 2.0); □ TabStop (поддерживается в .NET Compact Framework 2.0). Также наложены ограничения на технологию drag and drop и на поддержку графики. Во многих классах поддерживаются не все свойства, события и методы. Однако в .NET Compact Framework 2.0 ограничений стало меньше. Например, элементы управления теперь обладают свойствами Tabindex и TabStop. Элемент Form Элемент Form является контейнером для элементов управления и является рабочей площадкой для создания пользовательского ин- терфейса программы. Класс Form имеет несколько свойств, которые мо- гут различаться в зависимости от выбранной целевой платформы. Свойство FormBorderStyle Свойство FormBorderSytl е определяет стиль формы. По умолчанию используется стиль FormBorderStyle.FixedSingle. При этом форма заполняет все рабочее место экрана, и пользователь не может изме- нять размеры формы или перемещать ее по экрану. При установке значения FormBorderStyl е. None создается форма без рамки и заголов- ка. В этом случае можно изменять размеры и расположение формы
Элемент Form 31 программно, но пользователь по-прежнему не может манипулиро- вать формой. Свойство ControlBox Свойство ControlBox отвечает за отображение контейнера для эле- мента управления. Если свойство ControlBox имеет значение True, то контейнер будет отображаться. В противном случае он на экран не выводится. Для устройств Pocket PC подобный контейнер мо- жет содержать только одну кнопку. Свойства MinimizeBox и MaximizeBox В приложениях для Pocket PC форма может содержать только одну кнопку. Она отвечает либо за минимизацию формы, либо за ее за- крытие. Разработчик может управлять внешним видом кнопки при помощи свойства Minimi zeBox. Если оно имеет значение Т rue, то кноп- ка при нажатии будет сворачивать форму. Значение False позволя- ет создавать кнопку закрытия формы. Значение свойства Maxi mi zeBox игнорируется системой. Свойство WindowsState Свойство WindowsState определяет состояние окна при первона- чальной загрузке. Разработчик может использовать значения FormWindowState. Normal и FormWindowState. Maxi mi zed. Если свойство имеет значение FormWi ndowState. Normal то форма заполняет весь эк- ран, за исключением нижней полоски меню и верхней полоски системного меню Start (Пуск). При использовании значения FormWi ndowState. Maxi mi zed форма заполняет экран полностью, скры- вая системное меню Start (Пуск), но при этом нижняя полоса меню остается видимой. Размеры и расположение формы Свойство Size позволяет задавать размеры формы. Это свойство игнорируется, если свойство FormBorderStyle имеет значение Fi xedSingleProperty. Свойство Location задает координаты верхнего левого угла формы. Но так как форма обычно заполняет весь экран, то в большинстве случаев это свойство не используется.
32 Глава 3. Элементы управления Элементы управления В этом разделе будут рассмотрены основные элементы управления, которые используются для формирования пользовательского ин- терфейса. Особое внимание будет уделено различиям и особенно- стям поведения этих элементов. Элемент Button Для создания обычной кнопки используется класс System.Win- dows . Forms. Button. Эта кнопка обладает всеми основными функци- ями, которые есть у такого же класса в полной версии .NET Framework. Кнопка предназначена для обработки нажатия стилуса на соответствующую область экрана. В этом случае возникает со- бытие Click. Код, приведенный в листинге 3.1, является обработчи- ком этого события. Он выводит текущее время в текстовое поле после нажатия на кнопку с надписью Узнать время. Листинг 3.1 private void butGetTime_Click(object sender, EventArgs e) { txtCurTime.Text = DateTime.Now.ToLongTimeStringO: } Рисунок 3.3 показывает приложение в момент нажатия на кнопку. Рис. 3.3. Результат нажатия на кнопку Текст на кнопке может быть только однострочным. Если он не по- мещается на кнопке, то будет обрезан. Поэтому нужно быть очень осторожным при выборе текста для кнопки. В следующей главе, посвященной улучшениям элементов управления, приведен при-
Элементы управления 33 мер создания кнопки с многострочным текстом, которая создается при помощи неуправляемого кода с использованием функций Windows API. Функциональность элемента управления Button очень сильно уре- зана по сравнению с полной версией .NET Framework. В частности, у данного элемента нет свойств Image и ImageLi st, которые применя- ются для отображения на кнопке графики. Элемент TextBox В предыдущем примере дата отображалась в текстовом поле. Это поле создается при помощи класса TextBox, который позволяет вво- дить текст. Данный элемент поддерживает такие стандартные свой- ства, как BackColor и ForeColor. Событие Cl ick элементом TextBox не поддерживается, но разработчик может воспользоваться события- ми Keypress, KeyUp и KeyDown. Следует отметить особенность этого элемента. Несмотря на то что класс TextBox поддерживает свойство PasswordChar, при вводе пароля на экране всегда будет использовать- ся символ звездочки. Задать другой символ не получится. Также текстовое поле не поддерживает свойство Charactercasing, позволяющее в автоматическом режиме преобразовывать символы текста в нужный регистр. Впрочем, данный недостаток легко ис- править, что иллюстрирует фрагмент кода, приведенный в листин- ге 3.2. Листинг 3.2 private void txtCurTime KeyPress(object sender. KeyPressEventArgs e) { i f(Char.IsLetter(e.KeyChar)) { // сохраняем текущую позицию каретки int pos - txtCurTime.Selectionstart; // переводим в верхний регистр txtCurTime.Text = txtCurTime.Text. Insert(txtCurTi me.Seiecti onStart, Char.ToUpper(e.KeyChar).ToStri ng()); // перемещаем каретку в новую позицию txtCurTime.Selectionstart = pos + 1; e.Handled true; } } 2-2873
34 Глава 3. Элементы управления ПРИМЕЧАНИЕ---------------------------------------- У смартфонов внешний вид текстовых полей несколько отличается от стандартного вида. В частности, текстовое поле не имеет окантовки. Более подробно о текстовых полях в приложениях для смартфонов рассказывается в соответствующей главе. Элемент Label В рассмотренном примере также использовался элемент Label для отображения текстовой строки. Как правило, надпись использует- ся для отображения некоторого текста, который пользователь не может изменить. Сама отображаемая строка задается при помощи свойства Text. Текст на экране можно выравнивать с помощью свой- ства TextAl ign. Разработчик может использовать значения TopLeft, TopCenter и TopRight. При изменении текста в метке инициируется событие TextChanged. При создании элемента нужно следить за дли- ной отображаемой строки. Если текст слишком большой и не поме- щается в пределах элемента, то он попросту обрезается. В отличие от полной версии .NET Framework, элемент Label в .NET Compact Framework не поддерживает такие свойства, как AutoSize, Borderstyle, Image, ImageList и многие другие. Также не под- держивается событие Click. Впрочем, на практике редко возникает нужда в обработке этого события. Элемент RadioButton Элемент управления RadioButton позволяет создавать переключа- тели, объединенные в группы. Вся группа переключателей должна располагаться в контейнере. Примером такого контейнера может служить сама форма, но чаще используется элемент Panel. Когда пользователь выбирает один переключатель, то остальные переключатели в контейнере автоматически переводятся в выклю- ченное состояние. Приложение может иметь несколько групп эле- ментов RadioButton. В любом случае группы переключателей не за- висят друг от друга. При изменении состояния переключателя в классе RadioButton ини- циируются события Click и CheckedChanged. Событие Click возника- ет, когда пользователь щелкает стилусом на самом переключателе. Событие CheckedChanged возникает, когда состояние элемента Radi oButton меняется программно или в результате действий пользе-
Элементы управления 35 вателя. Событие Click не инициируется, когда свойство CheckedChan- ged меняется программно. Для демонстрации примера работы с элементом RadioButton можно создать аналог популярной телеигры «Кто хочет стать миллионером?». На экране будет отображаться вопрос, а пользователь должен выб- рать из представленных вариантов единственный правильный ответ. Код, реализующий основную функциональность приложения, приве- ден в листинге 3.3. Листинг 3.3 private void radClubl_CheckedChanged(object sender, EventArgs e) { if (this.radClubl.Checked) MessageBox.Show ("Увы, вы проиграли", “Ошибка!"): } private void radClub2_CheckedChanged(object sender, EventArgs e) { if (this.radClub2.Checked) MessageBox.Show ("Поздравляю! Вы выиграли миллион!", "Миллион!"): } private void radClub3_CheckedChanged(object sender, EventArgs e) { if (this.radClub3.Checked) MessageBox.Show ("Увы, вы проиграли", "Ошибка!"): } private void radClub4_CheckedChanged(object sender, EventArgs e) { if (this.radClub4.Checked) MessageBox.Show ("Увы, вы проиграли"."Ошибка!"): } На рис. 3.4 показан внешний вид этого приложения. В полной версии .NET Framework в качестве контейнера для пере- ключателей часто используется элемент GroupBox, который на дан- ный момент не поддерживается в библиотеке .NET Compact 2
36 Глава 3. Элементы управления Framework. Также не поддерживаются некоторые свойства, к кото- рым относятся Appearance, Image и ImageList. Рис. 3.4. Демонстрация работы независимых переключателей Элемент Panel Элемент управления Panel используется в качестве контейнера для размещения других элементов управления. Так как .NET Compact Framework не поддерживает элемент управления GroupBox, то для группировки таких элементов, как переключатели RadioButton, при- ходится использовать именно Panel. В версии .NET Compact Framework элемент не поддерживает свой- ства BorderStyle, BackGroundImage и AutoScroll. Элемент CheckBox Элемент управления CheckBox позволяет создавать независимый пере- ключатель в виде флажка. Элемент CheckBox имеет свойство Checkstate, позволяющее определить состояние переключателя. Программист может использоваться значения Unchecked, Checked и Indetermi nate. Зна- чение Unchecked свидетельствует о том, что флажок в переключателе не взведен. Если переключатель все же включен, то используется зна- чение Checked. Но значение Indeterminate требует некоторых поясне- ний. Состояние Indeterminate используется, когда для свойства ThreeState элемента CheckBox установлено значение Тrue. Если свойство Checkstate имеет значение Indeterminate, то элемент окрашен серым цветом, но, тем не менее, считается помеченным. При этом пользова- тель не может изменить состояние переключателя.
Элементы управления 37 Также элемент не распознает событие Click, если свойство AutoCheck имеет значение False. Для этого свойства нужно задать значение Тrue, чтобы пользователь мог пользоваться стилусом для работы с переключателем. Также элемент также не поддерживает некоторые свойства, в част- ности, ImageIndex. Элемент ComboBox Элемент управления ComboBox позволяет создавать поле со списком выбора. Благодаря своей компактности этот элемент управления хорошо подходит для тех задач, когда требуется экономить место на экране. Поле со списком выглядит как обычное текстовое поле Text Box со стрелкой, которая расположена в правой части поля. Ког- да пользователь щелкает по стрелке, то открывается список с пред- варительно заданными элементами. Когда пользователь выбирает определенный пункт списка или снова щелкает по стрелке, то спи- сок снова сворачивается. Добавлять текстовые элементы в ComboBox можно как в режиме про- ектирования, так и программно во время работы программы. В листинге 3.4 приведен пример добавления пунктов программным путем. Для этого нужно вызвать метод Add в свойстве коллекции Items элемента ComboBox. Отдельные пункты можно удалять с помо- щью метода Remove, а чтобы удалить все пункты сразу, применяется метод Clear. Приведенный пример показывает, как можно добавить три строки в элемент ComboBox с именем comboBoxl. Листинг 3.4 comboBoxl.Items.Add("Myp3MK"); comboBoxl.Items.Add("Барсик"); comboBoxl.Iterns.Add("Рыжик"): Чтобы узнать, какой элемент выбрал пользователь, применяет- ся свойство Selectedlndex или Selectedltem. Свойство Selectedlndex возвращает порядковый номер выбранного пункта. Этот номер можно использовать для доступа к выбранному пункту при по- мощи свойства Items. Следует помнить, что нумерация элемен- тов начинается с нуля. Пример работы со свойством Select Index приведен в листинге 3.5. Также в этом листинге показано, как можно получить доступ к выбранному пункту при помощи свой- ства Selectedltem.
38 Глава 3. Элементы управления Листинг 3.5 // Получим выделенный пункт с помощью Selectedlndex string seiItem = (string) cmbCats.Iterns[cmbCats.Selectedlndex]; MessageBox.Show(selItem); 11 Второй способ - получим пункт с помощью Seiectedltem string selltem = cmbCats.Selectedltem.ToStringO; MessageBox.Show(seiItem); В полной версии .NET Framework у элемента ComboBox для свойства DropDownStyle можно задавать значения Simple, DropDownList или DropDown. В .NET Compact Framework значение Simple не использует- ся. До выхода .NET Compact Framework 2.0 также не поддержива- лось и значение DropDown. Кроме того, по умолчанию в .NET Compact Framework применяется значение DropDownLi st, тогда как в полной версии .NET Framework по умолчанию используется стиль DropDown. Также не поддерживаются многие методы из основной версии биб- лиотеки. В .NET Compact Framework 2.0 у поля со списком появи- лась поддержка методов BeginUpdate и EndUpdate, которые позволяют избежать мерцания при загрузке большого числа элементов. ВНИМАНИЕ------------------------------------------------------ Внешний вид и поведение элемента ComboBox в смартфонах немно го отличается от аналогичных элементов в КПК. Более подробно об отличиях будет рассказано в соответствующей главе. Элемент ListBox Элемент ComboBox хорош для приложений с ограниченными простран- ствами формы, а список ListBox можно использовать, если на экране достаточно места для отображения всех пунктов списка. Список Listbox сразу показывает все имеющиеся элементы списка, при не- обходимости добавляя вертикальную полоску прокрутки, если все элементы списка не могут быть отображены одновременно. Элементы ComboBox и Li st Box имеют почти одинаковый набор свойств и методов. В листинге 3.6 показано, как можно программно доба- вить несколько строк в список ListBox. Листинг 3.6 1stFruit.Items.Add("Яблоко"): 1stFruit.Items.Add("Груша"); 1 stFrui t.Iterns.Add("Слива"); 1 stFruit.Iterns.Add("Персик");
Элементы управления 39 Свойство Selected Index содержит порядковый номер выбранного элемента списка. Если указать этот индекс в коде приложения, то выбранный элемент будет немедленно выделен в списке соответ- ствующим цветом. Если никакой элемент не выбран, то свойство Selected Index имеет значение -1. Также класс поддерживает свой- ство Selected Item, которое соответствует одноименному свойству класса ComboBox. Из часто используемых свойств элемента ListBox в полной версии NET Framework можно выделить свойство Multi Column, которое не поддерживается в .NET Compact Framework. В нем отсутствует го- ризонтальная полоска прокрутки, даже если строки текста не уме- щаются в списке полностью. Также не поддерживается многостроч- ное выделение, поэтому пользователь может выбрать только один элемент списка. Элемент NumericUpDown Элемент Numeri cUpDown позволяет создавать счетчик с числовым полем ввода. Такой элемент интерфейса помогает пользователю быстро вы- брать число из заданного диапазона. Элемент может работать только с целыми числа типа Integer. Десятичные значения округляются. Разработчик управляет поведением элемента NumericUpDown при по- мощи свойств Minimum, Maximum, Value и Increment. Свойства Minimum и Maximum определяют максимальное и минимальное значения элемен- та. Свойство Value содержит текущее значение в поле ввода. Свой- ство Increment определяет величину увеличения или уменьшения значения в поле, когда пользователь нажимает кнопки со стрелка- ми. Текущее значение всегда увеличивается и уменьшается на зна- чение свойства Increment, даже если результат выходит за диапа- зон, определенный свойствами Minimum и Maximum. Пользователь также может изменить свойство Value, просто указав соответствующее значение в поле. Если это значение находится в ин- тервале между Mi ni mum и Maxi mum, тогда свойства Va 1 ue и Text изменятся в соответствии с введенным значением. Если новое значение выходит за рамки заданных значений, то свойство Text отображает введенное число, а свойство Value принимает значение, которое приписано свой- ству Maximum. Чтобы запретить пользователю указывать числа в поле ввода, нужно для свойства Readonly задать значение True. При изменении значения элемента NumericUpDown инициируется со- бытие Vai ueChanged. Оно возникает только в том случае, если значе-
40 Глава 3. Элементы управления ние меняется программно или когда пользователь нажал кнопки со стрелками. При вводе числа событие не инициируется. В листинге 3.7 продемонстрирован пример использования элемента Numeri cUpDown и обработки события ValueChanged. Листинг 3.7 private void numericUpDownl_ValueChanged(object sender, EventArgs e) { int year = (int)this.numericllpDownl.Value: this.1 bl Note.Text = "Вы выбрали " + year.ToStringO + " год": } На рис. 3.5 показано, как функционирует элемент Numeri cUpDown. Рис. 3.5. Выбор года при помощи элемента NumericUpDown При работе с элементом Numeri cUpDown следует учитывать одну осо- бенность его функционирования. Предположим, пользователь на- жимает кнопку со стрелкой вверх, постоянно увеличивая значение счетчика на величину свойства Increment. При достижении макси- мального значения, определенного в свойстве Maximum, счетчик со- хранит значение, которое не будет отображено на экране. Теперь, когда пользователь начнет уменьшать значения с помощью кнопки со стрелкой вниз, то отчет пойдет не от максимального значения, которое отображено в поле ввода, а от последнего значения перед достижением максимума. Стоит проиллюстрировать эту ситуацию. Итак, у нас установлено текущее значение, равное 1992. Значение свойства Increment равно 6, а максимум ограничен значением 2006. Последовательные нажа- тия стрелки вверх доведут значение с 1992 до 2006. Итак, макси-
Элементы управления 41 мальное значение достигнуто. Теперь надо нажать кнопку со стрел- кой, направленной вниз. Казалось бы, на экране должно быть пока- зано число 2000 (2006 - 6), но следует учитывать, что перед превы- шением максимального значения счетчик запомнил число 2004 Именно от него будет отсчитываться разница, и на экране будет отображено число 1998. Элемент DomainUpDown Элемент DomainUpDown позволяет создавать счетчик с текстовым по- лем ввода. Этот элемент похож на элемент NumericUpDown, а его функ- циональность схожа с теми возможностями, которые предоставля- ют ComboBox или ListBox. Но в элементе DomainUpDown вместо чисел используются строки. Этот элемент очень широко применяется для построения интерфейса, так как он весьма компактен и не занима- ет много места на маленьком экране карманного компьютера. Сле- дует учитывать, что пользователь не может увидеть весь список. Если свойство Readonly имеет значение Тrue, то пользователь может выбирать только заранее заданные строки из списка. Если это свой- ство имеет значение False, то пользователь сможет добавить свой текст в поле ввода. Впрочем, напечатанный текст все равно не вой- дет в список. Так же как и элемент Numeri cUpDown, данный элемент управления со- держит текстовое поле и две кнопки со стрелками с правой сторо- ны Пользователь может использовать эти стрелки для прокрутки списка строк или ввести в поле свой текст, если свойство Readonly имеет значение Fa 1 se. При создании объекта свойство Selectedlndex имеет значение -1, показывающее, что ни один элемент списка пока еще не выбран. Если нужно выделить тот или иной пункт списка при загрузке эле- мента, то в свойстве Selectedlndex нужно указать соответствующий порядковый номер. В листинге 3.8 приведен пример, иллюстрирую- щий программное добавление строк в список и методику обработ- ки события SelectedltemChanged. Листинг 3.8 private void Forml Load(object sender. System.EventArgs e) { doma inUpDownl.Items.Add("Item domalnUpDownl.Items.Add("Item doma i nUpDownl.Iterns.Add("Itern domai nUpDownl.Items.Add("Item 1"); 2"); 3"); 4й); продолжение &
42 Глава 3. Элементы управления Листинг 3.8 (продолжение) domainUpDownl.Readonly » true; } private void domainUpDownl_SelectedItemChanged( object sender. System.EventArgs e) { labell.Text = domainUpDownl.Selectedlndex.ToStringO: label2.Text = domainUpDownl.Items[domainUpDownl.Selectedlndex].ToStringO: } Элемент ProgressBar Элемент управления ProgressBar предназначен для индикации процес- са выполнения какой-либо операции. Как правило, данный элемент активно используется при выполнении долгих операций, чтобы пользователь получил иллюзию контроля над работой приложения. Чаще всего разработчик оперирует свойствами Mi nimum, Maxi mum и Vai ue. Свойства Mi nimum и Maximum задают минимальное и максимальное зна- чения свойства Vaiue. А свойство Value определяет текущее значение индикатора. Как правило, данный элемент отображается в момент начала дол- гой операции, а после ее завершения делается невидимым с помо- щью метода Hide или свойства Visible. Для демонстрации работы индикатора прогресса было создано при- ложение, которое позволит отследить время варки яиц вкрутую. Предположим, что для варки достаточно трех минут. Нужно поло- жить яйца в воду и запустить таймер. По истечении трех минут при- ложение должно отобразить соответствующее сообщение. Основ- ной код приложения приведен в листинге 3.9. Листинг 3.9 private void tmrCook_Tick(object sender, EventArgs e) { if (this.progressBar1.Value < this.progressBarl.Maximum) { this.progressBarl.Value +- 1; 1 bl Counter .Text = this.progressBarl.Value.ToStringO; } if (this.progressBarl.Value >= this.progressBarl.Maximum) { tmrCook.Enabled = false;
Элементы управления 43 MessageBox.Show("Яйца сварились!"); this.progressBarl.Value = 0: 1 blCounter.Text = "0"; } } private void butStart_Click(object sender, EventArgs e) { tmrCook.Enabled = true: } На рис. 3.6 показан внешний вид приложения в момент отсчета вре- мени. Рис. 3.6. Индикатор прогресса, позволяющий сварить яйца вкрутую Элемент StatusBar Строка состояния выглядит как небольшая полоска в нижней час- ти приложения, в которой отображается текстовая информация для пользователя. Этот элемент интерфейса реализуется при помощи элемента StatusBar. Чтобы изменить текст в элементе StatusBar, до- статочно присвоить новое значение свойству Text. На рис. 3.7 пока- зан внешний вид приложения в тот момент, когда пользователь нажимает на кнопку, а в листинге 3.10 приведен пример кода, кото- рый меняет текст в строке состояния. Листинг 3.10 private void butClickMe_Click(object sender, EventArgs e) { this.statusBarl.Text - "Вы нажали на кнопку"; }
44 Глава 3. Элементы управления Рис. 3.7. Пример работы со строкой состояния Строка состояния поддерживает только одну информационную панель, а также не распознает события Click. Элемент TrackBar Элемент управления TrackBar предназначен для установки число- вого значения при помощи перемещения ползунка по числовой шкале. Основную работу с элементом разработчик выполняет при помощи свойств Minimum, Maximum и Value. Ползунок может распола- гаться как вертикально, так и горизонтально. Ориентация ползун- ка задается при помощи свойства Ori entati on. Свойство Т1 ckFrequency регулирует дистанцию между метками шкалы. По умолчанию зна- чение свойства Ti ckFrequency равно единице. Свойства Small Change и LargeChange определяют шаг изменения зна- чения Value. Свойство Small Change задает изменения основного зна- чения, когда пользователь нажимает на одну из кнопок навигации на самом карманном компьютере или на смартфоне. Свойство LargeChange показывает, на сколько будет изменено основ- ное значение, когда пользователь щелкнет стилусом на самом пол- зунке. При изменении значения свойства Value инициируется со- бытие ValueChanged. Для иллюстрации работы ползунка нужно создать новый проект и разместить на форме два элемента TrackBar. Один из них будет расположен горизонтально, а второй — вертикально. При этом по- ложение ползунков на шкалах будет синхронизировано (рис. 3.8). В листинге 3.11 приведен код, отвечающий за функциональность ползунков.
Элементы управления 45 Рис. 3.8. Пример работы с ползунками Листинг 3.11 private void trackVert VaiueChanged(object sender, EventArgs e) { this.trackHoriz.Value = this.trackVert.Value; } private void trackHoriz_ValueChanged(object sender, EventArgs e) { this.trackVert.Value = this.trackHoriz.Value: } Когда пользователь передвинет один ползунок, то второй ползунок автоматически будет переведен в то же положение, что и первый. Элемент Toolbar Элемент управления Tool Ваг позволяет создавать собственную па- нель инструментов. Во многих случаях использование панели ин- струментов может принести разработчику больше выгод, чем при- менение меню. Следует учитывать, что панель инструментов позволяет использовать изображения, что делает работу с этим эле- ментом удобным и наглядным. В .NET Compact Framework элемент Tool Ваг не может содержать текст. Все инструменты маркируются только при помощи графических изображений. Изображения свя- зываются с элементом при помощи класса ImageList. В приложени- ях для КПК панель инструментов всегда располагается в нижней части экрана справа от пунктов меню (рис. 3.9). Чтобы добавить в приложение элемент Tool Ваг, нужно сначала пе- реместить на форму элемент управления ImageLi st. Значок ImageLi st
46 Глава 3. Элементы управления появится в нижней части окна Form Designer рядом с элементом ma inMenu. В окне редактора свойств Properties нужно выбрать свой- ство Images и нажать кнопку редактирования. В результате будет открыто диалоговое окно Image Collection Editor. Рис. 3.9. Панель инструментов в приложении В этом окне следует добавить изображения, предназначенные для панели инструментов. Рекомендуется использовать картинки размером 16 х 16 пикселов, чтобы при их отображении не воз- никло искажений. Выбранные изображения включаются в состав приложения, и их не придется отдельно поставлять вместе с про- граммой. Теперь на форму надо перенести элемент Tool Ваг. В его свойстве Imaged st надо указать имя добавленного ранее элемента Imaged st. В рассматриваемом примере использовалось имя по умолчанию Imaged stl. Затем надо перейти к свойству Buttons и активировать окно редактора ToolBarButton Collection Editor. Так как было добавлено три изображения для кнопок, то нужно три раза нажать кнопку Add. Для каждой добавленной кнопки следует задать свойство Image Index. При необходимости разработчик может изменить стиль отображения кно- пок. По умолчанию используется стиль PushButton, который создает обычные кнопки. Если для кнопки задать стиль DropDownButton, то при ее нажатии будет отображаться меню или другое окно. Справа от кнопки на панели инструментов отображается стрелка. Стиль Separator позволяет создавать разделители между кнопками на панели управления. Стиль Togg 1 eButton позволяет создавать пе- реключаемую кнопку. При первом нажатии она переходит в акти- вированное состояние, в котором и остается до тех пор, пока кноп- ку не нажмут повторно.
Элементы управления 47 Итак, панель инструментов уже готова, хотя еще не было написано ни одной строчки кода. При щелчке на кнопках элемента Tool Ваг возникает событие ButtonCl ick. В листинге 3.12 приведен код, обра- батывающий нажатие первых двух кнопок. Листинг 3.12 private void tool BarlButtonCl ick (object sender. ToolBarButtonCli ckEventArgs e) { if (e.Button = this.toolBarButtonl) { MessageBox.ShowC'Bw выбрали первую кнопку"): } else if (e.Button = this.toolBarButton2) { MessageBox.ShowCBw выбрали вторую кнопку"); } } Элемент MainMenu Меню является одним из самых важных элементов графического ин- терфейса Windows-приложений. Не являются исключением и про- граммы для мобильных устройств. По умолчанию на форме уже при- сутствует элемент MainMenu. Но при добавлении в проект новой формы на ней меню не появляется, и его нужно добавить вручную. Следует помнить, что в приложениях для Pocket PC меню распо- лагается в нижней части окна программы, тогда как в приложениях для обычных компьютеров меню располагается в верхней части окна. Если в программе одновременно присутствуют меню и панель инструментов, то они будут отображаться вместе. Но меню будет прижато к левой границе окна, а панель инструментов — к правой. Пример работы с меню приведен в листинге 3.13. Листинг 3.13 private void mnuAbout Click(object sender, EventArgs e) { MessageBox.Show("Это моя программа!"); } private void mnuExit Click(object sender, EventArgs e) { this.CloseO; }
48 Глава 3. Элементы управления Элемент ContextMenu Элемент ContextMenu позволяет создавать контекстные меню для других элементов интерфейса. Этот элемент очень похож на эле- мент управления Mai nMenu. Но если Mai nMenu всегда связан с формой приложения, то ContextMenu можно связать с любым элементом фор- мы. Так как в КПК не используется мышь, то вызов контекстного меню вызывается операцией tap-and-hold вместо привычного щел- чка правой клавишей мыши. ВНИМАНИЕ------------------------------------------ Если вы пользуетесь эмулятором, то для имитации tap-and-hold нуж- но щелкнуть левой кнопки мыши и не отпускать ее некоторое время. Чтобы добавить элемент ContextMenu в приложение, нужно сначала переместить его значок на форму. Он появится в нижней части ре- дактора Form Designer, там же, где и элемент MainMenu. Но на самом деле во время выполнения программы контекстное меню будет ото- бражаться рядом с выбранным элементом интерфейса. Также кон- текстное меню можно создавать программно во время запуска при- ложения. При вызове контекстного меню инициируется событие Popup. Когда пользователь выбирает какой-то пункт меню, то возникает событие Cl ick. Чтобы привязать созданное контекстное меню к конкретному элементу интерфейса, нужно выбрать его на форме и в свойстве ContextMenu указать созданное контекстное меню. ПРИМЕЧАНИЕ--------------------------------------------------- До выхода .NET Compact Framework 2.0 элемент управления ContextMenu не поддерживал свойство ContextMenu.SourceControl. Элемент Timer Элемент Timer позволяет выполнять некоторые действия по исте- чении заданных интервалов времени. Чаще всего для работы с тай- мером разработчик применяет событие Tick. Данное событие ини- циируется только в том случае, если свойство Enabled имеет значение True. Если нужно остановить таймер, то достаточно при- своить данному свойству значение False. Интервал отсчета времени задается свойством Interval, а его зна- чение указывает используемый промежуток времени в миллисекун-
Элементы управления 49 дах. Если рабочий интервал таймера должен составлять 3 с, то надо установить значение 3000. Этот элемент управления уже применялся при работе с объектом ProgressBar. Элементы OpenFileDialog и SaveFileDialog Практически в каждом приложении пользователь должен иметь воз- можность сохранить файл или открыть его. Разработчикам регуляр- но приходится реализовывать подобную функциональность в своих программах. При желании можно самому придумать и разработать интерфейс для подобной задачи. Но можно воспользоваться и стан- дартными диалоговыми окнами открытия и сохранения файла. Именно для этого применяются элементы управления OpenF i1eDi а1од и SaveFileDialog. К сожалению, в версии .NET Compact Framework возможности данных элементов управления серьезно урезаны. Раз- работчик может манипулировать файлами только в пределах папки Му Documents и вложенных папок следующего уровня. Поэтому пап- ка Му Documents\Programming\Sample будет уже недоступна. Рассматриваемые элементы управления размещаются в нижней части дизайнера формы рядом с элементом Mai nMenu. При работе с данными элементами прежде всего надо позаботиться о свойстве Filter, которое ограничивает список доступных файлов, фильт- руя их по расширению. Свойство Ini tai Di rectory содержит имя папки, в которой по умолчанию располагаются файлы. Если это свойство оставить пустым, то обзор файлов начнется с самой пап- ки Му Documents. Основным методом для этих элементов является ShowDi al од. После его вызова на экране отображается модальное окно, в котором пользова- тель должен нажать кнопку ОК или Cancel При этом метод ShowDi al од возвращает значения Di al ogResul t .OK и DialogResult.Cancel соответ- ственно. Если получено значение Di al ogResul t. OK, то пользователь на- жал кнопку OK и в свойстве Filename содержится полный путь к вы- бранному файлу. Пример работы с элементами OpenFileDialog и SaveFileDialog при- веден в листинге 3.14. Листинг 3.14 private void butOpen_Click(object sender, EventArgs e) { ofd.Filter » "DLL|*.dll|Картинки|*.jpg"; продолжение &
50 Глава 3. Элементы управления Листинг 3.14 (продолжение) ofd. Initial Di rectory = "WMy DocumentsWTemplates"; if (DialogResult.OK •— ofd.ShowDialogO) { statusBarl.Text = ofd.FileName; } el se { statusBarl.Text = "Вы нажали на кнопку Отмена!": } } Элементы HScrollBar и VScrollBar Элементы управления HScrol 1 Ваг и VScrol 1 Ваг позволяют создавать полосы прокрутки для элементов, которые изначально не облада- ют этой функциональностью. Пользоваться этими полосами про- крутки совсем не сложно. Свойство Minimum задает значение эле- мента, когда ползунок находится в крайней левой или в крайней верхней позиции, для HScrollBar или VScrollBar соответственно. Свойство Maximum, задает максимальное значение для полос про- крутки. Значение свойства Vai ue зависит от положения ползунка. Оно всегда находится в диапазоне между значениями свойств Minimum и Maximum. Когда пользователь щелкает на полосе прокрутки, то свойство Value изменяется в соответствии со значением, заданным в свойстве LargeChange. Когда пользователь нажимает на кнопку навигации со стрелкой, то свойство Value изменяется в соответствии со значени- ем, заданным в свойстве Small Change. Следует обратить внимание на то, что если ползунок находится в положении, определяемом свойством Maximum, то свойство Value не равно значению Maximum. В этом случае значение свойства Value вычисляется по формуле Maximum - LargeChange + 1. При изменении свойства Vai ue инициируется событие Vai ueChanged. В листинге 3.15 приведен пример работы с полосами прокрутки. Листинг 3.15 private void vScrol!Barl_ValueChanged(object sender. EventArgs e) { this. 1ЫScroll .Text = this.vScrol 1 Bari.Vaiue.ToStringO; }
Элементы управления 51 На рис. 3.10 показан внешний вид приложения. Если переместить ползунок в нижнюю часть полосы прокрутки, то значение в соот- ветствии с формулой будет равно 91. Рис. 3.10. Пример работы с полосами прокрутки Список рисунков (ImageList) Элемент управления ImageLi st уже рассматривался при знакомстве с элементом Tool Ваг. Элемент ImageList используется для хранения коллекций растровых изображений. Как и многие другие элемен- ты, список рисунков не отображается во время выполнения про- граммы, а используется как контейнер, из которого по мере необхо- димости извлекаются хранимые изображения. Как правило, данный элемент используется совместно с такими элементами управления, как ListView, TreeView и Tool Ваг. Изображения можно добавлять в элемент управления во время ра- боты приложения. Для этого используется метод Add, который вхо- дит в состав члена класса Images. Сами картинки могут располагать- ся как в отдельных файлах, так и в ресурсах приложения. В листинге 3.16 показано, как можно добавить картинку из ресурсов в ImageLi st, а затем отобразить ее в элементе интерфейса PictureBox. Листинг 3.16 Bitmap image = new Bitmap(Assembly.GetExecutingAssembly(). GetMani festResourceStream(@"ImageLi st_CS.home.gi f")); imgList.Images.Add(image); picTest.Image = imgList.Images[O]; Изображение добавляется в начало списка, и его порядковый но- мер будет равен нулю. Если в ImageList уже было одно изображе-
52 Глава 3. Элементы управления ние, то новая картинка будет иметь порядковый номер, равный еди- нице. Это иллюстрируется листингом 317. Листинг 3.17 private void butFromImageList_Click(object sender. EventArgs e) { picTest.Image = imgList.Images[l]; } Все картинки, находящиеся в ImageList, имеют одинаковый размер. По умолчанию используется размер 16 х 16 пикселов. Разработчик может изменить размеры изображений, используя свойство ImageSi ze. Если менять отображаемые картинки при помощи таймера, то мож- но даже создать небольшую мультипликацию. Для этого достаточно список рисунков заполнить набором изображений, а затем поочеред- но отображать их в графическом поле. Элемент PictureBox Элемент управления PictureBox используется для отображения гра- фики. Данный элемент имеет ограниченную функциональность и не позволяет растягивать картинку в соответствии с размерами графи- ческого поля. В листинге 3.18 приведен фрагмент кода, который позволяет загру- зить изображение из графического файла. Листинг 3.18 private void butFromFile_Click(object sender, EventArgs e) { picTest.Image = new Bitmap(@"\Windows\banner.gif); Если использовать этот способ для добавления картинки, то нужно добавить изображение в проект и для свойства Build Action в окне свойств Properties задать значение Content. В процессе подготовки приложения к инсталляции изображение будет рассматриваться как часть программы. В рассмотренном примере использовалась готовая картинка, которая находится в папке Windows. Также можно загрузить изображение из ресурсов приложения. В этом случае надо добавить картинку в проект и для свойства Build Action задать значение Embedded Resource. Тогда не придется специально вклю- чать изображения в состав инсталлятора. В листинге 3.19 приведен пример, иллюстрирующий добавление изображения из ресурсов.
Элементы управления 53 Листинг 3.19 private void butRes Click(object sender, EventArgs e) { // Загружаем из ресурсов picTest.Image = new Bitmap(Assembly.GetExecutingAssembly(). GetMani festResourceStream( "Pi ctureBoxCS. kri sti na. jpg”)); } Элемент ImageList имеет свойство ImageSize, которое задает разме- ры хранимых изображений. Перед загрузкой картинки в графичес- кое поле можно установить требуемые размеры изображения с по- мощью данного свойства, как показано в листинге 3.20. Листинг 3.20 private void butImgList_Click(object sender, EventArgs e) { // изменяем размеры картинки imageListl.ImageSize = new System.Drawing.Size(160, 120); // загружаем картинку с измененными размерами picTest.Image = imageListl.Images[0J; } На рис. 3.11 показан внешний вид приложения, в котором для ра- боты с изображениями применяются все три описанных варианта. Рис. 3.11. Пример работы с элементом PictureBox Элемент ListView Элемент управления ListView похож на элемент ListBox, но вместо обычного текста данный элемент может показывать изображения. Фактически, правая часть рабочего окна Проводника в Windows ХР является типичным примером использования этого органа уп-
54 Глава 3. Элементы управления равления. Элементы в ListView могут отображаться в виде табли- цы, списка, а также как группа крупных и мелких значков. За спо- соб отображения содержимого отвечает свойство View. Значение Details позволяет отображать содержимое в виде таблицы, значе- ние List создает список, значение Largelcon позволяет отображать элементы списка в виде больших пиктограмм, а значение Smal 1 Icon отображает их как маленькие пиктограммы. В режиме Detai 1 s элемент управления LI stV1 ew позволяет создавать дополнительные столбцы. Их можно добавлять как во время про- ектирования, так и во время исполнения программы. Пример до- бавления столбцов во время работы приложения приведен в лис- тинге 3.21. Листинг 3.21 private void Forml_Load(object sender, EventArgs e) { // Устанавливаем нужный вид listViewl.View = View.Details; // Выделяем всю строку при выделении любого элемента listViewl.FullRowSelect = true; ColumnHeader columnHeaderl = new ColumnHeaderO; ColumnHeader columnheader2 = new ColumnHeaderO; ColumnHeader columnHeader3 - new ColumnHeaderO; columnHeaderl.Text я "Фамилия"; columnHeader2.Text - "Имя"; columnHeader3.Text = "E-mail": 1i stViewl.Columns.Add(columnHeaderl); 1i stViewl.Columns.Add(columnHeader2); 1i stVi ewl.Columns.Add(columnHeader3); ListViewItem Contactl = new ListViewItem("Иванов"); Contactl.SubItems. Add("Иван"); Contactl.SubItems.Add("i van@i vanov.ru"); ListViewItem Contact2 = new ListViewItemC"Петров"); Contact2.SubItems.Add("Петр"); Contact2.SubItems.Add(”peter@petrov.ru"): ListViewItem Contacts = new ListViewltem("Сидоров"); Contacts.SubItems.Add("Арнольд");
Элементы управления 55 Contacts.SubItems.Add("goat0sidorov.ru"); 1i stVi ewl.Iterns.Add(Contact1); 1i stViewl.Items.Add(Contact?): 1i stV1ewl.Iterns.Add(Contacts). } На рис. 3.12 показан внешний вид приложения со списком в виде таблицы. Рис. 3.12. Пример работы с элементом ListView В полной версии .NET Framework элемент управления Li stVi ew под- держивает свойство Multi Select, позволяющее одновременно выб- рать несколько элементов из списка. Версия .NET Compact Frame- work не поддерживает данное свойство, поэтому пользователь мо- жет выбрать только один элемент. Элемент TabControl Элемент управления TabControl очень удобен при создании интер- фейсов для устройств с малыми размерами экрана, так как он позво- ляет создавать многостраничные диалоговые окна. Вкладки, реали- зуемые этим элементом, имеют ярлычки, на которых отображаются заголовки страниц. И пользователь может легко переключаться меж- ду страничками, просто щелкая по этим ярлычкам. В устройствах Pocket PC вкладки располагаются в нижней части окна. Следует обратить внимание на то, что элемент TabControl все- гда располагается в верхнем левом углу контейнера. Например, если поместить TabControl на форму, то он появится в ее верхнем левом углу. Если же нужно изменить расположение этого элемента, то надо
56 Глава 3. Элементы управления поместить его на панель, которая является контейнером. При пере- мещении панели будет перемещаться и TabControl. Элемент TabControl следует расположить на форме. У него по умолча- нию будут созданы вкладки tabPagel и tabPage2. Если нужно добавить новую вкладку, то следует щелкнуть на маленькой стрелке в верхней части элемента TabControl и выбрать пункт меню Add Tab (рис. 3.13). Рис. 3.13. Добавление новой закладки в элементе TabControl В результате у элемента TabControl появится новая закладка, которую можно настроить в соответствии с потребностями разработчика. Так- же программист может воспользоваться услугами редактора TabPage Collection Editor для добавления новых закладок. В этом случае надо выбрать элемент TabControl в дизайнере формы, найти свойство TabPages и нажать кнопку редактирования этого свойства. В результате будет открыт редактор закладок. Для управления закладками можно также выделить TabControl, щелкнуть на нем правой кнопкой мыши и вы- брать пункты контекстного меню Add Tab или Remove Tab.
Элементы управления 57 Для определения текущей вкладки используется свойство Select- Index. При изменении данного свойства инициируется событие Sei ectedlndexChanged, что иллюстрирует код, приведенный в листин- ге 3.22. Листинг 3.22 private void tabControll_SelectedIndexChanged(object sender, EventArgs e) { swi tch (this.tabControl1.Seiectedlndex) { case 0: MessageBox.ShowCBbi выбрали первую вкладку"); break; case 1: MessageBox.ShowCBbi выбрали вторую вкладку"); break; case 2: MessageBox.ShowCBbi выбрали третью вкладку"); break; } } Элемент Tree View Элемент управления TreeVi ew позволяет представить данные в иерар- хическом виде. Именно в этом виде отображается структура дисковой системы в левой части рабочего окна программы Проводник Windows. Основой элемента TreeView являются объекты TreeNode и Nodes. При работе с TreeView также широко используется элемент управления ImageList, используемый как хранилище изображений для узлов. Заполнять древовидную структуру можно как на этапе конструи- рования формы, так и во время выполнения программы. Для созда- ния дерева в дизайнере формы нужно переместить на нее элемент ТreeV i ew. Затем следует выбрать свойство Nodes и запустить редак- тор TreeNode Editor. Кнопка Add Root отвечает за создание узлов де- рева. Кнопка Add Child позволяет добавить дочерний узел к выбран- ному узлу. Кнопка Delete удаляет выбранный узел. Чтобы задать текст, отображаемый в узлах, можно использовать свой- ство Text Также в узлах можно использовать изображения, для чего применяется комбинация элемента управления ImageLi st и свойства Sei ectedlmageLi st. Для определения текущего узла используется свой- ство SelectedNode.
58 Глава 3. Элементы управления На рис. 3.14 показан внешний вид приложения, использующего эле- мент интерфейса Т reeVi ew. Рис. 3.14. Использование элемента TreeView Элемент InputPanel Элемент управления InputPanel позволяет вводить текстовую ин- формацию при помощи виртуальной клавиатуры или панели рас- познавания знаков SIP (Soft Input Panel). Так как в полной версии .NET Framework данного элемента нет, то стоит рассмотреть его несколько подробнее. Как правило, в карманных компьютерах нет клавиатуры, поэтому для ввода данных используется виртуальная клавиатура. В обыч- ном состоянии она неактивна и находится в свернутом состоянии. Чтобы ее активировать, нужно щелкнуть стилусом по значку кла- виатуры в нижнем правом углу экрана, где располагается меню или панель инструментов Tool Ваг. Тогда виртуальная клавиатура по- явится на экране, и пользователь сможет вводить текст. Разработчик может программно управлять состоянием клавиатуры. Например, с помощью элемента InputPanel можно узнать текущее состояние SIP, возможность ее отображения и деактивации. Свойств и методов у элемента InputPanel не так уж много, но наиболее часто используемые члены класса приведены в следующем списке. □ Bounds — прямоугольник, определяющий размеры и позицию SIP. □ VI si Ы eDesktop — прямоугольная часть экрана, на которой не отображается SIP. □ Enabl ed — возможность работы с SIP. □ Enabl edChanged — событие, возникающее при изменении состоя- ния SIP.
Элементы управления 59 Свойства Bounds и Vi s 1Ы eDesktop доступны только для чтения и оп- ределяют прямоугольники, по которым можно судить о положении SIP и размерах клиентской области, не занятой SIP. Свойство Vi si bl eDesktop определяет прямоугольник, которым ограничена об- ласть экрана, не закрытая SIP. Когда виртуальная клавиатура ото- бражается, то условный прямоугольник поднимается от полоски навигации над окном SIP. Когда SIP скрыт, то прямоугольник за- нимает все пространство экрана. Свойство Bounds описывает размеры и позицию виртуальной клавиа- туры, когда она отображается на экране. Причем размеры этого пря- моугольника не меняются, даже если виртуальная клавиатура скрыта. Свойство Enabled имеет значение True, если виртуальная клавиату- ра отображается на экране. Значение свойства можно задавать про- граммно, скрывая или отображая клавиатуру. Если в приложении необходимо использовать элемент InputPanel, нуж- но следить, чтобы он при активации не загородил элементы управле- ния на форме, иначе пользователь просто не сможет ввести данные. Можно размещать текстовые поля на форме как можно выше, что- бы элемент InputPanel не мешал вводить данные. Но помимо этого можно отслеживать состояние виртуальной клавиатуры и при ее отображении передвигать вверх поля для ввода информации. В этом случае чаще всего приходится применять полосы прокрутки. Мож- но проиллюстрировать такое поведение примером, в котором эле- мент InputPanel будет активироваться, если текстовое поле полу- чит фокус. А когда TextBox потеряет фокус, то клавиатура должна исчезнуть. Эта функциональность реализуется кодом, приведенным в листинге 3.23. Листинг 3.23 private void txtTest_GotFocus(object sender, EventArgs e) { // Когда пользователь выбирает текстовое поле, // то автоматически активируем SIP inputPanell.Enabled = true: } private void txtTest_LostFocus(object sender, EventArgs e) { // При потере фокуса сворачиваем SIP inputPanell.Enabled - false;
60 Глава 3. Элементы управления В этом примере пришлось добавить на форму кнопку, иначе тек- стовое поле будет автоматически получать фокус при загрузке фор- мы и приложение не будет работать так, как это задумывалось из- начально. В данном случае именно кнопка будет получать фокус ввода при загрузке формы. Но для этого надо присвоить свойству кнопки Tab Index нулевое значение. Теперь если стилусом активировать текстовое поле, то автоматиче- ски будет отображена виртуальная клавиатура. Если на появившей- ся клавиатуре нажать клавишу Tab или щелкнуть на кнопке с надпи- сью Просто кнопка, то панель ввода автоматически свернется. Рис. 3.15. Пример работы с элементом InputPanel Элемент InputPanel поддерживает событие EnabledChanged, которое возникает при активации и деактивации панели ввода. С его помо- щью можно расширить функциональность примера. Следует доба- вить еще одно текстовое поле в нижнюю часть формы. Сейчас нужно отследить событие Enabl edChanged и отреагировать на него должным образом. При активации панели ввода текстовое поле должно сдви- нуться вверх, чтобы пользователь мог ввести свои данные. Пример обработки этого события приведен в листинге 3.24. Листинг 3.24 private void inputPanellEnabl edChanged (object sender, EventArgs e) { // Отслеживаем состояние панели ввода // Свойство Bounds возвращает размеры и позицию SIP if (inputPanell.Enabled = true)
Элементы управления 61 this.txtJump.Тор = 200 - InputPanel1.Bounds.Height; else this.txtJump.Top = 200: } На рис. 3.15 показан внешний вид окна тестового приложения. В этом примере позиция текстового поля была подобрана опытным путем, но в реальных проектах разработчик может программно вы- числить высоту формы, панели ввода, текстового поля и других элементов формы, чтобы более точно определить позицию сдвига. Элемент управления DataGrid Элемент управления DataGrid позволяет отображать данные в виде таблицы, как это сделано в электронной таблице MS Excel. Как и мно- гие другие элементы управления, он имеет обрезанные возможности по сравнению с полной версией .NET Framework. Например, отклю- чена поддержка свойства DataKember. Элемент управления DataGrid может быть связан с источниками данных при помощи свойства DataSource. Рассмотрим простейший пример работы с данным элементом. Прежде всего, потребуется создать XML-файл, содержащий некоторые данные. Для примера был использован файл artists.xml, в котором содержится информа- ция о некоторых известных артистов шоу-бизнеса. Файл содержит записи о фамилии, дате рождения и адресе проживания. Создан- ный файл нужно добавить в проект, расположить на форме элемент DataGrid и присвоить ему имя grdArtists. В листинге 3.25 приведен код обработчика события Forml Load. Листинг 3.25 private void Forml_Load(object sender, EventArgs e) { Cursor.Current = Cursors.WaitCursor; try { // Загружаем данные DataSet DataSet ds - new DataSetO; ds.ReadXml(@"\Program Files\DataGrid_CS\artists.xml"); grdArtists.DataSource = ds.Tables[0]; } catch (Exception) { продолжение &
62 Глава 3. Элементы управления Листинг 3.25 (продолжение) MessageBox.ShowCHe ногу загрузить данные в DataGridl", this.Text): } // Устанавливаем стили DataGridTableStyle ts = new DataGridTableStyleO; ts.MappingName - "Order": DataGridColumnStyle artistDate = new DataGridTextBoxColumn(); artistDate.MappingName = "BirthDate": artistDate.HeaderText = "Дата рождения": ts.Gri dColumnStyles.Add(arti stDate); DataGridColumnStyle artistName = new DataGridTextBoxColumn(): artistName.MappingName = "ArtistName": artistName.HeaderText = "Артист": artistName.Width = this.Width - artistDate.Width - 35: ts.Gri dColumnStyles.Add(arti stName): grdArtists.TableStyles.Add(ts); Cursor.Current - Cursors.Default: } В данном примере из XML-файла извлекаются данные, относящи- еся к фамилии артиста и дате его рождения. Остальная информа- ция игнорируется. При загрузке приложения в элемент DataGri d за- писываются требуемые данные. На рис. 3.16 показан внешний вид приложения. Рис. 3.16. Пример работы с элементом DataGrid
Элементы управления 63 Также стоит прочитать статью «Using the DataGrid Control in Pocket PC Applications», которую можно найти в справочной сис- теме MSDN. В этой статье иллюстрируются различные приемы про- граммирования, которые помогут расширить возможности данно- го элемента. Элемент Splitter Элемент управления Splitter появился только в .NET Compact Framework 2.0. В предыдущей версии его не было. Этот элемент реализует разделитель, который используется для изменения размеров закрепленных элементов управления во время выпол- нения программы. Элемент Splitter обычно используется вмес- те с элементами управления, содержащими данные переменной длины. Стоит рассмотреть работу данного элемента на конкретном при- мере. На форме следует расположить графическое поле Pi ctureBox и присвоить его свойству Dock значение Тор. Затем на форме надо расположить объект Splitter и его свойству Dock тоже присвоить значение Тор. Также следует расположить на форме текстовое поле TextBox. Его свойству Multiline надо присвоить значение True, а свойству Dock — значение Fill. Внешний вид получившегося при- ложения показан на рис. 3.17. Рис. 3.17. Применение элемента Splitter в приложении Даже без единой строчки написанного кода запущенное прило- жение будет вполне функционально. Если нужно увеличить об- ласть текстового поля для ввода новых данных, то достаточно на- жать стилусом на разделителе и передвинуть его чуть выше.
64 Глава 3. Элементы управления Элемент MonthCalendar Элемент управления MonthCalendar появился только в последней версии .NET Compact Framework 2.0. Данный элемент позволяет легко выбрать необходимую дату. Для создания тестового приложения на форме надо разместить эле- менты MonthCalendar и Label. Метка должна получить имя 1Ы Sei ectDate, а для свойства Text нужно задать значение Выбранная дата. Затем следу- ет дважды щелкнуть на элементе monthCalendarl, чтобы задать код об- работчика события DateChanged. Этот код приведен в листинге 3.26. Листинг 3.26 private void monthCalendarl_DateChanged(object sender. DateRangeEventArgs e) { 1blSeiectDate4Text = "Выбранная дата: " + monthCalendarl.Seiecti onStart.ToShortDateStringC); } Теперь можно запустить приложение и выбрать любую дату из ме- сячного календаря. Выбранная дата будет автоматически отобра- жаться в надписи 1Ы Sei ectDate, как показано на рис. 3.18. , "vinthi denda ltte2b Q fl n xiiy 2006 а S М Т W Т Г S эз гс г? 2е эт эо i 2 3 4 5 6 7 8 Я 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 ИН 28 29 30 31 1 2 3 4 5 Today: 5/16/06 Выбрамая дате 7/27/06 Рис. 3.18. Выбираем дату из месячного календаря Элемент DateTimePicker Элемент управления DateTimePicker также является новым элемен- том, который появился в .NET Compact Framework 2.0. Он позво- ляет выбирать не только дату, но и время. На форме надо разместить элемент DateT imePicker и две текстовые метки Label, в которых будут отображаться дата и время. Затем нуж-
Элементы управления 65 но дважды щелкнуть на элементе DateTimePicker, чтобы задать код обработчика события Vai ueChanged. Этот код приведен в листинге 3.27. Листинг 3.27 private void dateTimePickerl_ValueChanged(object sender. EventArgs e) { 1 bl Date.Text = "Дата: " + dateTimePi ckerl.Vaiue.ToShortDateStri ng(): IblTime.Text = "Время: " + dateTimePickerl.Vaiue.ToShortTimeString(); } Внешний вид получившегося приложения показан на рис. 3.19. . mePI пег 0 Tj << I®31 ф Рис. 3.19. Выбор даты при помощи элемента DateTimePicker В документации MSDN есть небольшая статья «How to: Use the DateTimePicker Class in the .NET Compact Framework», в которой приводится небольшой пример использования этого элемента. Под- держка данного элемента появилась и в смартфонах под управле- нием Windows Mobile 5.0. Но в этом случае внешний вид элемента будет несколько иным. Элемент DocumentList Новый элемент управления DocumentList, который появился в .NET Compact Framework 2.0, может заменить такие элементы, как SaveFi 1 eDi al од и OpenFi 1 eDialog, так как имеет все необходимые сред- ства для работы с файлами. Помимо этого элемент DocumentLi st име- ет дополнительные возможности, которые наверняка понравятся разработчикам программ. Он позволяет очень просто реализовать 3-2873
66 Глава 3. Элементы управления основные задачи манипулирования файлами, к которым относятся копирование, удаление, переименование и перемещение файлов. С помощью этого элемента также можно сортировать файлы по име- ни, дате создания, размеру. Кроме того, существует даже возмож- ность посылать файлы по электронной почте или передавать на другое устройство при помощи инфракрасной связи. Элемент DocumentLi st работает с файлами в пределах папки Му Documents, включая подпапки. Следует обратить внимание на то, что DocumentLi st является классом из пространства Mi crosoft. Wi г .dowsCE. Forms и не является частью полной версии .NET Framework. Поэтому есть смысл поближе познакомиться с данным элементом. Для разработки тестового приложения сначала потребуется создать новый проект, а затем переместить на форму элемент DocumentLi st. Для свойства Name надо задать значение DocListFile, свойство Dock должно получить значение Тор, свойству Heigth присваивается зна- чение 160, а для свойства Sei ectedDi rectory задается значение Му Documents. Также на форме надо разместить элементы ComboBox и StatusBar. Элементу ComboBox надо присвоить имя cboFileType. Затем следует выбрать свойство Items и открыть окно редактора String Collection Editor. Для списка надо задать значения BMP и WAV. Затем нужно дважды щелкнуть на элементе ComboBox, чтобы задать код обработчика события SelectedlndexChanged. Код обработчика приведен в листинге 3.28. Листинг 3.28 private void cboFileType_SelectedIndexChanged(object sender, EventArgs e) { if (cboFileType.Text = "BMP") { docListFile.Filter = "Рисунки (*.bmp)|*.bmp"; docListFile.SeiectedDirectory = "My Pictures": } el se { docListFile.Filter » "Звуки (*.wav)|*.wav"; docListFile.SeiectedDirectory = "My Music": } }
Элементы управления 67 Данный код динамически меняет значение свойства Fi 1 ter элемен- та DocumentList для отображения файлов определенного типа. Так- же меняется папка просмотра файлов. Если пользователь выберет расширение .BMP, то следует выбрать папку Му Pictures, специально предназначенную для хранения картинок. При выборе типа фай- лов .WAV выбирается папка Му Music. Теперь следует дважды щелкнуть на элементе DocumentList, чтобы создать обработчик события DocumentActi vated. Соответствующий код приведен в листинге 3.29. Листинг 3.29 private void docListFile_DocumentActivated(object sender, Mi crosoft.Wi ndowsCE.Forms.DocumentLi stEventArgs e) { statusBarl.Text s e.Path; 11 работа с выбранным файлом } Перед началом тестирования стоит скопировать несколько соответ- ствующих файлов в папки Му Pictures и Му Music. После запуска про- граммы нужно перейти в поле со списком и выбрать тип файлов. После этого будет активирован элемент DocumentList с выбранной папкой. Из списка документов можно будет выбрать конкретный файл. Следует обратить внимание на то, что выбранный файл имеет кон- текстное меню при помощи которого можно выполнять базовые операции с файлом (рис. 3.20). Путь к выбранному файлу отобра- жается в строке состояния. Рис. 3.20. Выбор файла при помощи элемента DocumentList з*
68 Глава 3. Элементы управления Элемент Notification Еще один новый элемент управления, который появился в послед- ней версии .NET Compact Framework 2.0, носит имя Notification. Данный элемент управления позволяет отображать интерактив- ные сообщения. В документации по данному элементу приводит- ся довольно интересный пример с использованием HTML-текста. Но в книге можно ограничиться более наглядным примером. Для создания тестового примера нужно переместить на форму эле- менты Notification и Button. При нажатии на кнопку необходимо отобразить соответствующее сообщение. Это реализуется при по- мощи кода, приведенного в листинге 3.30. Листинг 3.30 private void buttonl_Click(object sender, EventArgs e) { notification!.Text = "Позвони родителям!”: notification!.Caption = "Демонстрация примера"; notification!.Critical = true; // Уведомление висит на экране 10 секунд notification!.InitialDuration = 10; notification!.Visible = true; } На рис. 3.21 показано сообщение, которое будет отображаться на экране КПК в течение 10 секунд. Nouitcaton ft 13 MS11;20 Ф Рис. 3.21. Вывод сообщения с помощью элемента Notification ПРИМЕЧАНИЕ---------------------------------------- Элемент Notification применяется только в приложениях для кар- манных компьютеров. Смартфоны его не поддерживают.
Элементы управления 69 Элемент HardwareButton На карманных компьютерах кроме клавиш навигации присутствуют также дополнительные кнопки, при помощи которых активируются часто запускаемые приложения. Как правило, в состав программного обеспечения КПК входит утилита, с помощью которой можно назна- чить каждой из этих кнопок определенные команды. Но можно пред- ставить ситуацию, когда для создаваемой игры нужно, чтобы управ- ление осуществлялось с помощью этих кнопок. Тогда необходимо переопределить на время поведение кнопок в вашем приложении. И сделать это можно с помощью элемента HardwareButton, который по- явился в .NET Compact Framework 2.0 Следует рассмотреть пример использования этого нового элемента. Прежде всего нужно создать новый проект и поместить на панели Component tray два элемента HardwareButton с именами hrdLeftRotate и hrdRightRotate. Для каждой переопределяемой кнопки необходимо создать свой экземпляр элемента HardwareButton. В рассматриваемом примере будут переопределяться вторая и третья кнопки. Также на форме надо разместить графическое поле PictureBox. В него надо загрузить любое изображение и растянуть картинку таким обра- зом, чтобы она заняла верхнюю половину экрана. Изображение надо пристыковать к верхней части формы. Для этого свойству Dock при- сваивается значение Тор. Также на форме надо разместить надпись Label, при помощи которой будут отображаться подсказки. Надпись следует пристыковать к нижней части формы. Для этого свойству Dock присваивается значение Bottom. У обоих добавленных элементов HardwareButton нужно отыскать свойство Associ atedControl и задать зна- чение Forml. Также надо изменить значения свойств HardwareKey. Для первого элемента применяется значение Appl icationKey2, что соответ- ствует второй кнопке. Для второго элемента задается значение Appl i cat i опКеуЗ, что соответствует третьей кнопке под экраном. Теперь, когда все необходимые свойства установлены, нужно написать код для события Forml KeyUp. Код приведен в листинге 3.31. Листинг 3.31 private void Forml_Load(object sender, EventArgs e) { label 1.Text = "Нажните вторую кнопку для поворота экрана на 90 градусов": } private void Forml_KeyUp(object sender, KeyEventArgs e) { продолжение &
70 Глава 3. Элементы управления Листинг 3.31 (продолжение) switch ((HardwareKeys)e.KeyCode) { case HardwareKeys.Applicati onKey2: if (SystemSettings.ScreenOrientation — ScreenOri entati on.AngleO) { SystemSettings.ScreenOri entati on = ScreenOri entati on.Angle90; label1.Text - "Нажмите третью кнопку для поворота экрана в первоначальную позицию”; } break; case HardwareKeys.Appli cati опКеуЗ: if (SystemSettings.ScreenOrientation — ScreenOri entati on.Angle90) { SystemSetti ngs.ScreenOrientation « ScreenOrientation.AngleO; label1.Text = "Нажмите вторую кнопку для поворота экрана на 90 градусов"; } break; default: break: } Рис. 3.22. Поворот экрана при помощи аппаратных кнопок
Элементы управления 71 Запустите программу и попытайтесь нажимать по очереди на вто- рую и третью кнопки под экраном карманного компьютера. Резуль- тат показан на рис. 3.22. ПРИМЕЧАНИЕ------------------------------------------ В документации говорится, что различные модели КПК имеют раз- личное число кнопок, причем не все из них поддерживаются на систем- ном уровне. Например, Windows Mobile2003для Pocket PC поддержива- ет четыре кнопки, a Windows Mobile 5.0 для Pocket PC поддерживает пять кнопок. При этом класс HardwareButton не поддерживается смартфонами и другими устройствами на базе Windows СЕ, которые не является устройствами Pocket PC.
Глава 4 Улучшаем элементы управления В каждой новой версии Visual Studio .NET разработчики из Microsoft добавляют новые элементы управления, а также улучша- ют функциональность уже существующих элементов. Особенно это заметно на примере .NET Compact Framework. Уже простое срав- нение имеющихся элементов управления в версиях 1.0 и 2.0 пока- зывает, как много было добавлено новых элементов управления. Но, тем не менее, Microsoft не может создать элементы на все случаи жизни. Поэтому программистам иногда приходится создавать соб- ственные элементы. Также для улучшения существующих элемен- тов программисты прибегают к различным трюкам и хитростям. В этой главе будут рассмотрены некоторые приемы, которые, воз- можно, пригодятся в вашей практике. Текстовые поля Текстовые поля довольно часто используются в приложениях. В принципе, они достойно справляются с поставленными задача- ми, имея необходимую функциональность. Предположим, что на форме расположены несколько текстовых полей для ввода инфор- мации. Для улучшения удобства использования применяется сле- дующий трюк: после того как пользователь ввел необходимые дан- ные в текстовом поле и нажал клавишу Enter, фокус переходит к следующему текстовому полю. Код, реализующий подобный меха- низм работы, приведен в листинге 4.1. Листинг 4.1 private void textBoxl KeyUp(object sender, KeyEventArgs e) { if (e.KeyCode == Keys.Enter) textBox2.Focus(); } private void textBox2_KeyUp(object sender, KeyEventArgs e) {
Управление полосой прокрутки 73 if (е.KeyCode == Keys.Enter) textBox3.Focus(); } private void textBox3_KeyUp(object sender, KeyEventArgs e) { if (e.KeyCode “ Keys.Enter) textBoxl.FocusO; } Управление полосой прокрутки При отображении большого текста пользователь может применять полосу прокрутки для перемещения по тексту. Разработчик может использовать сообщение WMVScroll для программного управления полосой прокрутки. Например, можно использовать этот механизм для создания эффекта автоматической прокрутки текста. Для иллюстрации примера нужно расположить на форме текстовое поле и отобразить в нем какой-нибудь длинный текст. В примере ис- пользуется отрывок из произведения А. Пушкина «Дубровский». Так- же на форме надо расположить четыре кнопки, при помощи которых пользователь сможет управлять отображением текста, прокручивая его на одну строчку или страницу вниз и вверх. В листинге 4.2 приведен код, который реализует описанный способ отображения текста. Листинг 4.2 [Dll Import("coredl1.dlГ)] extern static int SendMessage(IntPtr hWnd, uint Msg, int wParam, int 1 Param); /// <summary> /// Сообщение Windows для работы с полосой прокрутки /// </summary> const int WM VSCROLL = 0x115; // константы для сообщения WM VSCROLL const int SBLINEUP = 0; const int SBLINEDOWN “ 1; const int SBPAGEUP = 2; const int SBPAGEDOWN - 3; private void Forml_Load(object sender, EventArgs e) продолжение
74 Глава 4. Улучшаем элементы управления Листинг 4.2 (продолжение) И Отрывок из повести А.С.Пушкина "Дубровский" txtBook.Text = ©"Несколько лет тону назад в одной из своих поместий жил старинный русский барин, Кирила Петрович Троекуров. Его богатство, знатный род и связи давали ему большой вес в губерниях, где находилось его имение. Соседи рады были угождать малейшим его прихотям; губернские чиновники трепетали при его имени; Кирила Петрович принимал знаки подобострастия как надлежащую дань; дом его всегда был полон гостями, готовыми тешить его барскую праздность, разделяя шумные, а иногда и буйные его увеселения".; } private void butUp_Click(object sender, EventArgs e) { // на одну строчку вверх SendMessage(txtBook.Handle. WMVSCROLL, SB LINEUP, 0); } private void butDown_Click(object sender. EventArgs e) { // на одну строчку вниз SendMessage(txtBook.Handle, WM VSCROLL. SBLINEDOWN, 0); } private void butPageUp Click(object sender. EventArgs e) { // на одну страницу вверх SendMessageCtxtBook.Handle. WM_VSCROLL, SB_PAGEUP. 0); } private void butPageDown_Click(object sender, EventArgs e) { // на одну страницу вниз SendMessageCtxtBook.Handle, WM VSCROLL, SB PAGEDOWN, 0); } Внешний вид приложения показан на рис. 4.1. Многострочный текст в кнопке По умолчанию текст для кнопок может содержать только одну стро- ку. Но при желании можно изменить этот стиль с помощью функ- ций GetWi ndowLong и SetW 1 ndowLong, как показано в листинге 4.3.
Многострочный текст в кнопке 75 Рис. 4.1. Программная прокрутка текста Листинг 4.3 [DI1 Import("coredll.dll")] private static extern IntPtr GetCaptureO; [DI1 Import!"coredl1,dlГ)] private static extern int GetWindowLong!IntPtr hWnd, int nlndex); [Dll Import!"coredll.dll")] private static extern int SetWindowLong!IntPtr hWnd, int nlndex, int dwNewLong); public const int GWLSTYLE = -16: // стиль многострочного текста public const int BS_MULTILINE = 0x2000: private void FormlLoad I object sender, EventArgs e) { IntPtr hWnd: int style: this.butMultiline.Capture = true: hWnd = GetCaptureO; this.butMultiline.Capture = false; style = GetWindowLongihWnd, GWL STYLE); SetWindowLongIhWnd, GWL STYLE, style | BSMULTILINE); } В этом примере для сравнения использовались две кнопки. На каж- дой из них размещен достаточно длинный текст (рис. 4.2).
76 Глава 4. Улучшаем элементы управления Рис. 4.2. Вид кнопок в процессе программирования При загрузке формы выполняется изменение стиля для первой кнопки butMultl 1 ine, а вторая кнопка остается без изменений. Пос- ле запуска приложения можно заметить, что длинный текст в пер- вой кнопке разбивается на две строки и полностью умещается в границах кнопки. Во второй кнопке слова обрезаются, и текст про- сто нельзя прочитать (рис. 4.3). Рис. 4.3. Создание многострочного текста на кнопке
Увеличение ширины выпадающего списка ComboBox 77 ВНИМАНИЕ--------------------------------------------------- Данный пример был написан еще для .NET Compact Framework 1.0. В .NET Compact Framework 2.0 нет надобности вызывать функцию GetCaptureQ для получения дескриптора hWnd, так как теперь поддерживается свойство Control.Han.dle. Увеличение ширины выпадающего списка ComboBox Выпадающий список у комбинированного окна равен ширине само- го комбинированного окна ComboBox. Но можно обойти это ограниче- ние с помощью неуправляемого кода, как показано в листинге 4.4. Листинг 4.4 /// <summary> /// Сообщение, получающее размеры выпадающего списка /// комбинированного окна /// </summary> const int CBGETDROPPEDWIDTH = 0x015f; /// <summary> /// Сообщение, устанавливающее размеры выпадающего списка /// комбинированного окна /// </summary* const int СВ SETDROPPEDWIDTH - 0x0160: [DI1 Import("coredl1.dll")] static extern int SendMessagedntPtr hwnd, int msg, int wParam, int 1 Param); private void Forml_Load(object sender, EventArgs e) { comboBoxl.Items.Add("Раз"): comboBoxl.Items.Add("Два"): comboBoxl.Items.Add("Три"); comboBox2.Items.AddC"Длинный текст"); comboBox2.Items.Add("Очень длинный текст"); comboBox2.Items.Add("Hy очень длинный текст"): // Устанавливаем желаемую ширину SendMessage(comboBox2.Handle. CB_SETDROPPEDWIDTH, 200, 0): // Получим ширину выпадающего окна продолжение &
78 Глава 4. Улучшаем элементы управления Листинг 4.4 (продолжение) int retval = SendMessage(comboBox2.Handle, CBGETDROPPEDWIDTH, 0. 0); this.Text - retval.ToStringO: } На форме надо разместить два элемента ComboBox. Один из них бу- дет стандартным. А второй элемент обработает сообщение CB_SETDROPPEDWIDTH со значением второго параметра 200. В резуль- тате выпадающий список будет в ширину занимать 200 пикселов. После запуска программы сначала надо обратить внимание на ра- боту первого комбинированного окна (рис. 4.4) Оно ведет себя стандартным образом. отЬобом ft Q Yi *<« чаа О Раз два Три Рис. 4.4. Стандартный размер выпадающего списка Теперь нужно перейти ко второму комбинированному окну. У него размер выпадающего списка увеличился, что позволяет увидеть весь текст (рис. 4.5). Рис. 4.5. Увеличенный размер выпадающего списка у ComboBox
ListBox 79 ListBox Элемент ListBox имеет множество возможностей, которые пока не реализованы в рамках платформы .NET Compact Framework. В ча- стности, данный элемент не позволяет осуществлять поиск элемен- тов по первым символам. Но для решения этой задачи можно ис- пользовать сообщение LB FINDSTRING. Чтобы создать тестовое приложение, нужно добавить на форму спи- сок Li stBox и текстовое поле TextBox. Также потребуется ввести код, приведенный в листинге 4.5. Листинг 4.5 const int LB FINDSTRING = 0x018F; const int LBFINDSTRINGEXACT = 0x01A2; [DI1 Import!"coredll.dll")] static extern int SendMessageUntPtr hwnd, int msg, int wParam, string IParam): private void textBoxlTextChangedlobject sender, EventArgs e) { //поиск строки по вводимым символам HstBoxl. Selectedlndex = SendMessageUistBoxl. Handle. LBFINDSTRING. -1, textBoxl.Text); } private void Forml_Load(object sender. EventArgs e) { 1i stBoxl.Iterns.Add("bank"); 1i stBoxl.Iterns.Add("banana"); 1i stBoxl.Items.Add("bal1“); 1i stBoxl.Items.Add("bounty"); 1i stBoxl.Iterns.Add("bar"); } После запуска проекта можно попробовать ввести в текстовом поле любое слово. Если в списке есть слова, начинающиеся с введенных символов, то они начнут выделяться в списке. Например, можно сначала ввести символ Ь, затем а и, наконец, L. Сначала будет выде- лено слово bank, а после третьего введенного символа выделение перейдет на слово ball. Существует также сообщение LB_FINDSTRINGEXACT, которое осуществ- ляет поиск по целому слову без учета регистра. Имеет смысл при-
80 Глава 4. Улучшаем элементы управления менять его, когда список содержит сотни записей и отыскивание нуж- ного слова становится утомительным занятием. Чтобы показать при- менение этого сообщения, нужно добавить в предыдущий пример до- полнительную кнопку и ввести код, приведенный в листинге 4.6 Листинг 4.6 private void buttonl_Click(object sender, EventArgs e) { listBoxl.Selectedlndex = SendMessageUistBoxl. Handle, LBFINDSTRINGEXACT, -1, ’'ball"); } ListView Возможно, вы замечали, что в некоторых программах используется элемент ListView с градиентной заливкой. Например, такое оформ- ление интерфейса можно увидеть в списке контактов. Оказывается, совсем не сложно применить такую раскраску в своем приложении. Но для этого надо использовать стиль LVS GRADIENT, как показано в листинге 4.7. Листинг 4.7 usi ng System.Runt ime.InteropServi ces; [Dll Import("coredl1.dll")] static extern int SendMessageUntPtr hwnd, uint msg, int wParam, int 1 Param); const int LVSEXGRADIENT = 0x20000000; const int LVM SETEXTENDEDLISTVIEWSTYLE = 0x1000 + 54; // Создаем градиентный фон для ListView private void CreateGradientListView(ListView listView) { // Получим дескриптор ListView IntPtr hLV = listView.Handle; // Устанавливаем расширенный стиль SendMessageChLV, (uint)LVMSETEXTENDEDLISTVIEWSTYLE, 0, LVS_EX_GRADIENT); // Обновляем вид
Текстовое поле для ввода чисел 81 listView.RefreshO: } private void Forml_Load(object sender, EventArgs e) { CreateGradi entLi stVi ew(1i stVi ewl); } Создание кнопки, содержащей изображение В статье «How to: Create a Custom Image Button Control», кото- рую можно отыскать по адресу msdn2.microsoft.com/en-us/Library/ msl72532(VS.80).aspx, описывается процесс создания кнопки, ко- торая может содержать в качестве фона любое изображение. В пер- вых версиях .NET Compact Framework кнопку вообще нельзя было сделать цветной, так как не существовало свойства BackColor. Потом данный недостаток был исправлен, но стандартными средствами пока не получится отобразить на кнопке рисунок. С помощью примера, который приводится в статье, можно обойти это ограничение. Список с расширенными возможностями В другой статье — «How to: Create an Owner-Drawn List Box», расположенной по адресу msdn2.microsoft.com/en-us/library/ ms229679(VS.80).aspx, — описывается создание списка ListBox с расширенными возможностями. В этом примере показано, как можно использовать графику и различные шрифты для отобра- жения элементов списка. Текстовое поле для ввода чисел Очень часто программистам необходимо создавать текстовые поля, в которых можно указывать только числа. В статье «How to: Create a Numeric Text Box», которую можно найти по адресу msdn2.microsoft.com/en-us/library/ms229644(VS.80).aspx, рассказыва- ется об одном из таких способов. Прочитав статью, вы поймете, как создавать текстовые поля, допускающие ввод только чисел, минуса, пробела и разделителя десятичных чисел.
82 Глава 4. Улучшаем элементы управления Сортировка элементов ListView Элемент управления Li stVт ew в .NET Compact Framework не поддер- живает метод Sort, который позволил бы отсортировать элементы списка. В документации MSDN есть статья «How to: Sort ListView Items», в которой рассказывается о том, как решить эту проблему при помощи метода Sort класса ArrayLi st и интерфейса I Comparer. Использование элемента DateTimePicker Элемент управления DateTimePicker появился только в последней вер- сии .NET Compact Framework 2.0. В документации MSDN есть ряд замечательных статей о том, как создать собственный элемент DateTi mePi cker для программ, работающих на платформе .NET Compact Framework 1.0. Стоит ознакомиться со статьями «Adding Designer Support to the .NET Compact Framework DateTimePicker Control» и «Microsoft .NET Compact Framework-based DateTimePicker Control».
ТГП F“ Глава 5 Мышь и клавиатура Мышь и стилус Взаимодействие с программой пользователь осуществляет с помо- щью стилуса или аппаратных кнопок на самом устройстве. А где же мышь и клавиатура? Ну, предположим, что клавиатуру можно за- менить ее виртуальным аналогом на экране КПК. Она имеет прак- тически ту же функциональность, что и настоящая клавиатура. Кро- ме того, следует помнить, что существуют КПК с настоящей, хоть и маленькой, клавиатурой. Что же касается мыши, то ее роль с ус- пехом выполняет стилус. Но стоит заметить, что у стилуса нет возможности эмулировать на- жатие правой кнопки мыши. Когда пользователь применяет сти- лус, то генерируются события MouseDown, MouseMove, MouseUp и Click. Первые три события могут сообщить о позиции курсора, как и со- бытия из настольной версии Windows. Курсоры Так как пользователь при работе использует стилус, то Windows Mobile не отображает на экране устройства стандартную стрелку курсора. Предполагается, что пользователь может самостоятельно попасть острым концом стилуса в маленькую кнопку или другой элемент. Но у мобильных систем курсоры все же есть. Первый из них является аналогом песочных часов в настольной версии Windows и выглядит как анимированный круг с разноцветными секторами. Второй курсор можно увидеть при вызове контекстно- го меню. Он выглядит как множество маленьких красных кружков, которые постепенно появляются вдоль воображаемой окружности. Песочные часы При выполнении длительных ресурсоемких операций нужно по- казать пользователю, что устройство работает, а не зависло. Луч-
84 Глава 5. Мышь и клавиатура ше всего вывести на экран устройства курсор ожидания. В кар- манных компьютерах в качестве такого курсора испол ьзуются не песочные часы, как в настольных компьютерах, а анимированный разноцветный круг. Установить данный тип курсора в приложе- нии очень просто, что иллюстрирует фрагмент кода, приведенный в листинге 5.1. Листинг 5.1 // Устанавливаем курсор ожидания Cursor.Current = Cursors.WaitCursor; // возвращаем курсор по умолчанию Cursor.Current = Cursors.Default; На рис. 5.1 показано приложение с соответствующим курсором. Рис. 5.1. Отображение курсора ожидания Обработка события Tap-and-Hold Так как в карманных компьютерах не используется правая кнопка мыши, то для вызова контекстного меню используется операция Tap-and-Hold. Пользователь касается стилусом экрана и некоторое время удерживает нажатие. Если элемент, на поверхности которо- го находится стилус, связан с элементом ContexMenu, то на экране появится контекстное меню. А что делать, если мы хотим создать собственный обработчик события Tap-and-Hold? В этом случае надо добавить в проект таймер и написать код для обработки собы- тий Mouse_Down, MouseJJp и timerl_Tick. Для таймера следует задать интервал, необходимый для инициирования события. Сам код при- веден в листинге 5.2.
Клавиатура 85 Листинг 5.2 private void Forml MouseDown(object sender, MouseEventArgs e) { // включаем таймер timerl.Enabled = true; } private void Forml_MouseUp(object sender, MouseEventArgs e) { timerl.Enabled = false; labell.Text - } private void timerl_Tick(object sender, EventArgs e) { labell.Text = "Вы нажали на экран"; } Клавиатура На большинстве карманных ком- пьютеров нет стандартной клавиа- туры, поэтому ввод текста осуществ- ляется с помощью виртуальной кла- виатуры SIP. В Visual Studio 2005 клавиатура SIP представлена эле- ментом InputPanel. Но в послед- нее время стали появляться устрой- ства с настоящей встроенной клави- атурой. Как правило, эти устройства имеют квадратный экран. Среда раз- работки поддерживает эмуляторы подобных моделей (рис. 5.2). Эти эмуляторы в своем названии содер- жат слово «Square». Кроме того, на устройствах имеют- ся клавиши навигации, клавиша Enter и кнопки запуска определен- ных приложений. Все эти клавиши могут обрабатывать стандартные со- бытия. Рис. 5.2. Эмулятор устройства с клавиатурой
86 Глава 5. Мышь и клавиат ура Клавиши навигации Если вы в процессе создания приложения в режиме работы с фор- мой щелкнуть мышью на любой из кнопок навигации, то среда раз- работки сгенерирует код для этих кнопок в событии Form KeyDown. В листинге 5.3 приведен пример обработчика этого события. Листинг 5.3 private void Forml KeyDown(object sender, KeyEventArgs e) { if ((e.KeyCode — System.Windows.Forms.Keys.Up)) { label1.Text = "Клавиша Вверх"; } if ((e.KeyCode = System.Windows.Forms.Keys.Down)) { label1.Text = "Клавиша Вниз"; } if ((e.KeyCode = System.Windows.Forms.Keys.Left)) { labell.Text = "Клавиша Влево"; } if ((e.KeyCode = System.Windows.Forms.Keys.Right)) { labell.Text = "Клавиша Вправо"; } if ((e.KeyCode =- System.Windows.Forms.Keys.Enter)) { labell.Text = "Клавиша Enter"; } } Рис. 5.3. Обработка нажатий клавиш навигации
Выключение устройства 87 Как видите, приложение определяет нажатую клавишу при помо- щи перечисления System. Wi ndows. Forms. Keys. Если открыть виртуаль- ную клавиатуру и нажать на клавиши со стрелками, то можно убе- диться, что они тоже инициируют событие FormJCeyDown (рис. 5.3). Если протестировать пример на устройстве с настоящей клавиату- рой, то можно заметить, что приложение правильно обрабатывает нажатие на встроенные клавиши со стрелками. Выключение устройства На карманных компьютерах также есть кнопка выключения уст- ройства. На самом деле при нажатии на эту кнопку устройство не выключается, а переходит в особый спящий режим. В мобильных устройствах программы и данные хранятся в памяти, и если уст- ройство действительно выключить, то все приложения и данные просто пропадут. Разработчик может программно перевести уст- ройство в спящий режим, имитируя нажатие этой кнопки выклю- чения с помощью функции API keybd_event, как показано в листинге 5.4. Листинг 5.4 /// <summary> /// Функция имитирует нажатия клавиш на клавиатуре /// </summary> /// <param пате=”ЬУк">Виртуальный код клавиши для имитации /// нажатия и отпускания клавиши</рагаш> /// <рагаш пате="Ь5сап">Зарезервировано - установлено в /// 0</рагат> /// <param name="dwFlags">0Har</param> /// <param name="dwExtraInfo">flononHHTenbHaB информация</рагаш> [DI1Import!"coredll.dll", CharSet = CharSet.Unicode)] public static extern void keybd_event(byte bVk, byte bScan, int dwFlags, int dwExtralnfo); // константа для кнопки выключения устройства public const int VK OEM 8 = OxDF; private void butOff_Click(object sender, EventArgs e) { // Имитируем нажатие кнопку выключения устройства keybd_event(VK_0EM_8, 0. 0. 0): }
88 Глава 5. Мышь и клавиатура Дополнительные материалы Если вы хотите узнать о клавиатуре еще больше, то стоит обратить внимание на блог Алекса Яхнина, который можно найти по адресу bLog.opennetcf.org/ayakhnin. Там можно найти статью «Keyboard hook in the CF v2» В данной статье рассказывается о перехвате всех сооб- щений, которые посылаются при нажатии любых кнопок устройства. Также может быть полезна статья «Custom SIP Control for CF». Автор статьи предлагает собственную реализацию элемента Inputcontrol, который содержит свою виртуальную клавиатуру. Этот пример мо- жет пригодиться при создании приложения, в котором не использует- ся стандартная панель ввода SIP, но при этом пользователь должен иметь возможность ввода информации.
Глава 6 Графика Классы д ля программирования графики Программирование графики в .NET Compact Framework опирает- ся на те же базовые приемы, что и работа с графикой для полной версии .NET Framework. Все основные классы для работы с графи- кой сосредоточены в пространстве имен System. Drawl ng. С помощью этих классов можно рисовать всевозможные фигуры, отображать линии, работать с изображениями и даже манипулировать текстом. В качестве своеобразного холста для графических опытов можно использовать поверхность формы или элементов управления. Са- мым главным классом является класс Graphics, который предостав- ляет поверхность и методы для вывода графики. Также широко ис- пользуются в графике такие классы, как Pen, Brush, Color, Rectangle, Line, Image. Класс Pen Класс Pen используется для создания пера, при помощи которого проводятся прямые и кривые линии. В отличие от полной версии .NET Framework, поддерживающей четыре перегруженных версии кон- структора Pen, .NET Compact Framework позволяет создавать перо только с помощью двух конструкторов. При вызове метода Pen (Color) создается перо указанного цвета. Конструктор Pen (Col or. Si ng 1 е) по- зволяет создавать перо указанных цвета и ширины. Но второй вари- ант поддерживается только в .NET Compact Framework 2.0. В листинге 6.1 приведен пример создания перьев синего и красного цветов. Затем при помощи перьев создаются две линии. Листинг 6.1 private void Forml Раint(object sender, PaintEventArgs e) { Graphics g = e.Graphics; // Синее перо толщиной 1 пиксел Pen bluePen = new Pen(Color Blue): // Красное перо толщиной 5 пикселов продолжение &
90 Глава 6. Графика Листинг 6.1 (продолжение) Pen redFatPen = new Pen(Color.Red. 5); g.DrawLine(bluePen, 10. 10. 230, 10); g.DrawLine(redFatPen, 10, 20, 230. 20); } Класс Brush Класс Brush является абстрактным классом для создания кистей, с по- мощью которых можно рисовать фигуры и текст на графической по- верхности. Библиотека .NET Compact Framework поддерживает клас- сы SolidBrush и TextureBrush. К сожалению, класс LinearGradientBrush, позволяющий рисовать красивые фигуры, в настоящее время не под- держивается. Класс SolidBrush При создании объекта SolidBrush нужно просто указать цвет, кото- рый будет использоваться для отображения фигур. Чтобы сменить цвет кисти, достаточно указать новый цвет в свойстве Color. В лис- тинге 6.2 приведен код, который позволяет нарисовать зеленый круг и желтый прямоугольник. Листинг 6.2 private void Forml_Paint(object sender. PaintEventArgs e) { Graphics g = e.Graphics: // Создаем кисть зеленого цвета SolidBrush myBrush = new SolidBrush(Color.Green); // Рисуем закрашенный круг g.FillEllipse(myBrush, 10, 30, 30. 30); // Меняем цвет кисти на желтый myBrush.Color = Col or.Yellow; // Рисуем закрашенный прямоугольник g.FillRectangle(myBrush, 50, 30, 50. 25); } Класс TextureBrush Класс TextureBrush позволяет создавать текстурную кисть. Подоб- ная текстурная кисть позволяет не рисовать однородным цветом,
Класс Color 91 а применять текстурное заполнение отображаемых графических примитивов. Использование подобной кисти позволяет добивать- ся красивых эффектов. В листинге 6.3 приведен пример использо- вания текстурной кисти с использованием изображения, входяще- го в состав Windows Mobile 2003. Листинг 6.3 private void FormlPai nt (object sender, PaintEventArgs e) { Graphics g = e.Graphics; // выбираем рисунок Image mylmage - new Bitmap(@"\Windows\alerts.bmp"); // создаем текстурную кисть TextureBrush texture = new TextureBrush(mylmage); // Рисуем эллипс, заполненный рисунком g.FillEllipse(texture, 10. 60. 120, 120); g.DisposeO; } ВНИМАНИЕ------------------------------------------------------- Такие свойства класса TextureBrush, как Transform и Wrap Mode, не поддерживаются в .NET Compact Framework 2.0. Класс Color При создании перьев или кистей применялся класс Color. Он позво- ляет задавать цвет либо с помощью предопределенного названия, либо указывая составные части цвета в модели RGB. Например, для создания красного цвета можно использовать код, приведенный в листинге 6.4. Листинг 6.4 // красный цвет по названию Color redColor - Col or.Red; // красный цвет из компонентов RGB Color redColor2 - Color.FromArgb(255, 0, 0); // Выводим на экран две красные линии g.DrawLine(new Pen(redColor), 10, 190, 100, 190); g.DrawLine(new Pen(redColor2), 10, 195, 100, 195);
92 Глава 6. Графика Класс Font Класс Font используется для вывода текста. Как ни странно, вывод текстовой информации тоже является графической операцией, что немного смущает новичков. Из четырнадцати возможных переза- груженных версий конструктора класса в .NET Compact Framework доступно только три. Для создания объекта Font нужно определить семейство шрифтов, размер символов и стиль начертания. Пример использования шрифта приведен в листинге 6.5. Листинг 6.5 Font myFont = new Font("Tahoma", 9, Fontstyle.Italic); g.DrawString("Карманный компьютер", myFont, myBrush, 14, 200); Класс Icon Объект Icon используется методом Drawicon для отображения пик- тограмм. Предположим, что необходимо использовать пиктограм- му, хранящуюся в ресурсах программы. В таком случае понадобит- ся код, приведенный в листинге 6.6. Листинг 6.6 Icon mylcon = new Icon(Assembly.GetExecutingAssembly(). GetMani festResourceStream("MyApp.Icon.ico")); Класс Bitmap Класс Bitmap предназначен для работы с растровыми изображения- ми. Программист может загрузить картинку в объект Bitmap из по- тока Stream, скопировать из существующего объекта Bitmap или за- грузить из файла. Также можно создать новый пустой объект Bitmap, указав только размеры картинки. Ранее класс Bitmap уже использо- вался при создании текстурной кисти. Но при этом применялся родственный объект Image. В листинге 6.7 приведен новый вариант создания кисти. Листинг 6.7 // выбираем рисунок Bitmap mylmage - new Bitmap(@"\Windows\alerts.bmp"); // создаем текстурную кисть TextureBrush texture = new TextureBrush(mylmage);
Графические методы 93 Структура Point Структура Point содержит координаты X и У для указания распо- ложения некоей точки, В библиотеке .NET Compact Framework поддерживается только один конструктор для создания объекта Point, в котором указываются эти координаты. Структура Poi nt час- то используется в методах DrawPolygon и Fi 11 Polygon, которые будут рассматриваться позже. Структура Rectangle Структура Rectangle определяет размер и расположение прямоуголь- ника. В мобильной версии используется только один конструктор, определяющий прямоугольник по координатам левого верхнего угла, ширине и высоте, что иллюстрирует код, приведенный в листинге 6.8. Листинг 6.8 Rectangle myRectangle = new RectangledO, 10, 70, 210); Графические методы В предыдущих примерах уже были использованы несколько мето- дов для работы с графикой. Но сейчас следует поговорить о них бо- лее подробно. Прежде всего нужно помнить, что для работы с графи- ческими методами необходимо сначала создать объект Graphics. Существует несколько способов получения объекта Graphics, и они будут рассматриваться достаточно подробно Метод CreateGraphics формы или элемента управления позволяет получить объект Graphics, предоставляющий возможность рисовать на форме или элементе управления. Этот метод демонстрируется в листинге 6.9. Листинг 6.9 Graphics g = this.CreateGraphicsO; Метод Fromlmage создает новый объект Graphics из заданного объек- та Image. При помощи этого метода можно изменять существую- щее изображение или создавать новое изображение. Причем об- работанное изображение можно потом сохранить в графическом файле. Использование метода иллюстрирует код, приведенный в листинге 6.10.
94 Глава 6. Графика Листинг 6.10 Bitmap bmp = new B1tmap(150. 90): Graphics g » Graphics.Fromlmage(bmp); Метод OnPaint класса Form получает в качестве параметра объект PaintEventArgs. Одним из членов данного объекта является объект Graphics, связанный с формой. Переопределяя метод OnPai nt класса Form, можно получить доступ к объекту Graphics из параметра PaintEventArgs, после чего можно работать с графикой в клиентской области формы. Вызов этого метода показан в листинге 6.11. Листинг 6.11 Protected override void OnPaint(PaintEventArgs e) { Graphics g = e.Graphics; } Получив любым из перечисленных способов доступ кобъекту Graphics, программист может рисовать фигуры, линии, кривые, изображения и текст при помощи различных методов. Самые распространенные графические методы будут рассмотрены в этом разделе главы. Метод Drawlmage рисует заданный объект Image в указанной, пози- ции экрана. Всего существует четыре перегруженные версии мето- да. Но в самой простой его версии достаточно указать координаты выводимой картинки, как показано в листинге 6.12. Листинг 6.12 g.Drawlmage(myImage, 10, 10): С помощью метода Drawlmage можно выводить на экран не все изоб- ражение, а только его часть. В этом случае надо указать размеры прямоугольника, который определяет размеры выводимой облас- ти картинки, как показано в листинге 6.13. В примере используется перечисление Graphi csllni t Pi xel, которое позволяет указывать еди- ницы измерения. Листинг 6.13 Bitmap myBMP - new Bitmap(0"\windows\banner.git"); Rectangle portion = new RectangleU, 1, 150, 25): g.DrawImage(myBMP, 20, 220, portion, GraphicsUnit.Pixel): Метод Fl 11 Rectangle уже применялся при рассмотрении кистей. Метод DrawRectangle использует перо вместо кисти, поэтому на эк- ран выводится незакрашенный прямоугольник.
Создание собственных методов DrawPie и FillPie 95 Чтобы нарисовать достаточно сложную фигуру, можно задать массив точек и соединить их прямыми отрезками, после чего можно закра- сить получившуюся фигуру. Для этого разработчик может использо- вать методы DrawPolygon и Fill Polygon. В листинге 6.14 приведен код, который позволяет нарисовать простой ромб по указанным точкам. Point(150,50), Point(200,100), Point(150.150), Point(lOO.lOO), Point(150,50). Листинг 6.14 // Нарисуем ромб // Зададим массив точек Point[] arrPoint = { new new new new new }: g.DrawPolygon(bluePen, arrPoint); Если все рассмотренные ранее методы объединить в одно прило- жение и затем запустить его, то на экране устройства будет отобра- жено несколько графических образов, как показано на рис. 6.1. Рис. 6.1. Основные приемы работы с графикой Создание собственных методов DrawPie и FillPie В данный момент .NET Compact Framework не поддерживает графи- ческие методы DrawPI е и Fi 11 Pi е, которые позволяли бы рисовать кру- говые секторы. Но можно создать свою версию этих методов, исполь- зуя математические вычисления, как показано в листинге 6.15.
96 Глава 6. Графика Листинг 6.15 ///«summary* ///Рисуем закрашенный сектор ///Параметры функции ///g ///solidBrush ///x.y ///width ///height ///startAngle ///endAngle ///«/summary* - Объект Graphics - Кисть для закраски сегмента - Координаты центра - Ширина сегмента - Высота сегмента - Значение начального угла - Значение конечного угла private void FilIPieCGraphics g, SolidBrush SolidBrush, int x, int y, int width, int height, double startAngle. double endAngle) { doublet] xAngle - new double[12]; doublet] yAngle = new doubletl2]; double angleincrement = (endAngle • startAngle) / 10; double angle = startAngle; for (int i = 0; i <= 10; i++) { xAngleti] “ x + (Math.Cos(angle * (Math.PI / 180)) * (width /2)); yAnglefi] = у + (Math.Sin(angle * (Math.PI / 180)) * (height /2)); angle += angleIncrement; } xAngletll] = x + (Math.Cos(endAngle * (Math.PI / 180)) * (width /2)); yAngletll] = у + (Math.Sin(endAngle * (Math.PI / 180)) * (height / 2)); Pointt] anglePoints = { new Point(x, y), new Point((int)xAngletO], (int)yAngletO]), new Point((int)xAngletl], (int)yAngletl]), new Point((int)xAnglet2], (int)yAnglet2])« new Point((int)xAnglef3], (int)yAnglet3]), new Point((int)xAnglet4], (int)yAnglet4]), new Point((int)xAnglet5], (int)yAnglef5]), new Point((int)xAnglet6], (int)yAnglet6]). new Point((int)xAnglef7], (int)yAnglef7]), new Point((int)xAnglet8], (int)yAnglet8]),
Создание собственных методов DrawPie и FillPie 97 new Point((int)xAngle[9], (int)yAngle[9]), new Point((int)xAngletlO], (int)yAngletlO]), new PointC(int)xAngletll]. (int)yAngletllJ) }: g.Fi11 Polygon(solidBrush, anglePoi nts); . } ///<summary> /// Рисуем границы сектора ///g - Объект Graphics ///pen - Перо для рисования сегмента ///x.y - Центр сегмента ///width - Ширина сегмента ///height - Высота ///startAngle - Значение начального угла ///endAngle - Значение конечного угла ///</summary> private void DrawPieeGraphics g. Pen pen, int x, int y, int width, int height, double startAngle, double endAngle) { doublet] xAngle = new double[12]; doublet] yAngle « new doubletl2]; double angleincrement - eendAngle - startAngle) / 10; double angle = startAngle; for tint i - 0; i <= 10; i++) { xAnglefi] = x + tMath.Cos(angle * (Math.PI / 180)) * (width /2)); yAngleti] = у + (Math.Sin(angle * (Math.PI / 180)) * (height /2)); angle += angleincrement: } xAnglefll] = x + (Math.Cos(endAngle * (Math.PI / 180)) * (width /2)); yAnglefll] = у + (Math.Sin(endAngle * (Math.PI / 180)) * (height /2)): Pointf] anglePoints = { new Point(x.y), new Point((int)xAngletO],(int)yAngletO]), new Point((int)xAngletl], (int)yAngletlJ). продолжение & 4-2873
98 Глава 6. Графика Листинг 6.15 (продолжение) new Point((int)xAngle[2],(int)yAngle[2]), new Point((int)xAngle[3].(int)yAngle[3J), new Polnt((1nt)xAngle[4],(int)yAngle[4]), new Poi nt((int)xAngle[5],(int)yAngle[5]), new Point((int)xAngle[6],(int)yAngle[6]), new Point((int)xAngle[7],(int)yAngle[7]), new Point((int)xAngle[8],(int)yAngle[8]), new Point((int)xAngle[9],(int)yAngle[9]). new Point((int)xAngle[10],(int)yAngle[10]) new Point((int)xAngle[ll],(int)yAngle[ll]) }; g.DrawPolygon(pen, anglePoints); } private void FormlPaint(object sender, PaintEventArgs e) { // Выводим несколько секторов на экран DrawPie(e.Graphics, new Pen(Color.Red), 130, 165, 100, 100, 0, 45); FillPie(e.Graphics, new SolidBrush(Color.Green), 120. 160, 100, 100, 46. 90); FillPie(e.Graphics, new SolidBrush(Color.Yellow), 120, 160, 100, 100, 91, 120); FillPie(e.Graphics, new SolidBrush(Color.Blue), 120, 160, 100, 100, 121, 260); FillPie(e.Graphics, new SolidBrush(Color.Red), 120, 160, 100, 100, 261, 360); } SYd Mr 11 2# Ф Рис. 6.2. Создание секторов Результат работы этой программы показан на рис. 6.2.
Создание фонового рисунка для формы 99 Создание фонового рисунка для формы К сожалению, .NET Compact Framework не поддерживает свойство Background Image, которое создает фоновый рисунок для формы. Но каждый программист может восполнить данный пробел, переопре- деляя метод OnPai nt. Нужно создать новый проект и разместить на форме какой-нибудь элемент управления, например кнопку. Кнопка не будет выполнять никаких функций. Она потребуется лишь для демонстрации тех- нологии. Также надо добавить в проект изображение, которое бу- дет использоваться в качестве фона для формы. В нашем примере картинка будет внедрена в программу как ресурс, хотя можно за- грузить ее из обычного графического файла. Чтобы все работало так, как запланировано, необходимо переопределить метод OnPai nt (). Новый код метода приведен в листинге 6.16. Листинг 6.16 protected override void OnPaint(PaintEventArgs e) { // получим картинку из ресурсов Bitmap backgroundimage = new Bitmap(Assembly.GetExecutingAssembly(). GetMani festResourceStreamC "BackgroundlmageCS. sochi cat. jpg")): e.Graphi cs.Drawlmage(backgroundimage, thi s.ClientRectangle, new Rectangle(O. 0, backgroundimage.Width, backgroundimage.Height), Graphi csUni t.Pi xel); } Рис. 6.3. Заполнение фона формы своим рисунком 4*
100 Глава 6. Графика После запуска программы можно будет увидеть, что форма имеет фоновый рисунок, а кнопка расположена поверх фона (рис. 6.3) Копирование рисунка Библиотека .NET Compact Framework 1.0 не поддерживает ме- тод System. Drawl ng. Image. Cl one, позволяющий создать точную ко- пию картинки. Это ограничение легко обходится с помощью соз- дания собственных методов. Кроме того, можно расширить воз- можности метода и добавить функциональность, позволяющую ко- пировать часть картинки. Соответствующий код приведен в лис- тинге 6.17. Листинг 6.17 // Копируем все картинку protected Bitmap CopyBitmap(Bitmap source) { return new Bitmap(source); } // Копируем часть картинки protected Bitmap CopyBitmap(Bitmap source. Rectangle part) { Bitmap bmp = new BitmapCpart.Width, part.Height); Graphics g = Graphics.Fromlmage(bmp); g.DrawImage(source, 0. 0, part. GraphicsUnit.Pixel); g.DisposeO; return bmp; } private void buttonl Click(object sender, EventArgs e) { Graphics g - CreateGraphicsO; Bitmap myBMP - new Bitmap(@"\windows\banner.gif”): // Половина ширины картинки int left = myBMP.Size.Width / 2; // Копируем всю картинку Bitmap clone = CopyBitmap(myBMP); // копируем левую часть картинки
Копирование рисунка 101 Bitmap part = CopyBitmap(myBMP, new Rectangle(O, 0, left, myBMP. Si ze. Hei ght)); // Выводим три картинки по вертикали: // источник, копию и копию левой части int у = 10: // картинка-источник g.DrawImage(myBMP, 10, у); у += myBMP.Height + 10; // картинка-копия g.DrawImage(clone, 10, у): у += clone.Height + 10: // копия левой части картинки g.Drawlmage(part, 10, у); у += part.Height + 10: g.DisposeO; } private void button2_Click(object sender, EventArgs e) { Graphics g = CreateGraphicsO: Bitmap myBMP = new Bitmap(@"\windows\banner.gif"): g.ClearCColor.White); int left = myBMP.Size.Width / 2; // Копия картинки Bitmap clone » (Bitmap)myBMP.CloneO; int у = 10: g.Drawlmage(myBMP, 10, y): у += myBMP.Height + 10: g.Drawlmage(clone, 10, y): у += clone.Height + 10: g.DisposeO; } В этом примере создаются две перегруженные версии метода Copy Image. При помощи этого метода можно копировать картинку или ее часть. Для сравнения в примере было показано, как можно скопи- ровать картинку с помощью метода Cl one, доступного в .NET Compact Framework 2.0. Результат работы соответствующего приложения показан на рис. 6.4.
102 Глава б. Графика Рис. 6.4. Копирование картинки разными способами Поддержка прозрачности Библиотека .NET Compact Framework позволяет использовать про- зрачный цвет, но при этом налагает определенные ограничения на эту возможность. Например, в библиотеке нет такого удобного ме- тода, как MakeTransparent. Но разработчик может задать прозрачный цвет при помощи метода SetCol огКеу класса ImageAttm butes. При этом разрешается использовать один и тот же цвет для минимального и максимального значений цветового ключа в версии метода ImageAttributes.SetColorKey (Color. Color). ПРИМЕЧ АНИE---------------------------------------------- Вторая перегруженная версия метода ImageAttributes.SetColor- Key(Color, Color, ColorAdjustType) в .NET Compact Framework не под- держивается. Используя прозрачный цвет, можно добиться красивых результа- тов при наложении картинки на картинку. Этот механизм стоит продемонстрировать на конкретном примере. Для тестового приложения были подготовлены две картинки. Пер- вое изображение является фотографией моего кота, а во втором хранится небольшая табличка с его именем, сделанная за несколь- ко секунд в стандартной программе Paint. При создании таблички фон был залит красным цветом, а для текстовой строки был вы- бран другой тон. Затем оба рисунка были добавлены в проект как ресурсы. Чтобы продемонстрировать рассматриваемый эффект, надо рас- положить на форме две кнопки. При нажатии первой кнопки таб-
Поддержка прозрачности 103 личка будет накладываться на фотографию без изменений. При нажатии на вторую кнопку красный цвет сначала будет опреде- лен как прозрачный и только потом произойдет наложение изоб- ражений. Основной код приложения приведен в листинге 6.18. Листинг 6.18 private void butAdd!mage_Click(object sender, EventArgs e) { Graphics g = CreateGraphicsO; // получим картинки из ресурсов Bitmap imgCat = new Bitmap(Assembly.GetExecutingAssembly(). GetMani festResourceStream( "TransparentCS.mycat.jpg")); Bitmap imgName = new Bitmap(Assembly.GetExecutingAssembly(). GetMani festResourceStream("Transparent_CS.catname.bmp”)); g.Drawlmage(imgCat, 0, 0, new Rectangle(0. 0, imgCat.Width, imgCat.Height), Graphi csllni t. Pi xel); g.DrawImage(imgName, 50. 120, new Rectangle(0. 0, imgName.Width, imgName.Height), GraphicsUnit.Pixel); g.DisposeO; } private void but!mage2_Click(object sender. EventArgs e) { Graphics g = CreateGraphicsO; // получим картинки из ресурсов Bitmap imgCat new Bitmap(Assembly.GetExecutingAssembly(). GetMani festResourceStream( "TransparentCS .mycat. jpg")): Bitmap imgName я new Bitmap(Assembly.GetExecutingAssembly(). GetMani festResourceStream( "T ransparentCS. catname. bmp")); // Очистим экран g.Clear(Color.White); // Выводим первую картинку g.Drawlmage(imgCat, 0, 0, new Rectangle(0, 0, imgCat.Width, imgCat.Height), Graphi csllni t. Pi xel); ImageAttributes attr = new ImageAttributesO; // Устанавливаем красный цвет как прозрачный attr.SetColorKey(Color.Red, Color.Red); // Выводим вторую картинку с установленными атрибутами продолжение &
104 Глава 6. Графика Листинг 6.18 (продолжение) Rectangle dstRect = new Rectangle(50, 120, imgName.Width, imgName.Height): g.Drawlmage(imgName, dstRect, 0. 0, imgName.Width, imgName.Height, GraphicsUnit.Pixel,attr); g.DisposeO; } ВНИМАНИЕ----------------------------------------------------- He забудьте импортировать пространство имен System.Draw- ing.Imaging при работе с этим примером. Если просто наложить одну картинку на другую, то результат бу- дет, мягко говоря, не очень красивым (рис. 6.5). Рис. 6.5. Неудачный вариант наложения двух картинок Если же воспользоваться методом SetColorKey для установки про- зрачного цвета, то результат наложения двух изображений будет выглядеть достойно (рис. 6.6). Рис. 6.6. Наложение картинки с использованием прозрачности
Округленные прямоугольники 105 Округленные прямоугольники Так как .NET Compact Framework не позволяет создавать округ- ленные прямоугольники встроенными средствами, то необходимо самостоятельно реализовать эту задачу. В этом разделе будет рас- сматриваться решение, предложенное Алексом Яхниным (Alex Yakhnin) в его блоге blog.opennetcf.org/ayakhnin/. Для достижения заданного эффекта надо нарисовать серию линий, которые соеди- няют эллипсы, и закрасить внутреннюю область сплошным цветом (рис. 6.7). Рис. 6.7. Создание прямоугольника со скругленным углами Соответствующий код приведен в листинге 6.19. Листинг 6.19 public static void DrawRoundedRectangle(Graphics g, Pen p. Color backColor. Rectangle re, Size size) { Point[] points = new Point[8]: // подготовим точки для фигуры points[0].X = rc.Left + size.Width I 2; points[0].Y = rc.Top + 1; points[l].X = re.Right - size.Width / 2; points[l].Y = rc.Top + 1; points[2].X = rc.Right; points[2].Y = rc.Top + size.Height I 2; points[3].X = rc.Right; points[3].Y = rc.Bottom size.Height I 2; points[4].X = rc.Right - size.Width / 2; points[4].Y = rc.Bottom; points[5].X = rc.Left + size.Width / 2; points[5].Y « rc.Bottom: points[6].X - rc.Left + 1; points[6].Y = rc.Bottom - size.Height / 2; points[7].X = rc.Left + 1; points[7].Y = rc.Top + size.Height / 2; // приготовим кисть для фона продолжение &
106 Глава 6. Графика Листинг 6.19 (продолжение) Brush fillBrush = new SolidBrushCbackColor); // рисуем отрезки и круги для округленного прямоугольника g.DrawL1ne(p, rc.Left + size.Width / 2, rc.Top, re.Right - size.Width I 2, rc.Top): g.FillEllipse(fillBrush, re.Right - size.Width, rc.Top, size.Width, size.Height): g.DrawEllipseCp, rc.Right - size.Width, rc.Top, si ze.Wi dth, si ze.Hei ght): g.DrawLine(p, rc.Right. rc.Top + size.Height / 2, rc.Right, rc.Bottom - size.Height / 2): g.FillEllipse(fillBrush, rc.Right - size.Width, rc.Bottom - size.Height, size.Width, size.Height): g.DrawEllipseCp. rc.Right - size.Width, rc.Bottom - size.Height. size.Width, size.Height): g.DrawLineCp, rc.Right - size.Width / 2. rc.Bottom, rc.Left + size.Width / 2, rc.Bottom): g.FillEllipseCfillBrush, rc.Left, rc.Bottom - size.Height, size.Width, size.Height): g.DrawEllipseCp, rc.Left, rc.Bottom - size.Height. size.Width, size.Height); g.DrawLineCp, rc.Left, rc.Bottom - size.Height / 2. rc.Left, rc.Top + size.Height / 2): g.FillEllipseCfillBrush, rc.Left, rc.Top, size.Width, size.Height); g.DrawEllipseCp, rc.Left, rc.Top, size.Width, size.Height); // заполняем прямоугольник, скрывая внутренние эллипсы g.FilIPolygonCfillBrush. points): // освобождаем ресурсы f i 11 Brush.Di sposeC); } private void butDrawRoundedRectangleClickCobject sender, EventArgs e) { Graphics g » CreateGraphicsC): Rectangle rc = new RectangleClO, 10, 200, 50): DrawRoundedRectangle(g, new PenCColor.Black), Color.CadetBlue, rc, new SizeC8, 8)); } Результат работы этого кода показан на рис. 6.8.
Создание экранных снимков 107 Рис. 6.8. Отображение закрашенного прямоугольника со скругленными углами Создание экранных снимков Если при работе с мобильным устройством необходимо сделать скриншоты, то для реализации замысла необходимо использовать внешние устройства. Конечно, можно просто сфотографировать экран, но настоящий программист будет использовать функции Windows API. В этом разделе главы будет рассматриваться пример копирования определенной области окна, всего рабочего окна про- граммы или любого другого окна. Для демонстрации примера надо разместить на форме список, три кнопки и один таймер. Сам код приведен в листинге 6.20. Листинг 6.20 [DlllmportC'coredll.dll", EntryPoint e "GetDesktopWindow")] public static extern IntPtr GetDesktopWindowO: [Dll Import("coredll.dll". EntryPoint - “GetDC")] public static extern IntPtr GetDCCIntPtr hWnd); [DI1 Import("coredl1.dll". EntryPoint = "ReleaseDC”)] public static extern IntPtr ReleaseDC(IntPtr hWnd, IntPtr hDC): [Dll Import("coredl1.dll")] public static extern int BitBlt(IntPtr hdcDest. int nXDest, int nYDest, int nWidth, int nHeight, IntPtr hdcSrc, int nXSrc. int nYSrc, uint dwRop); const int SRCCOPY = 0x00CC0020; private void ScreenShot(string filename. Graphics gx. Rectangle rect) { продолжение &
108 Глава 6. Графика Листинг 6.20 (продолжение) Bitmap bmp = new Bitmap(rect.Width. rect.Height): Graphics g = Graphics.Fromlmage(bmp); BitBlt(g.GetHdc(). 0, 0, rect.Width, rect.Height, gx.GetHdc(), rect.Left, rect.Top, SRCCOPY): bmp.Save(fi1ename, System.Drawing.Imaging.ImageFormat.Bmp); bmp.DisposeO; g.DisposeO: } private void butPartOfWindow_Click(object sender, EventArgs e) { // Делаем снимок списка ScreenShot(@"\My Documents\save.bmp", this.CreateGraphicsO, listBoxl.Bounds); } private void butScreen_Click(object sender, EventArgs e) { // Делаем снимок экрана Rectangle rect = new Rectangle(0.0,240,240); Bitmap bmp = new Bitmap(rect.Width, rect.Height): Graphics g = Graphics.Fromlmage(bmp); IntPtr hwnd = GetDesktopWindowO; IntPtr hdc = GetDC(hwnd); BitBlt(g.GetHdc(), 0, 0, rect.Width, rect.Height, hdc, rect.Left. rect.Top. SRCCOPY); bmp.Save(@”\My Documents\screen.bmp". System.Drawing.Imaging.ImageFormat.Bmp); // Освобождаем ресурсы ReleaseDC(hwnd, hdc); bmp.DisposeO; g.DisposeO: } private void timerl Tick(object sender, EventArgs e) { // Делаем снимок экрана через 5 секунд Rectangle rect = new Rectangle(0, 0, 240, 240); Bitmap bmp = new Bitmap(rect.Width, rect.Height); Graphics g = Graphics.Fromlmage(bmp); IntPtr hwnd = GetDesktopWindowO; IntPtr hdc = GetDC(hwnd);
Создание экранных снимков 109 BitBlt(g.GetHdc(), 0, 0. rect.Width, rect.Height, hdc, rect.Left, rect.Top, SRCCOPY); bmp.Save(@"\My Documents\5sec.bmp“, System.Drawing.Imaging.ImageFormat.Bmp); // Освобождаем ресурсы ReleaseDC(hwnd, hdc); bmp.Dispose!); g.DisposeO; timed. Enabled = false; } private void but5Sec_Click(object sender, EventArgs e) { timed. Enabled = true; } Функция ScreenShot позволяет быстро получить участок экрана и сохранить его в графическом файле. В рассмотренном примере внешний вид списка сохраняется в файле listbox.bmp, Для этого до- статочно было указать имя файла, объект Graphics и размеры спис- ка Li stBox. Для получения снимка экрана пример пришлось несколь- ко усложнить, добавив вызовы функций GetDesktopWi ndow и GeDC. Если нужно получить снимок другой программы, то придется вос- пользоваться таймером. После запуска таймера в распоряжении пользователя будет 5 секунд, чтобы запустить другое приложение. Основная программа будет работать в фоновом режиме и сделает снимок экрана. Чтобы проверить работу приложения, нужно запустить програм- му, нажать каждую кнопку, а затем с помощью программы File Explorer найти сохраненные файлы. ВНИМАНИЕ-------------------------------------------------------- Нужно проявлять определенную осторожность при работе с ме- тодом Bitmap.Save (). Дело в том, что в Windows Mobile 2003 и бо- лее ранних версиях операционных систем библиотека .NET Compact Framework не поддерживает сохранение графических файлов в форматах GIF, JPEG или PNG. Сохранять файлы можно только в формате BMP. Причем во время написания кода редак- тор не заметит ошибки и позволит запустить программу с непра- вильным вызовом метода. Однако при вызове метода возникнет исключение NotSupportedException. К счастью, в Windows Mobile 5.0 поддерживаются все четыре графических формата.
110 Глава 6. Графика Метод Lockbits В .NET Compact Framework 2.0 появилась ограниченная поддерж- ка метода LockBits, при помощи которого можно манипулировать массивом пикселов изображения. Перечисление ImageLockMode в данном методе позволяет использовать значения Readerite, Readonly и WriteOnly. А перечисление Pi xel Format поддерживает значения, перечисленные в следующем списке: □ Formatl6bppRgb555; □ Formatl6bppRgb565; □ Format24bppRgb; □ Format32bppRgb. На сайте MSDN можно найти статью «How to: Use LockBits» с при- мером, в котором создается картинка и меняется интенсивность си- них пикселов с помощью метода LockBits. В листинге 6.21 приведен пример, который для большей наглядности пришлось немного из- менить. Листинг 6.21 private Bitmap CreateBitmap(int width, int height) { Bitmap bmp = new Bitmap(@"\Windows\msn.gif"): width - bmp.Size.Width; height = bmp.Size.Height; Graphics g = Graphics.Fromlmage(bmp); g.DisposeO; return bmp; } protected override void OnPaint(PaintEventArgs e) { Bitmap bmp =» CreateBitmap(100, 100); // Выводим картинку-оригинал e.Graphics.DrawImageCbmp, 0, 0); MakeMoreBlue(bmp); // Рисуем модифицированную картинку ниже исходного изображения е.Graphics.DrawImageCbmp. 0, 50); bmp.DisposeO: } private void MakeMoreBlue(Bitmap bmp) {
Графический редактор ill // Задаем формат данных о цвете для каждой точки изображения Pixel Format pxf = Pixel Format.Format24bppRgb; // Блокируем изображение в памяти Rectangle rect = new Rectangle(O, 0, bmp.Width, bmp.Height); BitmapData bmpData = bmp.LockBi ts(rect, ImageLockMode.ReadWrite, pxf); // Получаем адрес первой строки развертки IntPtr ptr = bmpData.ScanO; // Массив, содержащий байты изображения int numBytes = bmp.Width * bmp.Height * 3; byte[] rgbValues = new byte[numBytes]; // Копируем значения RGB в массив Marshal.Copy(ptr, rgbValues. 0, numBytes); // Модифицируем изображение, устанавливая // синий цвет для каждой точки в картинке for (int counter = 0; counter < rgbValues.Length; counter += 6) rgbValues[counter] = 255; // Копируем значения RGB обратно в изображение Marshal.Сору(rgbValues, 0, ptr, numBytes); // Разблокируем биты в памяти bmp.UniockBits(bmpData); } После запуска приложения на экране будут показаны две копии картинки, причем нижнее изображение будет немного отличаться от верхнего насыщенностью цветов. Графический редактор Теперь, когда мы ознакомились с графическими методами, настало время написать простейший графический редактор с минимальны- ми возможностями. В этом приложении можно будет рисовать при помощи стилуса линии, а также прямые цветные линии из трех цве- тов. Процесс рисования узоров на экране КПК при помощи стилу- са гораздо больше похож на реальное рисование, чем рисование мышью в стандартных графических редакторах. Весь код программы сводится к обработке событий мыши MouseDown, MouseMove и MouseUp. В принципе, приемы создания графических эф- фектов ничем не отличаются от соответствующих приемов, приме- няемых на обычных персональных компьютерах. Я взял два при- мера из своей книги «Занимательное программирование на Visual Basic .NET» и перенес код в проект с учетом синтаксиса языка С#, что иллюстрирует листинг 6.22.
112 Глава 6. Графика Листинг 6.22 private int xrnd. y_md; Pen myPen = new Pen(Color.LightBlue); private bool bPaint: Graphics g; private Pen erasePen; private Point ptsStart; private Point ptsPrevious; private Point ptsCurrent; private void Forml_MouseDown(object sender, MouseEventArgs e) { // Начинаем рисование bPaint = true; if (mnuLines.Checked) { ptsStart.X = e.X; ptsStart.Y = e.Y; ptsPrevious = ptsStart; } if (mnuPaint.Checked) { 11 координаты стилуса при нажатии xjnd = e.X; у md = e.Y; } } private void Forml_MouseMove(object sender. MouseEventArgs e) { if (bPaint) { if (mnuLines.Checked) { ptsCurrent.X = e.X; ptsCurrent.Y = e.Y; g = CreateGraphicsO; g.DrawLine(erasePen, ptsStart.X, ptsStart.Y, ptsPrevious.X, ptsPrevious.Y); g.DrawLine(myPen, ptsStart.X, ptsStart.Y, ptsCurrent.X, ptsCurrent.Y); ptsPrevious = ptsCurrent; g.DisposeO:
Графический редактор 113 } if (mnuPaint.Checked) { g = CreateGraphicsO: int x mm = e.X; int y_mm = e.Y; g.DrawLine(myPen, x_md, yjnd, x_mm, ymm); x_md = x mm; y_md = ymm; g.DisposeO: } } } private void Forml_MouseUp(object sender. MouseEventArgs e) { bPaint = false: } private void mnuClear_Click(object sender, EventArgs e) { g » CreateGraphicsO: g.Clear(this.BackColor); g.DisposeO: } private void Forml_Load(object sender, EventArgs e) { erasePen = new Pen(this.BackColor): } private void mnuPaint_Click(object sender, EventArgs e) { mnuPaint.Checked = !mnuPaint.Checked: mnuLines.Checked = ImnuLines.Checked; } private void mnuGreenPenCl ick (object sender, EventArgs e) { myPen.Color = Col or.Green: } private void mnuRedPen_Click(object sender, EventArgs e) { myPen.Color » Col or.Red: }
114 Глава 6. Графика На рис. 6.9 показано, как выглядит созданный графический редак- тор в работе. Рис. 6.9. Простейший графический редактор Дополнительные материалы Если нужно ознакомиться с дополнительными материалами по про- граммированию графики, то стоит обратиться к статьям документа- ции MSDN. Например, в статье «How to: Display a Gradient Fill» рас- сказывается^ том, как раскрасить элемент градиентной заливкой. Также в документации MSDN имеется статья уже известного нам Алекса Яхнина «Creating a Microsoft .NET Compact Framework-based Animation Control», в которой рассказывается о создании элемента управления, способного воспроизводить анимированное изображе- ние, создавая его на основе серии картинок. Тем, кто планирует пи- сать приложения для устройств под управлением Windows Mobile 5.0, стоит обратить внимание на технологию Windows Mobile DirectX and Direct3D, которая дает дополнительные возможности для рабо- ты с графикой.
Глава 7 Разработка приложений Активация и деактивация формы Модель выполнения программ на карманном компьютере отличает- ся от поведения программ, работающих на обычном персональном компьютере. Например, на мобильных компьютерах используется один экземпляр запущенной программы. Аналогом подобного пове- дения на настольных компьютерах является почтовая программа Outlook Express, которая всегда запускается в одном экземпляре. При попытке запуска программы она просто активируется (если уже была запущена). При этом вторая копия программы не запускается. При создании приложения для КПК разработчику не придется при- лагать никаких усилий для реализации подобного поведения. Среда выполнения .NET Compact Framework сама позаботится о том, чтобы запускался только один экземпляр программы. Следует помнить, что пользователь не должен сам закрывать программу. При запуске новой программы ее окно просто загораживает предыдущую программу. Учитывая подобное поведение, нужно писать программы, которые не занимают много ресурсов системы. Однажды запущенное приложе- ние может находиться в памяти несколько дней, пока пользователь не перезагрузит компьютер или не закроет программу самостоятельно. Деактивированная программа закроется автоматически, если система обнаружит уменьшение свободной памяти при разрядке батареи. Но, тем не менее, иногда надо проследить, чтобы при закрытии программа освободила ресурсы, которые она использовала. Бывают ситуации, когда приложение поддерживает соединение с базой данных или осу- ществляет связь с COM-портами. В этом случае система может не освободить занимаемые программой ресурсы. Для отслеживания со- стояния формы используются события Form. Deacti vate и Form. Act i vated. В листинге 7.1 приведен пример работы с этими событиями. Листинг 7.1 private void Forml_Activated(object sender, EventArgs e) { // Здесь ваш код для восстановления связей с портами и т. д. продолжение &
116 Глава 7. Разработка приложений Листинг 7.1 (продолжение) 1ЫInfo.Text = "Приложение активировано"; } private void Forml_Deactivate(object sender. EventArgs e) { // Здесь ваш код для освобождения ресурсов 1ЫInfo.Text = "Приложение деактивировано"; } Так как приложение в неактивном состоянии может быть закрыто системой, то важно блокировать возможную потерю данных. Для этого нужно использовать событие Deactivate. Закрыть или свернуть окно Закрыть или свернуть — вот в чем вопрос. Компания Microsoft пред- ложила для мобильных приложений модель поведения программ, отличающую от принятой в настольных компьютерах. Когда пользователь щелкает на кнопке закрытия, то на самом деле окно программы не закрывается, а сворачивается. Для пользователей подобное поведение приложений кажется странным, поэтому не- которые разработчики создавали программы, которые позволяли закрывать приложения одним нажатием стилуса. Популярность таких программ говорит о том, что не всем пользователям понрави- лось поведение приложений, которые отнимают ресурсы у систе- мы. Но сейчас не нужно обсуждать целесообразность такого подхо- да к закрытию программ. Разработчик может создать приложение, которое позволит выбрать вариант закрытия приложения. Пользо- ватель может нажать кнопку закрытия, чтобы просто свернуть окно, либо выполнить команду меню Выход, чтобы действительно закрыть приложение. Но бывают ли такие ситуации, когда действительно требуется при- нудительно закрывать программу? Такая необходимость возника- ет при отладке и тестировании программы в эмуляторе. При стан- дартной модели поведения довольно утомительно каждый раз вручную останавливать программу, запущенную в эмуляторе. Ко- нечно, можно временно присвоить свойству MinimizeBox при отлад- ке значение False, что поможет избавиться от этой проблемы. Но перед окончательным релизом программы надо все же поставить значение True. Однако полагаться на свою память не стоит. Гораздо проще воспользоваться условной компиляцией.
Пиктограмма приложения 117 При создании приложения надо использовать несколько строчек кода в конструкторе формы сразу после вызова процедуры Initial 1zeComponent(), как показано в листинге 7.2. Листинг 7.2 #if DEBUG MinimizeBox = false; #else MinimizeBox = true; #endif Этот код стоит вынести на панель инструментов (рис. 7.1), что по- зволит быстро добавлять эту конструкцию в создаваемые прило- жения. Отныне все примеры в данной книге будут снабжаться этим кодом. Рис. 7.1. Код условной компиляции на панели инструментов Пиктограмма приложения Любая серьезная программа должна иметь собственную пиктограм- му. Чтобы указать используемую пиктограмму, надо при помощи команды меню Project ► Properties открыть диалоговое окно Property
118 Глава 7. Разработка приложений Pages, выбрать раздел Application и указать путь к файлу с пикто- граммой в свойстве Icon (рис. 7.2). Г* Ct - Microsoft Visual Studlr 4r Edr **» Project Buto Debug 'да TooB Vmta» Coosnunty Hep 1 Frxmi.csjDeuspj CrateApp CS* start Pa^e Ctject Browser J Apptaton* Buki Assembly name: OaceA<ip_CS Debut namespace- j ‘CrateA₽₽_CS ВиИ Events Debug CXxpvt type; Wndows Apdcabcn -S L Informal»'».. Resources Reference Paths sgnng Startup object (Not set} Devices Rera**«e*' ;1соп_201Л» < Resource .He: X И __ Рис. 7.2. Добавление пиктограммы для приложения Создание собственных диалоговых окон Сложные приложения часто используют несколько форм. Напри- мер, во многих программах имеется диалоговое окно 0 программе, в котором отображаются информация о программе, номер версии, сведения об авторе и логотип компании. Для создания подобных форм хорошо подойдет собственное диа- логовое окно. Чтобы отобразить такое окно, используется метод ShowDi a log. Этот метод делает недоступным родительскую форму, пока диалоговое окно находится на экране. Диалоговое окно может возвращать результат вызова метода ShowDi a log не только себе, но и родительскому окну. Предположим, что нужно создать специальное окно авторизации пользователя для доступа к программе. В состав проекта нужно вклю- чить новую форму, которая будет реализована как диалоговое окно проверки имени пользователя LogonForm. Это будет маленькое окно без четко очерченной границы. В нем надо разместить текстовое поле и две кнопки. Затем надо задать значения свойств FormBorderStyl е, Size и Location. При загрузке основной формы и обработке события Load создается новый экземпляр объекта LogonForm и вызывается как
Создание собственных диалоговых окон 119 диалоговое окно. Данное окно может вернуть значения Dial ogResul t. OK, если пользователь ввел имя, или Di al ogResul t. Cancel, если он просто закрыл форму. Если было введено правильное имя, то главная форма продолжает свою работу. В противном случае приложение следует закрыть. Соответствующий код приведен в листинге 7.3. ВНИМАНИЕ-------------------------------------------- Чтобы элементы управления диалогового окна были доступны вы зывающей форме, их надо объявить с модификатором public. По умолчанию используется модификатор private. Листинг 7.3 private void Forml_Load(object sender. EventArgs e) { LogonForm LogonFrm = new LogonFormO; if (LogonFrm.ShowDIalog() — DialogResult.Cancel) { LogonF rm.Di spose(); this.CloseO; } else { this.Text +*="-" + LogonFrm. txtCheck. Text; LogonFrm.Di spose(); } } После того как форма авторизации будет отображена на экране, нужно обработать события Click для нажатия кнопки проверки вве- денного имени пользователя или кнопки отмены. Первая кнопка проверяет правильность ввода имени. Если проверка завершилась успешно, то возвращается значение Di alogResul t.OK. Это иллюстри- рует код, приведенный в листинге 7.4. Листинг 7.4 private void butOK_Click(object sender, EventArgs e) { if (txtCheck.Text —» "Alex") { this.DialogResult = DialogResult.OK; } else { продолжение &
120 Глава 7. Разработка приложений Листинг 7.4 (продолжение) MessageBox.Show("В доступе отказано. Попробуйте еще раз", "Вход в программу"); } } Если пользователь не знает имени для доступа к программе, то ему придется нажать кнопку Отмена. В этом случае обработчик собы- тия butCancel Click, код которого приведен в листинге 7.5, возвра- щает значение Di alogResult .Cancel в главную форму, которая за- крывает приложение. Листинг 7.5 private void butCancel_Click(object sender. System.EventArgs e) { this.DialogResult = DialogResult.Cancel: } Создание заставки Splash Screen Многие программы имеют так называемые заставки (splash screen). При загрузке формы сначала отображается окно с логотипом ком- пании, названием продукта и дополнительной информацией. Сле- дует реализовать приложение с подобным экраном, чтобы научить- ся использовать эту технологию. Прежде всего надо создать новый проект и добавить к уже имею- щейся форме еще одну форму с именем Spl ash. При запуске прило- жения заставка появится во весь экран с заданным текстом в цент- ре экрана. Эта форма будет отображаться в течение трех секунд, а затем она автоматически закроется и на экране останется основ- ная форма. Создание подобного окна практически не отличается от предыду- щего примера. Но в этом примере надо использовать таймер, кото- рый будет отвечать за появление и закрытие начальной заставки. Эта же форма будет использоваться как диалоговое окно для стан- дартного пункта меню 0 программе. Итак, надо создать дополнительную форму AboutForm и задать зна- чения всех необходимых свойств окна На форме надо располо- жить таймер, интервал срабатывания которого будет равен 3 с. Код, реализующий подобное поведение программы, приведен в ли- стинге 7.6.
Создание заставки Splash Screen 121 Листинг 7.6 protected override void OnPaint(PaintEventArgs e) { StringFormat sf = new StringFormatO; sf.Alignment - StringAlignment.Center; sf.LineAlignment = StringAlignment.Center; Graphics g = e.Graphics: g.DrawString(".NET Compact Framework", this.Font, new SolidBrush(Color.Blue), Screen.Primaryscreen.Bounds, sf); } private void timerl_Tick(object sender, EventArgs e) { this.CloseO; В событии OnPai nt формы About Form нужно установить свойства для вывода текста. При желании можно добавить отображение логоти- па. Через заданный интервал таймер просто закроет это окно. Код для основной формы MainForm приведен в листинге 7.7. Листинг 7.7 public MainFormO { Ini ti ali zeComponent(); #if DEBUG MinimizeBox = false; #el se MinimizeBox = true; #endi f AboutForm about = new AboutFormO; about.ShowDi alog(); } private void mnuAbout_Click(object sender, EventArgs e) { AboutForm about = new AboutFormO: about.ShowDi alog(); } Теперь при запуске приложения на экране сначала будет отображать- ся заставка. После истечения трех секунд она исчезнет, и пользова- тель увидит основную форму.
122 Глава 7. Разработка приложений Поворот экрана Устройства с операционной системой Pocket PC 2003 Second Edition и старше обрели долгожданную возможность поворачи- вать содержимое экрана. Раньше пользователям приходилось ус- танавливать дополнительные программы для достижения такого эффекта. А разработчики получили возможность управлять по- воротами экрана управляемыми методами только в .NET Compact Framework 2.0. Но зато теперь это можно сделать буквально од- ной строкой кода. Тем, кто по ряду причин должен по-прежнему использовать .NET Compact Framework 1.0, придется задейство- вать сложный код с вызовами функций API, который приведен в листинге 7.8. Сначала надо установить ссылку на пространство имен Mi crosoft. Wi ndowsCE. Forms. После этого следует просто исполь- зовать нужные свойства класса SystemSettings. Листинг 7.8. using Microsoft.WindowsCE.Forms; // запоминаем настройки экрана ScreenOrientation initialOrientation - SystemSetti ngs.ScreenOri entati on; private void butRot90_Click(object sender. EventArgs e) { // поворачиваем экран на 90 градусов SystemSetti ngs.ScreenOri entati on - ScreenOri entati on.Angle90; } private void butRestore_Click(object sender, EventArgs e) { // восстанавливаем старую ориентацию if (SystemSettings.ScreenOrientation !-• initialOrientation) { try { SystemSettings.ScreenOrientation = initialOrientation; } catch (Exception) { // Невозможно вернуться к старым настройкам
Готовые приложения 123 MessageBox.ShowCHe могу восстановить " + "предыдущую ориентацию экрана."); } } Рекомендации по дизайну форм Компания Microsoft выработала определенные рекомендации по дизайну форм, которых следует придерживаться. Эти рекоменда- ции можно найти в документации MSND. Так, например, в одной из статей указывается, что в некоторых случаях пользователь пред- почитает пользоваться пальцами вместо стилуса. Поэтому, если форма содержит кнопки, то они должны быть достаточно больши- ми, чтобы было удобно нажимать на них. Рекомендуемые размеры кнопок для Pocket PC составляют 21 х х 21 пикселов, если пользователь применяет стилус, и 38 х 38 пик- селов, если он предпочитает нажимать кнопки пальцами. Также надо предусмотреть свободное пространство между элементами, чтобы избежать ошибочных нажатий. Если маленькие кнопки для копирования и удаления файлов находятся рядом, то пользовате- ли не раз вспомнят вас недобрым словом при неаккуратном нажа- тии на кнопки. В то же время следует группировать часто используемые элемен- ты, чтобы пользователю не пришлось часто перемещать стилус. Это тоже достаточно утомительно. Понятно, что к играм эти рекомен- дации не относятся. В них действуют свои законы. Готовые приложения До сих пор мы с вами изучали примеры, которые могли бы стать частью настоящих программ. Но сейчас пришло время написать не- сколько приложений, которые уже можно использовать в реальной жизни. Так получилось, что первые две программы были написаны для смартфонов, о которых речь пойдет в дальнейших главах. Но при помощи этих примеров можно получить представление о программировании для этого класса устройств. К тому же, на их основе можно написать аналогичный пример для карманных ком- пьютеров, что поможет увидеть сходство в написании приложений для разных типов устройств.
124 Глава 7. Разработка приложений Файловый менеджер для смартфона Смартфоны под управлением Windows Mobile 2005 не имеют в со- ставе системы приложения, которое позволяет просматривать со- держимое папок и файлов. В этом разделе будет рассматриваться аналог Проводника для смартфона. Основой данного примера по- служил великолепный проект с открытым исходным кодом, кото- рый находится на сайте www.businessanyplace.net/?p=spfilernan. Ав- тор проекта Кристиан Форсберг (Christian Forsberg) любезно разрешил использовать его программу в качестве учебного посо- бия для этой книги. Интересна история создания этого приложения. Сам автор ориги- нальной версии писал программу еще на Visual Studio .NET 2003 для смартфонов под управлением системы Smartphone 2003. Когда я скачал исходный код и попытался запустить его, то среда разра- ботки Visual Studio .NET 2005 предложила конвертировать проект. Я согласился, в результате получил новую версию проекта. Теперь, после переделки, проект запускался в Visual Studio 2005 и исполь- зовал эмулятор Smartphone 2003. Но мне захотелось использовать пример для смартфонов Windows Mobile 2005. Для этого достаточ- но было в свойствах проекта выбрать другую платформу. Снова несколько минут раздумий, и Visual Studio выдает еще раз переде- ланный проект. Но и этого мне мало. Проект по-прежнему исполь- зует .NET Compact Framework 1.0. После выбора нужных значений свойств проекта, он стал использовать .NET Compact Framework 2.0. Осталось лишь перевести некоторые команды на русский язык и ввести новые возможности .NET Compact Framework 2.0 вместо старых конструкций, которые применялись для .NET Compact Framework 1.0. Зачем нужен файловый менеджер Файловый менеджер необходим как для разработчиков, так и для пользователей. С его помощью можно просматривать содержимое папок, удалять лишние файлы, создавать новые папки и выполнять другие операции с файлами. Трудно сказать, почему Microsoft ре- шила не включать программу подобного рода в состав системы Windows Mobile. Кстати, производители смартфонов самостоятель- но добавляют файловые менеджеры собственной разработки в со- став стандартных программ. Но мы с вами напишем свою програм- му, что гораздо интереснее.
Файловый менеджер для смартфона 125 Графический интерфейс программы У создаваемого приложения будет своя пиктограмма. При запуске программа будет отображать содержимое папки Му Documents. Сам графический интерфейс программы очень прост и понятен. Нави- гация по папкам осуществляется простым выделением нужной пап- ки и нажатием кнопки Enter. Для перехода на один уровень вверх нужно выделить папку, обозначенную двумя точками. Пункт меню Выход закрывает файловый менеджер, а пункт Меню позволяет вы- брать выполняемую операцию (рис. 7.3). Рис. 7.3. Общий вид файлового менеджера Меню содержит команды для всех стандартных файловых опера- ции. Пользователь может удалять, копировать, добавлять и пере- именовывать файлы. Также имеется возможность работы с ярлы- ками. Чтобы использовать эту возможность, нужно сначала выбрать файл, выполнить команду Копировать, затем перейти в нужную пап- ку и выполнить команду Вставить ярлык. При выборе команды Свой- ства появляется соответствующее окно (рис. 7.4). В этом окне отображается справочная информация о файле или папке. Пользователь сможет найти размер файла, дату его созда- ния и атрибуты файла, которые можно модифицировать.
126 Глава 7. Разработка приложений i> , ‘JSP1 Size ькв (гехМ: И.ЙЛ»1НМ1 NWMtK □ fiwd-only Рис. 7.4. Окно свойств Код программы Теперь можно приступить к написанию кода. При запуске програм- мы выполняется обработчик события Form Load. При загрузке ос- новной формы MalnForm работает код, приведенный в листинге 7.9. Листинг 7.9 Li stVi ewHelper.SetGradi ent(1i stVi ew); string bs = Path.DirectorySeparatorChar.ToStringO; // Устанавливаем начальную папку this.path = bs + "My Documents" + bs; // Заполняем список папок и файлов fillListO; Сначала устанавливается внешний вид элемента listView с гради- ентной закраской фона. Затем устанавливается папка по умолча- нию Му Documents, которую программа открывает при загрузке. Ме- тод fill Li st заполняет список ListView содержимым открываемой папки. Сам код метода приведен в листинге 7.10. Листинг 7.10 /// <summary> /// Заполнение ListView списком папок и файлов /// </summary> private void ‘FillListO { Cursor.Current = Cursors.WaitCursor; // Заполняем ListView списком папок и файлов // в выбранной папке ListViewItem Ivi; 1i stVi ew.Begi nUpdate(); listView. Items. ClearO; // Если не корневая папка if (path.Length > 1)
Файловый менеджер для смартфона 127 И Добавляем папку "Вверх" Ivi = new ListViewItem(UPDIR); Ivi.Imageindex = 0; listView.Items.Add(lvi); } // Добавляем папки stringE] dirs = Directory.GetDirectories(path); ArrayList list = new ArrayList(dirs.Length); for(int i = 0; i < dirs.Length; i++) list.Add(dirs[i]); - list.Sort(new SimpleComparer()); foreach(string dir in list) { Ivi = new ListViewItem(Path.GetFileName(dir)); Ivi.ImageIndex = 0; listview.Items.Add(lvi); } // Добавляем файлы string[] files = Directory.GetFiles(path); list = new ArrayList(files.Length); for(int i = 0; i < files.Length; i++) list.Add(filesEi]); list.Sort(new SimpleComparer()); foreach(string file in list) { Ivi = new ListViewItem(Path.GetFileName(file)); Ivi.Imageindex = 1; listView.Items Add(lvi); } 1i stVi ew.EndUpdate(); if(listView.Items.Count > 0) { // выделяем первый элемент listView.ItemsEO].Selected = true; listView.ItemsEO].Focused = true; } Cursor.Current = Cursors.Default; } Итак, посмотрим, что делает метод f i 11 Li st. Перед заполнением элемента списком файлов надо очистить его содержимое от преды- дущих записей при помощи метода Clear. После очистки списка надо проверить, является ли папка корневой. Если папка не корне-
128 Глава 7. Разработка приложений вая, то в список добавляется специальная папка «На один уровень выше». Затем в список добавляются все папки в отсортированном порядке. После этого наступает очередь файлов. Они сортируются и зано- сятся в список. Наконец, первый элемент списка выделяется дру- гим цветом. Заодно на первом элементе устанавливается фокус вво- да. Навигация по папкам и файлам осуществляется с помощью кнопок и дополняется кнопкой Action. Код навигации приведен в листинге 7.11. Листинг 7.11 /// <summary> /// Навигация по папкам и файлам /// </summary> /// <param name="sender"></param> /// <param name="e"></param> private void listView_ItemActivate(object sender. System.EventArgs e) { Cursor.Current e Cursors.WaitCursor; Listviewitem Ivi = 1i stVi ew.Iterns[1istVi ew.SeiectedIndi ces[0]]: bool isFolder - Ivi.Imageindex -» 0: if (Ivi.Text — UPDIR) { path = path.Substrings, path.Substrings, path.Length - D.LastlndexOf(Path.DirectorySeparatorChar) + 1): fillListO; } else if(isFolder) { path += Ivi.Text + Path.DirectorySeparatorChar; fillListO; } else Shell Execute.Start(path + Ivi.Text); Cursor.Current = Cursors.Default; } После нажатия кнопки действия приложение получает информа- цию о выделенном пункте. Если выделена специальная папка пе- рехода на один уровень выше, то текущий путь заменяется путем к родительской папке. Если выделена папка, то путь меняется на
Файловый менеджер для смартфона 129 путь к выделенной папке. Если выделен файл, то приложение пы- тается запустить его с помощью ассоциированной программы. Теперь разберем код для команд меню. Для команды Вырезать код приведен в листинге 7.12. Листинг 7.12 private void cutMenuItem_Click(object sender, System.EventArgs e) { ListViewItem Ivi = 1istView.Iterns[1istView.SeiectedIndices[0]]; clipboardFileName == this, path + Ivi.Text; clipboardAction - ClipboardAction.Cut; } Путь к текущему выбранному файлу сопоставляется с производи- мым действием. Код, выполняющийся после выбора команды Ко- пировать, приведен в листинге 7.13. Листинг 7.13 private void copyMenuItemClick (object sender, System.EventArgs e) ListViewItem Ivi = listView.Items[listView.SelectedIndices[O]]; clipboardFileName e path + Ivi.Text: clipboardAction = ClipboardAction.Copy; } Для команды меню Вставить код немного усложняется. Он приве- ден в листинге 7.14. Листинг 7.14 private void pasteMenuItem_Click(object sender, System.EventArgs e) { // Если файл существует string dest - path + Path.GetFileName(clipboardFileName): i f(Fi1e.Exi sts(dest)) { if(MessageBox.Show("Файл уже существует, перезаписать?", this.Text, MessageBoxButtons.YesNo. MessageBoxIcon.Questi on, 5 287, продолжение
130 Глава 7. Разработка приложений Листинг 7.14 (продолжение) MessageBoxDefaultButton.Button2) — DialogResult.Yes) File.Delete(dest); else return; } // Перемещаем или копируем string s = path. Substrings, path.Length - 1); swi tch(cli pboa rdActi on) { case ClipboardAction.Cut: Fi1e.Move(cli pboardF i1eName. dest); break; case ClipboardAction.Copy: File.Copy(clipboardFileName, dest. false); break; } clipboardAction = ClipboardAction.None; clipboardFileName = string.Empty; fillListO; } Перед тем как вставить файл в другую папку, нужно удостоверить- ся, что в ней нет файла с таким именем. Если же такой файл суще- ствует, то надо предупредить пользователя и узнать, что он хочет сделать. Код для команды Вставить ярлык приведен в листинге 7.15. Листинг 7.15 private void pasteShortcutMenuItem Click(object sender. System.EventArgs e) { int i - 2; string s = string.Empty; string dest; while(true) { dest = path + "Shortcut" + s + " to " + Path.GetFi1eName(Path.GetFi1eNameWi thoutExtensi on (clipboardFileName) + ".Ink"); i f (!Fi1e.Exi sts(dest)) break; s = " (" + i.ToStringO + ")";
Файловый менеджер для смартфона 131 } Streamwriter sw = new StreamWriter(dest): s = clipboardFileName; if(s.IndexOf(" ") > 0) s = "V" + s + s = s. Length. ToStringO + "#" + s; sw.WriteLine(s); sw.CloseO; fillListO; } В этом коде создается уникальное имя ярлыка, которое затем запи- сывается в виде файла с добавлением. К имени ярлыка добавляет- ся расширение .LNK. Код для команды Переименовать приведен в листинге 7.16. Листинг 7.16 private void renameMenuItem_Click(object sender. System.EventArgs e) { Cursor.Current - Cursors.WaitCursor; ListViewltern Ivi - 1i stVi ew.Iterns[1i stVi ew.SeiectedIndi ces[0]]; bool isFolder = Ivi.Imageindex = 0; string s; if(isFolder) s = "папку"; else s = "файл"; NameForm nameForm - new NameForm(this, "Переименовать " + s. Ivi.Text, new SetNameDelegate(SetRename)); if (nameForm. ShowDi al ogO = Di al ogResul t. OK) fillListO; 1istView.Focus(); } Сначала обрабатывается текущий выделенный элемент. Если пользо- ватель выделил папку, то для формы nameForm задается соответству- ющий заголовок Переименовать папку. Также из этой формы передает- ся в основную форму новое имя папки или файла с помощью метода Set Rename, как это показано в листинге 7.17. Листинг 7.17 /// <summary> /// Метод для переименования папки или файла л продолжение & 5*
132 Глава 7. Разработка приложений Листинг 7.17 (продолжение) /// </summary> /// <param пате="пате">Имя папки или файла</рагат> public void SetRename(string name) { ListViewItem Ivi = 1i stVi ew.Iterns[1i stV1ew.Seiectedlndi ces[0]]: bool isFolder » Ivi.ImageIndex = 0: string itemName = path + Ivi.Text; string destName = Path.GetDirectoryName(itemName) + Path.DirectorySeparatorChar.ToStringO + name; if(isFolder) Di rectory.Move(itemName, destName); else File.Move(itemName, destName); } После того как будет получена информация о выделенном элемен- те, он переименовывается. Для реализации команды Удалить исполь- зуется код, приведенный в листинге 7.18. Листинг 7.18 private void deleteMenuItem_Click(object sender. System.EventArgs e) { ListViewItem Ivi = 1istView.Iterns[1istView.SeiectedIndices[0]]; bool isFolder = Ivi.ImageIndex = 0; string s = "Are you sure you want to delete " + Ivi.Text: if(isFolder) s += ” and all its content"; s += "?"; if(MessageBox.Show(s, this.Text, MessageBoxButtons.YesNo, MessageBoxIcon.Questi on. MessageBoxDefaultButton.Button2) == DialogResult.Yes) { if(isFolder) Directory.Delete(path + Ivi.Text, true); el se File.Delete(path + Ivi.Text); fillListO; } }
Файловый менеджер для смартфона 133 Перед удалением папки или файла запрашивается подтверждение действий пользователя. Для создания новой папки используется следующий код, приведенный в листинге 7.19. Листинг 7.19 private void newFolderMenuItem_Click(object sender, System.EventArgs e) { Cursor.Current = Cursors.WaitCursor; ListViewItem Ivi = 1i stVi ew.Iterns[1i stVi ew.SeiectedIndi ces[0]]; NameForm nameForm = new NameForm(this. "Новая папка", new SetNameDelegate(SetNewName)); if(nameForm.ShowDialog() == DialogResult.OK) fillListO: listView.FocusO; } В результате действия этой функции отображается форма NameForm с заголовком Новая папка. Эта форма также передает информацию в главную форму при помощи метода SetNewName, который приведен в листинге 7.20. Листинг 7.20 /// <summary> /// Устанавливает новое имя для папки /// </summary> /// <param пате="пате">Имя для папки</рагат> public void SetNewName(string name) { Di rectory.CreateDirectory(path + name); } Метод создает папку с заданным именем. Как видно, код его чрез- вычайно прост. Код для выполнения команды Свойства приведен в листинге 7.21. Листинг 7.21 private void propertiesMenuItem_Click(object sender. System.EventArgs e) { Cursor.Current « Cursors.WaitCursor; продолжение
134 Глава 7. Разработка приложений Листинг 7.21 (продолжение) ListViewItem Iv1 = 1i stVi ew.Iterns[1i stVi ew.Seiectedlndi ces[0]]: Fileinfo fi = new Filelnfo(path + Ivi.Text): PropertiesForm propertiesForm = new PropertiesForm(this, fi. new SetNameDelegate(SetRename), new SetAttri butesDelegate(SetAttri butes)): if(propertiesForm.ShowDialog() — Di alogResult.OK) fillListO; listView.FocusO; } Этот код вызывает форму Properti esForm, которая отображает атрибу- ты выбранного файла или папки. Также в этой форме пользователь может изменять атрибуты файла при помощи метода SetAttri butes, код которого приведен в листинге 7.22. Листинг 7.22 public void SetAttributes(FileAttributes fileAttributes) { ListViewItem Ivi - listView.Itemsfli stVi ew.Seiectedlndi ces[0]]: bool isFolder = Ivi.Imageindex = 0: if(isFolder) { Directoryinfo di = new Directorylnfo(path + Ivi.Text); di.Attributes = fileAttributes: } el se { Fileinfo fi = new Filelnfo(path + Ivi.Text); fi .Attributes = fileAttributes; } } Для создания градиентной заливки соответствующего элемента ин- терфейса применяется метод, код которого приведен в листинге 7.23. Листинг 7.23 public static void SetGradient(System.Windows.Forms.ListView listView) { // Новый вариант // Для .NET Compact Framework 2.0
Диспетчер задач 135 SendMessage(1istView.Handle, LVMSETEXTENDEDLISTVIEWSTYLE, 0, LVS_EX_GRADIENT); listView.Refresh!); } Итак, основные трудности реализации программы рассмотрены. Кроме того, в примере присутствуют вызовы функций Windows API для работы с табличным списком ListView. Эти примеры рассмат- ривались в главе 4, поэтому не стоит повторять их разбор. На са- мом деле эту программу можно улучшать до бесконечности, добав- ляя новые функциональные возможности. Надеюсь, у вас это получится. Буду рад, если вы пришлете свои варианты примеров, которые, на ваш взгляд, украсят программу. Диспетчер задач Но мы с вами не расстаемся с программами, написанными Кристиа- ном Форсбергом. На его сайте можно найти еще одну полезную программу, необходимую как разработчику, так и пользователю. Это Диспетчер задач (Task Manager). Программа подобного рода тоже отсутствует в стандартной поставке Windows Mobile. А ведь эта программа очень полезна в работе. Владелец смартфона под уп- равлением системы Windows Mobile может узнать много нового о своей системе после запуска этой утилиты. Диспетчер задач по- кажет все программы, которые размещаются в памяти смартфона, отбирая системные ресурсы. Диспетчер задач также позволяет уда- лять из памяти ненужные программы и процессы. Не случайно, многие производители сами снабжают свои устройства программа- ми подобного типа. Если вам не повезло и у вас нет такой програм- мы, то вы можете сами написать Диспетчер задач. Как и предыдущий пример, оригинальная версия программы была написана на Visual Studio 2003 для смартфонов под управлением Windows Mobile 2003 на платформе .NET Compact Framework 1.0. Следуя нашей традиции, я с согласия автора конвертировал проект для Visual Studio 2005 для целевой системы Windows Mobile 5.0 и с применением .NET Compact Framework 2.0. Графический интерфейс программы Диспетчер задач при запуске показывает список запущенных про- грамм (рис. 7.5).
136 Глава 7. Разработка приложений Рис. 7.5. Внешний вид программы С помощью меню, размещенного в левой части окна, можно акти- вировать выбранное приложение. При этом сам менеджер задач закрывается. Меню, расположенное в правой части окна, предос- тавляет пользователю несколько больше возможностей. Команды этого меню приведены в следующем списке: □ Обновить — обновляет список запущенных программ; □ Процессы — показывает список запущенных процессов; □ Остановить — останавливает выбранную программу; □ Остановить все — останавливает все запущенные программы; □ Вид — показывает информацию о процессе; □ Убить — закрывает процесс; □ 0 программе — выводит информацию об авторе программы; □ Готово — закрывает программу. Внешний вид этого меню показан на рис. 7.6. Рис. 7.6. Команды меню для правой кнопки Код программы При активации основной формы Mai nForm программа получает спи- сок запущенных программ при помощи процедуры fi 1 ITaskLi st, код которой приведен в листинге 7.24.
Диспетчер задач 137 Листинг 7.24 private void fi1ITaskList() { Cursor.Current = Cursors.WaitCursor: // Получим список запущенных приложений windows - WindowHelper.EnumerateTopWindowsO; // Заполняем ListView ListViewItem Ivi: 1i stVi ew.Begi nUpdate(); listView. Items. ClearO; foreach(Window w in windows) { Ivi = new ListViewltem(w.ToStringO); listView.Items.Add(lvi); } listView.EndUpdate(); if(listView.Items.Count > 0) { listview.Iterns[0].Selected = true: listView.Items[0].Focused = true; } Cursor.Current = Cursors.Default; } Данная процедура использует класс WindowHelper, который позво- ляет получить информацию о запущенных приложениях. В листин- ге 7.25 приведен код метода EnumerateTopWindows, который находит все окна запущенных в системе приложений. Листинг 7.25 public static WindowE] EnumerateTopWindowsО { ArrayList windowList = new ArrayListO; IntPtr hWnd = IntPtr.Zero; Window window = null; // Получим первое окно hWnd = GetActiveWindowO; hWnd = GetWindow(hWnd. GW HWNDFIRST); while(hWnd != IntPtr.Zero) { if(IsWindow(hWnd) && IsWindowVisible(hWnd)) { IntPtr parentWin - GetParent(hWnd); if ((parentWin — IntPtr.Zero)) { продолжение
138 Глава 7. Разработка приложений Листинг 7.25 (продолжение) int length = GetWindowTextLength(hWnd): if (length > 0) { string s = new string('\0', length + 1): GetWindowText(hWnd, s, length + 1): s = s.Substrings, s.lndex0f('\0')); if(s != "Tray" && s != "Start" && s != "Task Manager”) { window = new WindowO; window.Handle = hWnd; window.Text = s; windowList.Add(window); } } } } hWnd = GetWindowChWnd, GWHWNDNEXT); } return (Window[])windowList.ToArray(typeof(Window)); } В этом методе вызываются функции Windows API, с помощью ко- торых можно получить список всех открытых окон. Все обнаружен- ные окна добавляются в список, если они удовлетворяют некото- рым условиям. Добавляемые окна не должны иметь родительских окон, они должны быть видимыми и иметь заголовок. При этом сам Диспетчер задач не должен попасть в этот список. Все остальные окна записываются в массив. Активация и закрытие приложения Для активации запущенного приложения вызывается функция Win- dows API SetForegroundWi ndow, которая использует дескриптор окна. Для закрытия приложения используется функция SendMessage с соответ- ствующим сообщением закрытия WM CLOSE. Для закрытия сразу всех окон можно использовать функцию Windows API SHCloseApps, кото- рая закрывает все запущенные программы, кроме самого Диспетчера задач. Код, выполняющий эти действия, приведен в листинге 7.26. Листинг 7.26 public static void ActivateWindowCIntPtr hWnd) {
Диспетчер задач 139 // Активируем приложение SetForegroundWindow(hWnd); } public static void CloseWindow(IntPtr hWnd) { // Закрываем приложение SendMessage(hWnd, WM_CLOSE, 0, 0); } public static void CloseAppsO { 11 Закрываем все приложения SHC1oseApps(i nt.MaxValue): } Перечисление процессов Для отображения списка процессов используется функция, код которой приведен в листинге 7.27. Листинг 7.27 private void fillProcessListO { Cursor.Current » Cursors.WaitCursor; // Получаем список запущенных процессов processes = Process.GetProcessesO; // Заполняем ListView ListViewItem Ivi: 1i stVi ew.Begi nUpdate(); 1i stVi ew.Iterns.Clear(); foreach(Process p in processes) { Ivi = new ListViewItem(p.ProcessName): //Ivi .SubItems.AddCID"); 1i stVi ew.Iterns.Add(1vi); } listView.EndllpdateO; if(listView.Items.Count > 0) { listView.Items[0].Selected = true: listView.Items[0].Focused = true; } Cursor.Current - Cursors.Default; }
140 Глава 7. Разработка приложений Список активных процессов извлекается при помощи класса Process. Основой класса является метод GetProcesses, приведенный в листинге 7.28. Листинг 7.28 public static Process[] GetProcessesО { ArrayList procList = new ArrayListO: IntPtr handle = CreateToolhelp32Snapshot(TH32CS_SNAPPR0CESS, 0); if((int)handle > 0) { try { PROCESSENTRY32 peCurrent; PR0CESSENTRY32 pe32 » new PR0CESSENTRY32O: byte[] peBytes = pe32.ToByteArray(): int retval = Process32First(handle, peBytes): while(retval — 1) { peCurrent = new PR0CESSENTRY32(peBytes): Process proc = new Process(new IntPtr!(int)peCurrent.PID), peCurrent.Name, (i nt)peCurrent.ThreadCount. (int)peCurrent.BaseAddress): procList.Add(proc); retval = Process32Next(handle, peBytes); } } catch(Except!on ex) { throw new Exception("Exception: " + ex.Message): } CloseToolheip32Snapshot(handle); return (Process[])procList.ToArray(typeof(Process)): } else { throw new Exception("Unable to get processes!"): } } С помощью данного метода можно узнать детальную информацию о каждом процессе.
Маленький блокнот 141 Закрытие процесса Чтобы закрыть процесс, используется метод Kill, код которого при- веден в листинге 7.29. Листинг 7.29 public void Kill() { IntPtr hProcess: hProcess = OpenProcess(PROCESS TERMINATE, false, (int) processld); if(hProcess != (IntPtr) INVALIDHANDLEVALUE) { bool bRet; bRet = TernnnateProcess(hProcess. 0); CloseHandle(hProcess); } } Данный метод также использует вызовы функций Windows API. Функция OpenProcess получает дескриптор процесса, который затем передается функции Terml nateProcess для уничтожения процесса. Код, отвечающий за внешний вид элемента управления ListView, полностью идентичен коду из предыдущего примера, поэтому его можно просто скопировать и не рассматривать отдельно. Теперь с помощью Диспетчера задач пользователь сможет узнать список запущенных программ и процессов и даже управлять ими. Маленький блокнот Однажды мой друг, далекий от программирования, попросил меня написать простенький текстовый редактор для карманного ком- пьютера. Его не совсем устраивало приложение Word Mobile, ко- торое используется для работы с текстовыми файлами в операци- онной системе Windows Mobile. Заказчик хотел получить только основные функции стандартного Блокнота из Windows ХР, то есть копирование, вырезание, вставку и удаление текста. Также он хо- тел обойтись без установки .NET Compact Framework 2.0, так как устаревшая модель его карманного компьютера обладала малой емкостью памяти. В рамках решения поставленной задачи и была написана програм- ма Блокнотик, которая и будет рассматриваться в этом разделе главы.
142 Глава 7. Разработка приложений Единственная сложность при написании данного текстового ре- дактора состояла в том, что библиотека .NET Compact Framework 1.0 не поддерживает работу с буфером обмена на уровне управля- емого кода. Поэтому пришлось прибегать к вызовам функций Windows API. Данный пример можно использовать в качестве основы для тех, кто хочет написать свой текстовый редактор для .NET Compact Framework 1.0. Надо заметить, что если бы я стал писать свой при- мер с использованием .NET Compact Framework 2.0, то справиться с задачей было бы гораздо легче, так как вторая версия библиотеки поддерживает буфер обмена, который так необходим при операци- ях с текстом. Первые шаги После запуска Visual Studio .NET 2005 надо создать новый проект. При выборе типа проекта надо указать, что будет использоваться .NET Compact Framework 1.0. Для начала на форме следует разме- стить текстовое поле с именем txt Ed i tor. Для свойства Multiline надо задать значение True, а свойство Scrol 1 Bars получит значение Both. Так как текстовое поле обычно занимает все пространство формы, его нужно вручную растянуть до нужного размера. Учитывая, что я писал программу для конкретной модели мобильного устройства, большой ошибки в моих действиях не было. Но не будем забывать, что существуют другие устройства, размеры экрана у которых будут другими. Поэтому стоит устанавливать размеры элементов про- граммно в соответствии с текущими размерами формы. Также на первом этапе разработки надо указать позицию текстового поля и установить в нем фокус. Соответствующий код был добавлен в обработчик события Form_Load, что иллюстрирует листинг 7.30. Листинг 7.30 private void MainForm_Load(object sender, EventArgs e) { // устанавливаем позицию текстового поля txtEditor.Location = new Point(0, 0); // Приравниваем размеры текстового поля к размерам формы txtEditor.Width = this.Width; txtEditor.Height = this.Height; И Устанавливаем фокус txtEdi tor. FocusO; }
Маленький блокнот 143 Если бы программа создавалась для настольного компьютера, то на- писанный код не вызывал бы никаких сомнений. Но у КПК нет внешней клавиатуры, и для ввода текста используется панель вво- да SIP. Поэтому на форму надо добавить элемент InputPanel. Так как при активации панель ввода закроет часть формы, то надо на- писать код для вычисления высоты текстового поля для этого слу- чая и соответствующим образом изменить обработчик события Form Load, как показано в листинге 7.31. Листинг 7.31 private void MainForm_Load(object sender, EventArgs e) { // Высоту текстового поля устанавливаем в зависимости от SIP //txtEditor Height = this.Height; SetTextBoxHei ght(); } // устанавливаем размеры текстового поля в зависимости от // активности SIP private void SetTextBoxHeight() { if (SIP.Enabled) txtEditor.Height = SIP.Visi bl eDesktop.Height + 2; else txtEditor.Height = this.Height; } private void SIP EnabledChanged(object sender, EventArgs e) { SetTextBoxHeight(); } Стандартные операции с текстом На данной стадии уже создан первый прототип приложения. После запуска программы пользователь может вводить и удалять текст с по- мощью SIP. Но этого недостаточно для комфортной работы с текстом. Пользователь должен иметь возможность манипулировать текстом, то есть копировать часть текста, вырезать его, удалять и вставлять в нужную позицию. Для этого надо создать меню при помощи эле- мента mai nMenu. В подменю Правка надо добавить стандартные коман- ды редактирования текста — Отменить, Вырезать, Копировать, Вставить. Если бы приложение создавалось для среды исполнения .NET
144 Глава 7. Разработка приложений Compact Framework 2.0, то можно было бы использовать класс Clipboard. Но так как используется .NET Compact Framework 1.0, то придется обратиться к функциям Windows API и использовать не- управляемый код, что иллюстрирует листинг 7.32. Листинг 7.32 // сообщения буфера обмена public const int WMCUT = 0x0300; public const int WM_COPY = 0x0301: public const int WM PASTE = 0x0302 public const int WM CLEAR = 0x0303 public const int WM UNDO = 0x0304; // функции API [DllImport!"coredl!.dll”. CharSet = CharSet.Unicode)] public static extern IntPtr GetFocusO; [DI1 Import("coredl1.dl1")] public static extern int SendMessage(IntPtr hWnd, uint Message, uint wParam, uint 1 Param); private void mnuCutCl ick (object sender. EventArgs e) { // Вырезаем текст SendMessage(hwndEditor. WM CUT, 0, 0); } private void mnuUndoCl ick (object sender, EventArgs e) { 11 Отменяем последнее действие SendMessage(hwndEditor, WMUNDO, 0, 0); } private void mnuCopy_Click(object sender, EventArgs e) { // Копируем выделенный текст SendMessage(hwndEditor, WM_COPY, 0. 0); } private void mnuPaste Click(object sender, EventArgs e) { // Вставляем текст из буфера обмена SendMessage(hwndEditor, WMPASTE, 0, 0); } private void mnuDelete Click(object sender. EventArgs e) {
Маленький блокнот 145 // Удаляем выделенный текст SendMessageChwndEditor, WMCLEAR, 0, 0); } Теперь необходимо добавить в создаваемое приложение поддержку контекстного меню. Использование контекстного меню избавит пользователя от необходимости постоянно переводить стилус в нижнюю часть экрана для доступа к командам меню. В программу нужно добавить элемент управления ContextMenu и сделать список команд меню, который будет дублировать подпункт основного меню Правка. Созданное контекстное меню надо связать с текстовым по- лем при помощи свойства ContextMenu. Осталось только скопировать код из команд основного меню в соответствующие места для команд контекстного меню. Например, для команды контекстного меню Ко- пировать надо использовать код, приведенный в листинге 7.33. Листинг 7.33 private void cmenuCopy_Click(object sender, EventArgs e) { // Копируем выделенный текст SendMessageChwndEditor, WM_COPY, 0, 0): } Мы сделали еще один шаг вперед. Теперь наш маленький блокнот умеет работать с текстом. Но приложение нужно еще немного до- работать. Например, пользователь может во время работы с блок- нотом переключиться на другую программу и скопировать в буфер обмена картинку, а затем вернуться обратно к текстовому редакто- ру. Конечно, картинку нельзя вставить в текстовое поле. Поэтому надо проверить тип содержимого в буфере обмена, и если там со- держатся не текстовые данные, то нужно заблокировать пункт меню Вставить. Для этого можно использовать функцию IsClip- boa rdFormatAvai 1 abl е, а проверку данных в буфере обмена выполнять в событии Popup, как показано в листинге 7.34. Листинг 7.34 [DI1 Import("Coredl1.dll")] private static extern bool IsClipboardFormatAvailableCuint uFormat); // константа для буфера обмена private const uint CFJJNICODETEXT = 13; public static bool IsTextO { продолжение &
146 Глава 7. Разр< ботка приложений Листинг 7.34 (продолжение} try { return IsClipboardFormatAvailable(CFUNICODETEXT); } catch (Exception ex) { MessageBox.ShowCHe могу понять, что содержится в буфере обмена!"); return false; } } private void mnuEdit_Popup(object sender, EventArgs e) { if (IsTextO) mnuPaste.Enabled = true; el se mnuPaste.Enabled = false; } Подобные изменения надо сделать и для пунктов меню. Если пользователь не выделил часть текста, то пункты Вырезать, Копиро- вать и Удалить также должны быть заблокированы. Код, реализую- щий эту функциональность, приведен в листинге 7.35. Листинг 7.35 //Если текст выделен if (txtEditor.SeiectionLength > 0) { mnuCut.Enabled = true; mnuCopy.Enabled = true; mnuDelete.Enabled = true; } else { mnuCut.Enabled = false; mnuCopy.Enabled » false; mnuDelete.Enabled = false; } Следующим шагом в развитии программы будет добавление фай- ловых операций. Работа с текстовым редактором предполагает не только правку текста, но и сохранение текста в файле, а также чте- ние данных из файла. Для этого в меню создаются соответствую-
Маленький блокнот 147 щие команды Создать, Открыть, Сохранить и Сохранить как. Код, свя- занный с этими командами, приведен в листинге 7.36. Листинг 7.36 private void mnuOpenCl ick (object sender. EventArgs e) { dlgOpenFile.Filter = "Текстовые документы (*.txt)|*.txt|Bce файлы I*.*"; dlgOpenFi1e.ShowDi alog(); if (File.Exists(dlgOpenFile.FileName)) { fname = dlgOpenFile.FileName: StreamReader sr = new StreamReader(fname, System.Text.Encodi ng.GetEncodi ng("Wi ndows-1251"). false): txtEdi tor. Text = sr.ReadToEndO; flag = false: sr.CloseO: } } private void mnuSaveAs_Click(object sender, EventArgs e) { SaveFileDialog dlgSaveFile = new SaveFileDialogO; dlgSaveFile.Filter « "Текстовые документы (*.txt)|*.txt|Bce файлы I*.*"; dlgSaveFi1e.ShowDi alog(); fname - dlgSaveFile.FileName; savedataO: } private void savedataO { if (fname = "") { SaveFileDialog dlgSaveFile = new SaveFileDialogO; dlgSaveFile.Filter = "Текстовые документы (*.txt)|*.txt| Все файлы I*.*"; Di al ogResult res = dl gSaveFi le. ShowDi al ogO; if (res — DialogResult.Cancel) { return: } fname - dlgSaveFile.FileName; MessageBox.Show(fname); продолжение &
148 Глава 7. Разработка приложений Листинг 7.36 (продолжение) } StreamWriter sw = new StreamWriter(fname, false, System.Text.Encodi ng.GetEncodi ng("Wi ndows-1251")): sw.WriteLine(txtEditor.Text): sw.FlushO; sw.CloseO: flag = false: } private void mnuSave_Click(object sender, EventArgs e) { savedataO: } private void txtEditor_TextChanged(object sender, EventArgs e) { flag = true: } Работа с файлами в .NET Compact Framework не отличается от мето- дов работы с файлами в полной версии .NET Framework, поэтому заострять внимание на этом коде не нужно. Осталось только доба- вить в программу некоторые детали, которые придают программе профессиональный вид. Нужно присоединить собственную пикто- грамму приложения, а также добавить диалоговое окно 0 программе с упоминанием автора программы и логотипом фирмы. Безусловно, вы можете наделить текстовый редактор новыми возможностями или расширить его функциональность. Например, для сохранения и от- крытия файлов я использовал стандартные диалоговые окна, кото- рые работают с файлами в пределах папки Мои документы. Но исполь- зуя код ранее созданного файлового менеджера, можно научить приложение сохранять и открывать файлы в любом месте файловой системы. Также можно доработать меню Формат, позволяющее рабо- тать с различными кодировками текста. Распространение приложений Даже если вы написали очень полезную программу, она не сможет обрести всемирную известность, пока вы держите ее на своем ком- пьютере. Нужно все же распространить программу и, если она не бес- платная, то и немного заработать на отпуск. Программы для настоль- ных компьютеров распространять довольно просто. Нужно лишь
Распространение приложений 149 создать специальный проект для создания установочного пакета, ко- торый сгенерирует специальный файл установки Microsoft Installer (MSI). К сожалению, для мобильных устройств процесс создания установочных файлов немного отличается. В процессе распростра- нения программы участвуют три составляющие: настольный компь- ютер, программа синхронизации Microsoft ActiveSync и программа wceload.exe для извлечения файлов из cab-файлов. Для пользователя процесс установки программы не сильно отлича- ется от привычной схемы. Сначала он скачивает программу или на- ходит ее на компакт-диске. Затем запускает установочный msi-файл. Программа Microsoft Installer с помощью специального мастера ус- тановки помогает пользователю установить программу с нужными настройками. После этого программа считается установленной, и пользователь может запускать ее. Создание саЬ-файла Прежде чем установочный пакет попадет в руки пользователя, нужно хорошенько поработать над его созданием. Устройства под управле- нием Windows Mobile не могут напрямую работать с файлами .msi. Вместо этого используются кабинетные файлы с расширением .cab. Таким образом, задача программиста заключается в том, чтобы соста- вить список команд для программы синхронизации ActiveSync, кото- рые позволят скопировать cab-файлы на устройство с учетом необхо- димых установок. Для создания удобного установочного пакета с интуитивно понятным интерфейсом вам необходимо выполнить не- хитрую последовательность действий. 1. Создать cab-файл для устройства. 2. Добавить в cab-файл дополнительные файлы, используемые программой, например изображения или файлы с данными. 3. Добавить в cab-файл инструкции для записи в реестр. 4. Зарегистрировать cab-файл с помощью ActiveSync, чтобы пользо- ватель мог установить приложение с настольного компьютера. 5. Написать код для различных дополнительных возможностей, которые будут использоваться установочным пакетом во время установки или деинсталляции. 6. Упаковать все необходимые файлы в один специальный файл установки с расширением .msi. Вы, вероятно, знаете, что кабинетный файл является специальным файлом упаковки и компрессии, с помощью которого можно ежи-
150 Глава 7. Разработка приложений мать файлы, что приведет к уменьшению их размеров. Также в этом файле могут содержаться инструкции для внесения изменений в ре- естр системы. За обработку cab-файлов на устройстве отвечает ути- лита wceload.exe, входящая в состав Windows Mobile. Создание проекта Приступим к разработке проекта для создания установочного пакета. Прежде всего нужно запустить уже существующий проект, который планируется подготовить для распространения. В качестве примера будет использоваться проект SmallNotepad. Затем нужно выполнить ко- манду меню File ► Add ► New Project. В открывшемся диалоговом окне надо перейти в раздел Other Project Types, выбрать тип Smart Device Cab Project и задать имя нового проекта DeployNotepadCab (рис. 7.7). Рис. 7.7. Выбор нового проекта для распространения приложения В окне свойств надо задать значения свойств Manufacturer и ProductName. Другие свойства позволяют задать минимальные и максимальные тре- бования к операционным системам, в которых может быть запущена ваша программа. Затем надо запустить редактор File System Editor, нажав соответству- ющую кнопку в окне свойств. Нужно выбрать пункт Application Folder и в контекстном меню выбрать пункт Add ► Project Output (рис. 7.8).
Распространение приложений 151 г la.1 Mot^safl„CS .V Isual Studio ete ЙЙ ‘flew Protect &AJ Debug T©c& Acton Wdow Community a. Рис. 7.8. Выбор параметров проекта В результате этого будет открыто диалоговое окно Add Project Output Group (рис. 7.9). Рис. 7.9. Диалоговое окно Add Project Output Group С помощью данного окна можно выбрать различные типы файлов, необходимые для программы, такие как файлы документации или, например, локализированные ресурсы. Нужно выбрать пункт Primary Output и нажать кнопке ОК. В правой части окна следует щелк- нуть правой кнопкой мыши на единственном пункте Primary output from SmaU.Notepad_CS и в контекстном меню выбрать пункт Create Shortcut to Primary output from SmallNotepad_CS (рис. 7.10). Это позво- лит включить пиктограмму в список файлов для распространения.
152 Глава 7. Разработка приложений Созданный ярлык надо переместить мышью в папку Program Files Folder. Теперь можно приступать к созданию установочного файла. Рис. 7.10. Создание пиктограммы приложения В меню надо выполнить пункт Build ► Build DeployNotepadCab. После этого среда разработки создаст специальный файл с расширением .САВ. При помощи файлового менеджера его нужно найти и запом- нить его расположение. Теперь надо установить созданный файл на эмуляторе. Для этого выполняется команда меню Tools ► Device Emulator Manager. В диало- говом окне надо выбрать эмулятор. Например, Pocket PC 2003 SE Emulator. В этом же окне следует выполнить команду меню Actions ► Connect. При этом выбранный эмулятор будет активирован. Рис. 7.11. Активация и настройка эмулятора
Дополнительные материалы 153 В окне эмулятора надо выполнить команду меню File ► Configure. После этого откроется окно настроек эмулятора, в котором следует перейти в раздел Shared Folder. В этом разделе надо выбрать папку, в которой находится созданный cab-файл (рис. 7.11). Эмулятор бу- дет считать, что данная папка является карточкой памяти. Если открыть в эмуляторе программу File Explorer (Start ► Programs ► File Explorer) и найти папку Storage Card, то в ней можно будет увидеть ранее созданные установочные файлы. Нужно выбрать файл DeployNotepadCab и запустить его. В результате начнется процесс установки программы на устройство. При установ- ке автоматически будет создан файл деинсталляции. Он поможет кор- ректно удалить приложение. Для этого в окне эмулятора надо выпол- нить команду меню Start ► Settings ► System ► Remove Program. В списке установленных программ надо найти ранее установленное приложе- ние, выделить его и нажать кнопку Remove (рис. 7.12). Рис. 7.12. Деинсталляция приложения В результате этого действия будет запущен мастер удаления про граммы, который корректно удалит все файлы, относящиеся к при- ложению. На этом изучение примера создания установочного фай- ла можно считать законченным. Дополнительные материалы На сайте MSDN есть очень подробная статья «Deploying .NET Compact Framework 2.0 Applications with .cab and .msi Files», в кото- рой приведены дополнительные сведения о создании и распростра- нении установочных файлов. Стоит ознакомиться с данным материа- лом, чтобы использовать все возможности установочных файлов.
т~* о Глава» Эмулятор и другие утилиты Программы для отладки приложений В этой главе речь пойдет об утилитах, необходимых для успешного программирования приложений для мобильных устройств. Пожа- луй, самой главной из этих утилит является программный эмуля- тор Device Emulator. Кроме того, в состав Visual Studio 2005 входит несколько вспомогательных утилит, позволяющих выполнять раз- личные операции на реальном устройстве или на эмуляторе. Эмулятор При создании приложений для КПК и смартфонов необходимо про- верять работу написанной программы на устройстве, которое силь- но отличается от настольного компьютера. Когда вы пишете стан- дартное Windows-приложение, вы можете сразу увидеть его работу, запустив соответствующий исполняемый файл. Написав програм- му для мобильных устройств, необходимо протестировать ее на со- ответствующем устройстве, так как ваш настольный компьютер здесь уже не поможет. Но даже если разработчик еще не приобрел карманный компьютер или смартфон под управлением Windows Mobile, то он все равно может тестировать свои приложения. В этом случае надо проверять их работоспособность на специальных эму- ляторах. Следует отметить, что в некоторых случаях эмулятор все-таки не сможет выполнить эту задачу. Например, он не поможет проверить работу кода, который использует возможности инфракрасной свя- зи. И, тем не менее, эмулятор является очень мощным и удобным инструментом для отладки приложений. Надо сказать, что качество и возможности эмулятора постоянно улучшаются и совершенствуются. Разработчики, которые програм- мировали еще на eMbedded Visual Basic и Visual Studio 2003, без со- мнения, обратят внимание на возросшую скорость работы эмулято- ра, его надежность и удобство. Эмулятор, поставляемый с Visual Studio 2005, имеет улучшенную поддержку общих папок, програм-
Запуск эмулятора 155 мы синхронизации ActiveSync и последовательных портов. Также эмулятор поддерживает альбомную и книжную ориентацию. Рань- ше об этом приходилось только мечтать. Особенно приятно отме- тить тот факт, что можно дополнительно скачать локализованные версии эмуляторов. Например, все примеры для Windows Mobile 5.0 тестировались исключительно на русской версии эмулятора. Запуск эмулятора Итак, при написании своей программы у разработчика есть возмож- ность выбирать, где тестировать свой код. Как правило, программу сначала проверяют на эмуляторе. Это позволяет быстро исправить ошибки и устранить недочеты. А уже окончательную версию про- граммы можно и нужно проверить на реальном устройстве. Рассмотрим вариант запуска эмулятора и его настройки. Сначала требуется создать или открыть проект, предназначенный для мо- бильного устройства, например, первую программу «Здравствуй, мир», которая создавалась в главе 2. После выполнения команды меню Debug ► Start Debugging среда разработки Visual Studio отобра- жает диалоговое окно Deploy (рис. 8.1). Рис. 8.1. Запуск эмулятора В диалоговом окне отображается список, в котором содержатся одно реальное устройство и четыре эмулятора разных типов устройств. Нужно выбрать любой эмулятор из списка. Стандартным выбором в данном случае является значение Pocket PC 2003 SE Emulator. Нуж- но выделить строку с выбранным эмулятором и нажать кнопку
156 Глава 8. Эмулятор и другие утилиты Deploy. Через несколько секунд на экране компьютера появится эмулятор карманного компьютера, в котором будет запущено вы- бранное приложение. Программист может работать с тестируемой программой так же, как и на реальном устройстве. Кроме того, мож- но оставить в покое программу и запустить любое приложение, ко- торое есть на этом эмуляторе. ПРИМЕЧАНИЕ------------------------------------------- Списки эмуляторов на каждом компьютере разработчика могут различаться, так как можно скачать и установить дополнитель- ные эмуляторы Когда будут рассматриваться примеры для уст- ройств под управлением Windows Mobile 5.0, диалоговое окно будет содержать уже другие эмуляторы. После того как тестирование программы будет завершено, вам надо остановить выполнение программы при помощи команды меню Stop debugging в среде разработки. При этом не стоит закрывать само окно эмулятора, как часто делают начинающие программисты. Если оставить эмулятор работать, то это позволит потратить меньше вре- мени на повторную загрузку эмулятора при следующей отладке программы. ПРИМЕЧАНИЕ------------------------------------------- Если ваша программа имеет код для закрытия приложения this. Close (), то режим отладки автоматически остановится и выполнять ко- манду меню Stop debugging не понадобится. Настройка эмулятора Попробуем теперь поработать с различными настройками эмуля- тора Для начала следует выполнить команду меню Tools ► Options. В открывшемся диалоговом окне Options надо выбрать строку Device Tools, а в ней активировать пункт Devices. Затем в списке Devices надо выбрать элемент Pocket PC 2003 SE и нажать кнопку Properties (рис. 8.2). На экране появится новое диалоговое окно Pocket PC 2003 SE Properties (рис. 8.3). Обратите внимание на то, что по умолчанию программа устанавливается в папку Program Files. После ознакомления со свойствами эмулятора нужно закрыть все диалоговые окна и вернуться в главное окно среды разработки. Там
Настройка эмулятора 157 надо выполнить команду меню Tools ► Device Emulator Manager. На экране откроется новое диалоговое окно, в котором будут перечис- лены все имеющиеся эмуляторы (рис. 8.4). Рис. 8.2. Окно .настроек эмулятора Рис. 8.3. Окно свойств эмулятора Надо выбрать из списка элемент Pocket PC 2003 SE Emulator, а затем выполнить команду меню Actions ► Connect. Менеджер эмуляторов загрузит выбранный эмулятор. На экране появится специальный значок, который сигнализирует об установленном соединении. За-
158 Глава 8. Эмулятор и другие утилиты тем нужно выполнить команду меню Actions ► Cradle. Если опера- ция пройдет успешно, то значок состояния эмулятора изменится. Это означает, что эмулятор КПК соединен с виртуальной док-стан- цией. Теперь можно синхронизировать данные с помощью програм- мы синхронизации ActiveSync. По завершении операции нужно выполнить команду меню Actions ► Uncradle. Рис. 8.4. Список установленных эмуляторов Эмуляция карточки памяти Все модели карманных компьютеров и смартфонов имеют возмож- ность увеличения объема памяти при помощи различных карточек памяти. Дополнительный объем дискового пространства исполь- зуют для хранения фильмов, фотографий и других документов. Особенно это актуально для пользователей устройств под управле-
Эмуляция карточки памяти 159 нием Pocket PC 2003, так как после перезагрузки устройства все данные на устройстве стираются. Эмулятор позволяет использовать любую папку настольного компьютера в качестве карточки памя- ти. Для выбора подключаемой папки нужно в окне эмулятора вы- полнить команду меню File ► Configure и на вкладке General указать соответствующую папку в пункте Shared Folder (рис. 8.5). Рис. 8.5. Эмуляция карточки памяти Рис. 8.6. Папка Storage Card
160 Глава 8. Эмулятор и другие у ги in. 11 После того как соответствующая папка будет подключена, можно с помощью стандартной программы File Explorer, входящей в состав Windows Mobile, убедиться, что у устройства теперь имеется карточ- ка памяти, которая представлена как папка Storage Card (рис. 8.6). Изменение ориентации экрана Эмулятор позволяет легко менять ориентацию экрана. Достаточно перейти на вкладку Display после выполнения команды меню File ► Configure и выбрать нужный режим в разделе Orientation (рис. 8.7). Рис. 8.7. Настройка ориентации экрана Рис. 8.8. Вращение устройства
Выход в Интернет черрз эмулятор 161 Если выбрать соответствующее значение для поворота экрана, то эмулятор повернет изображение устройства (но не экрана) на 90° (рис. 8.8). Выход в Интернет через эмулятор Совсем не обязательно при отладке своих программ для карманных компьютеров или смартфонов копировать программы на данные ус- тройства, запускать их и проверять работоспособность приложе- ний. Гораздо удобнее использовать эмуляторы соответствующих ус- тройств. Несомненно, вы так и поступали при изучении предыдущих примеров. Но все описанные примеры не использовали ресурсы Интернета. Однако сейчас количество программ, использующих се- тевые возможности, стремительно растет. К счастью, эмулятор при- ходит на выручку и в этой ситуации. Если ваш компьютер, на кото- ром установлен эмулятор, имеет соединение с Интернетом, то можно подключить к Сети и сам эмулятор. Настройка не очень сложна, и все этапы приведены в следующем списке. 1. Создать новый проект в Visual Studio 2005. 2. Запустить программу ActiveSync. Возможно, она неактивна, и ее пиктограмма располагается в области уведомлений. В этом слу- чае надо щелкнуть правой кнопкой мыши на этом значке и выпол- нить команду контекстного меню Открыть Microsoft ActiveSync. 3. Вернуться в среду разработки Visual Studio 2005 и выполнить команду меню Tools ► Device Emulator Manager. На экране появит- ся диалоговое окно Device Emulator Manager. 4. Щелкнуть правой кнопкой мыши на соответствующем эмулято- ре и выполнить команду контекстного меню Connect. На экране появится соответствующий эмулятор. 5. Вернуться в диалоговое окно Device Emulator Manager и снова щелкнуть правой кнопкой на выбранном ранее эмуляторе, а за- тем выполнить команду Cradle. 6. В диалоговом окне Device Emulator Manager у выбранного эмуля- тора появится значок, показывающий, что эмулятор теперь под- ключен к системе настольного компьютера. 7. Автоматически появится сообщение от Microsoft ActiveSync о том, что установлено соединение (рис. 8.9). 8. В этом окне нужно нажать кнопку ОК. 9. На экране появится окно Мастер синхронизации (рис. 8.10). 6- 2873
162 Глава 8. Эмулятор и другие утилиты Рис. 8.9. Сообщение ActiveSync Рис. 8.10. Окно ActiveSync 10. Так как сейчас синхронизация не нужна, то следует нажать кнопку Cancel. И.Появится основное окно программы Microsoft ActiveSync, сиг- нализирующее, что установлено соединение с компьютером. 12. Закрыть окно программы Microsoft ActiveSync. Программа про- должает работать в фоновом режиме. В области уведомлений должна отображаться зеленая пиктограмма. 13. В очередной раз вернуться в окно программы Device Emulator Manager и закрыть его. Программа также продолжает работать в фоновом режиме, а ее пиктограмма тоже появится в области уведомлений. 14. Настало время настройки эмулятора для доступа в Интернет. Нужно щелкнуть правой кнопкой на зеленом значке ActiveSync и выполнить команду контекстного меню Открыть Microsoft ActiveSync. Затем надо выполнить команду меню File ► Connection Settings и выбрать режим This computer is connected to The Internet, после чего останется только нажать кнопку ОК. 15. В эмуляторе надо нажать кнопку Start и щелкнуть на пиктограм- ме Internet Explorer. В результате будет запущен стандартный браузер. В адресной строке можно указать URL любого суще- ствующего сайта. Эмулятор должен загрузить выбранный сайт.
Новая версия эмулятора 163 Теперь компьютер соединен с Интернетом через эмулятор. Это поз- волит отлаживать программы, использующие соединение с Интер- нетом. Изменение внешнего вида эмуляторов Разработчик может также создать собственный внешний вид эму- лятора. Соответствующую информацию можно найти в справоч- ной системе. Описание внешнего вида эмулятора хранится в XML- файлах, которые описывают внешний вид устройства. Если нужно, чтобы эмулятор был точно похож на ваше устройство, надо подго- товить соответствующие рисунки устройства и указать их в файле. Затем в настройках эмулятора можно указать путь к новому фай- лу. И тогда эмулятор будет выглядеть именно так, как ваше соб- ственное устройство. Эмулятор как отдельный продукт Эмулятор поставляется вместе с Visual Studio 2005. Эмуляторы для тестирования программ на новых устройствах, таких как Windows Mobile 5.0, также интегрируются в оболочку Visual Studio. И чтобы установить эмулятор на новой машине, раньше приходилось уста- навливать весь пакет Visual Studio 2005. Разработчики на форумах часто спрашивали: можно ли устано- вить эмулятор как отдельную программу на компьютере? До не- давнего времени ответ был отрицательным. Но наконец-то Microsoft прислушалась к просьбам разработчиков и выпустила эмулятор в виде отдельного продукта. Более подробную инфор- мацию об этом можно найти на веб-странице www.microsoft.com/ downloads/details.aspx?Family!d=C62D54A5-183A-4AlE-A7E2- CC500EDlF19A&displaylang=en. Новая версия эмулятора Работы над улучшением эмулятора не прекращаются. На странице «Microsoft Device Emulator 2.0 Beta — Community Technology Preview», которая располагается по адресу http://www.microsoft.com/ do wn load s/details. as рх? Family! D-13f 5 de85-30cd-4506-9c5b- a2068falee9e&DisplayLang=en, выложена бета-версия эмулятора, ко- торая будет работать с будущей версией Windows СЕ 6.0. В новой б*
164 Глава 8. Эмулятор и другие утилиты версии обещана еще большая скорость работы эмулятора и реали- зованы дополнительные возможности. Набор утилит Visual Studio Remote Tools В состав Visual Studio 2005 входят несколько утилит, которые мо- гут пригодиться разработчикам программ для мобильных уст- ройств. Если открыть группу программ Visual Studio Remote Tools, то можно увидеть, что в ней расположены утилиты, перечисленные в следующем списке: □ Remote File Viewer; □ Remote Heap Walker; □ Remote Process Viewer; □ Remote Registry Editor; □ Remote Spy; □ Remote Zoom-in. Если вы собираетесь серьезно посвятить себя программированию для мобильных устройств, то необходимо изучить все эти програм- мы. Но сейчас будут рассмотрены три наиболее важные утилиты. Remote Zoom-in Утилита Remote Zoom-in позволяет получить снимок экрана уст- ройства или эмулятора на компьютере разработчика Большинство иллюстраций к данной книге были сделаны с помощью данной про- граммы. Использовать эту утилиту очень просто. Перед ее запуском необ- ходимо подключить к компьютеру устройство или эмулятор. При запуске программа предложит вам список доступных устройств, с которых можно получить снимок экрана (рис. 8.11). После нажатия кнопки ОК программа автоматически установит связь с выбранным устройством и загрузит текущее изображение экрана. Если необходимо получить новое изображение с экрана устройства, то нужно выполнить команду меню File ►New Bitmap. Утилита Remote Zoom-in сохраняет экранные снимки в формате ВМР- файлов. Функциональные возможности программы сильно урезаны, пользователь может вырезать часть изображения, изменить его раз- мер или сделать копию. Но от этой программы и не требуется боль-
Remote Registry Editor 165 ших возможностей. Сохранив изображение в файле, при необходимо- сти можно редактировать его в любом графическом редакторе. Рис. 8.11. Выбор устройства для получения снимка экрана Remote File Viewer Утилита Remote File Viewer является аналогом стандартного Про- водника, входящего в состав Windows ХР. С помощью этой утили- ты пользователь может просматривать содержимое папок устрой- ства или эмулятора, а также копировать файлы из устройства на настольный компьютер и наоборот. При запуске утилита сначала отображает список доступных уст- ройств, а затем устанавливает связь с выбранным устройством. Remote Registry Editor Еще одной полезной утилитой является редактор реестра Remote Registry Editor. С помощью данного редактора пользователь может изменять, удалять и создавать новые записи в реестре. В одной из следующих глав будет рассказано о программном изменении зна- чений реестра с помощью функций Windows API. С помощью этой утилиты можно контролировать работу этих функций.
Глава 9 Программирование для смартфонов Особенности программирования для смартфонов В этой главе мы научимся создавать приложения для смартфонов под управлением системы Windows Mobile 5.0. Так получилось, что в России смартфоны под управлением Smartphone 2003 поначалу не получили широкого признания. Признанными лидерами на рынке «умных» телефонов были такие марки, как Nokia, Siemens и Sony Ericsson, которые использовали в телефонах операционную систему Symbian. И если на рынке КПК компании Microsoft удалось потес- нить своего вечного конкурента PalmOs, то в сфере мобильной связи основная борьба еще впереди. Небольшие изменения начались, когда в продаже появились смарт- фоны под управлением Windows Mobile 5.0, выпускаемые азиатски- ми компаниями. В этой главе мы только познакомимся с основными особенностями программирования для смартфонов, а в следующей главе более подробно изучим платформу Windows Mobile 5.0. Если .NET Compact Framework можно считать подмножеством пол- ной версии .NET Framework, то смартфоны можно считать подмно- жеством карманных компьютеров. Причем разница между ними стремительно стирается. Но, тем не менее, между двумя типами мобиль- ных устройств есть существенные различия. Во-первых, смартфоны являются прежде всего мобильными телефонами, пред- назначенными для телефонных разговоров , Во-вторых, размер эк- рана у смартфонов меньше, чем у стандартных карманных компью- теров, и составляет 176 х 220 пикселов, тогда как у КПК размер экрана 240 х 320 пикселов. ПРИМЕЧАНИЕ------------------------------------- В последнее время все чаще выпускаются смартфоны с разрешени- ем 240 х320 пикселов. А КПК стали все чаще стали делать с экра- ном 480 х 640 пикселов.
Создание приложения для смартфона 167 Еще одной отличительной и, пожалуй, главной чертой смартфонов является отсутствие стилуса. Следовательно, тип экрана также от- личается от экрана карманного компьютера. Пользователь может взаимодействовать с приложением только при помощи кнопок теле- фона. И хотя «умные» телефоны используют такую же версию .NET Compact Framework, эти различия заставляют применять совсем иные приемы программирования. В состав Visual Studio 2005 уже входят эмулятор для Smartphone 2003 и необходимые шаблоны проектов. Пора приступить к созда- нию приложения для смартфона. Самый первый пример будет сде- лан для устройства Smartphone 2003, а остальные — для Windows Mobile 5.0. И хотя речь о Windows Mobile 5.0 пойдет только в сле- дующей главе, я решил сразу тестировать программы именно для этой платформы. В этом случае вам придется скачать дополнитель- ный пакет Windows Mobile 5.0 SDK Smartphone, который содер- жит дополнительные эмуляторы для этого класса устройств. Создание приложения для смартфона В главе 7 уже создавался проект, рассчитанный на работу с смарт- фоном. Мы тогда немного забежали вперед. Настало время вернуть- ся к истокам и начать изучение с самого начала. Запустите Visual Studio 2005 для создания нового проекта. Надо выбрать тип проекта Smartphone 2003. Для этого типа применяется только .NET Compact Framework 1.0. Сразу после создания надо за- пустить эмулятор при помощи команды меню Debug ► Start Debugging. На экране будет отображено окно Deploy со списком имеющихся эму- ляторов. Нужно выбрать эмулятор и нажать кнопке Deploy. Если все прошло нормально, то эмулятор будет загружен с пустой формой. В первом упражнении надо лишь проверить возможности работы с эмулятором. Поэтому надо закрыть приложение (но не эмулятор!) и продолжить работу с приложением в режиме проектирования. ПРИМЕЧАНИЕ------------------------------------------ Так как у приложений для смартфонов нет кнопки закрытия окна, то непонятно, как можно закрыть программу. Можно нажать кнопку Stop Debugging. Если на эмуляторе нажать кнопку с крас- ным телефоном, то окно программы будет свернуто, а не закрыто, и все равно придется воспользоваться первым способом для закры- тия приложения.
168 Глава 9. Программирование для смартфонов Создание меню Практически все программы для смартфонов работают при помо- щи команд меню. Поэтому надо получить базовые навыки работы с этим элементом управления. В режиме проектирования формы сле- дует щелкнуть мышью в левой части голубой полоски, которая рас- SmartphenePSrst^CS Microsoft VEuaf Studio положена в нижнеи части экрана. Эта полоска является элементом меню, которое вызывается нажа- тием кнопки Soft Keyl, находя- щейся под экраном. На форме по- явится текст Туре Неге (рис. 9.1). В этой области нужно ввести сло- во Привет и нажать клавишу Enter. Введенный текст появится в ле- вой части формы, и будет активи- рована Soft Key 2 с той же надпи- сью Туре Неге. В этой области нужно ввести слово Закрыть. Те- перь можно вводить текст для подменю. Новый пункт меню по- лучит заголовок Выход. Перед словом Выход появится единица. Среда разработки Visual Studio 2005 автоматически вставляет цифры в создаваемое меню. Эти цифры являются номерами кно- пок-клавиш телефонов. С помо- щью этой подсказки пользователь может быстро активировать нуж- ный пункт меню нажатием соот- ветствующей кнопки (рис. 9.2). Рис. 9.1. Создание меню Теперь нужно перейти на форму и дважды щелкнуть на пункте созданного меню Выход. В результате будет открыт редактор кода с заготовкой функции menu I tem3_ С11 с k. Ее код приведен в листинге 9.1. Листинг 9.1 private void menuItem3_Click(object sender, EventArgs e) { this.CloseO: }
Элементы управления 169 Рис. 9.2. Создание подменю После запуска приложения следует нажать правую серую кнопку под экраном. При этом будет активирована правая часть меню и появится пункт 1 Выход. Для выполнения этой команды можно нажать клавишу 1 или большую кнопку в центре телефона, кото- рая выполняет функцию клавиши подтверждения. Если все было сделано правильно, то приложение закроется. Итак, только что мы создали приложение для смартфона, добавили в него меню, запус- тили приложение и закрыли его. В отношении меню приложения для смартфонов установлено спе- циальное правило. Клавиша Left Softkey может иметь только один пункт меню, а клавиша Right Softkey может иметь разветвленное меню. Любопытно отметить, что при попытке создания подменю для левой клавиши предыдущая версия среды разработки Visual Studio 2003 выводила ошибку. Теперь такой ошибки не вы- водится, и, теоретически, ничто не мешает нарушить установив- шуюся традицию. Например, при использовании примеров для смартфонов под управлением Windows Mobile 5.0 программист может создавать вложенные меню для левой кнопки. Элементы управления Так как пользователь лишен возможности пользоваться стилусом, то многие элементы управления смартфонами не поддерживаются.
170 Глава 9. Программирование для смартфонов В этом легко убедиться, достаточно лишь взглянуть на панель ин- струментов проекта для смарфтонов, чтобы увидеть, как резко уменьшилось число поддерживаемых объектов. Поначалу количество не поддерживаемых элементов управления приводит в замешательство. Как же писать приложения, если смарт- фон не поддерживает такой распространенный элемент, как кноп- ка? Так как в смартфонах не используется стилус, то применение кнопок просто бессмысленно. Управление объектами в приложе- ниях для смартфонов осуществляется при помощи реальных кно- пок-клавиш. Кроме стандартных кнопок с цифрами у смартфона имеются еще так называемые softkey-клавиши. Это две дополнительные кнопки под экраном, которые выполняют очень важные функции в прило- жениях. Именно с помощью этих кнопок осуществляется работа с меню. Также надо помнить, что внешний вид элементов управления зача- стую отличается от вида аналогичных элементов на КПК. Возьмем, к примеру, текстовое поле. Текстовое поле TextBox в смартфонах не имеет окантовки. Она появляется только в том случае, когда тек- стовое поле получает фокус. В этом нетрудно убедиться на простом примере. Следует добавить на форму два текстовых поля. Одно из них авто- матически получит фокус при загрузке приложения (рис. 9.3). Если с помощью клавиши навигации перейти на второе поле, то оно по- лучит окантовку, а у первого поля, соответственно, окантовка про- падет. Рис. 9.3. Окантовка у первого текстового поля, имеющего фокус Чтобы не путать текстовые поля с элементами Label, в надписях используют более жирный текст. На форме надо расположить два элемента Label. На рис. 9.4 видно, что строки label 1 и label2 выде-
Элементы управления 171 ляются более жирным начертанием текста. На этом различия не за- канчиваются. Стоит расположить на форме еще одно текстовое поле и для его свой- ства Multiline указать значение True. В поле надо ввести какой-ни- будь длинный текст. После запуска проекта будет видно, что тексто- вое поле не в состоянии уместить весь текст, а показывает только несколько первых слов с завершающим маленьким треугольником. labeil [textBoxl J label? tactScx? В лесу родилась елочк » Рис. 9.4. Различия внешнего вида некоторых элементов управления Если установить фокус ввода на этом текстовом поле и нажать кноп- ку Enter, то текст полностью будет показан в новом окне (рис. 9.5). Рис. 9.5. Полный текст в текстовом поле Пользователь может самостоятельно дописать слова песни в новом окне и выбрать команду Done или отказаться от подтверждения вво- да с помощью команды Cancel. То же самое касается и элемента ComboBox. Данный элемент получа- ет окантовку при получении фокуса и отображает уже два треуголь- ника. Чтобы раскрыть список элементов, хранящихся в комбини- рованном окне, необходимо сначала установить фокус и нажать на кнопку Enter. При этом будет открыто новое окно, в котором с по- мощью клавиш навигации пользователь может выбрать необходи- мый элемент и выполнить команду меню Done.
172 Глава 9. Программирование для смартфонов Существует также альтернативный способ выбора элемента из ComboBox. Для этого нужно опять установить фокус на комбиниро- ванном окне и прокручивать имеющиеся записи при помощи кно- пок навигации Влево или Вправо. Режимы ввода Первые модели сотовых телефонов для отправки сообщений име- ли только один режима ввода. Пользователь нажимал на кнопки телефона в определенном порядке, вводя тот или иной символ. Затем появились другие режимы. В частности, сейчас поддержи- вается числовой режим, так называемый режим T9 и символь- ный режим. Поначалу библиотека .NET Compact Framework не имела поддержки режимов ввода. Поэтому для установки необ- ходимого режима программистам приходилось использовать механизм P/Invoke для вызова функций API, как показано в ли- стинге 9.2. Листинг 9.2 [Dlllmport("coredl1.dll”. EntryPoint = "SendMessage”)] private static extern uint SendMessage(IntPtr hWnd. uint msg. uint wParam, uint IParam); // Сообщение для режима ввода const uint EM_SETINPUTMODE - OxOODE: // Перечисление режимов ввода public enum InputModeAPI { Spell = 0. T9 = 1, Numbers = 2. Text = 3 } public static void SetlnputModeCControl Ctrl, InputModeAPI mode) { SendMessage!Ctrl .Handle, EM SETINPUTMODE, 0. (uint)mode); } private void mnuT9_Click(object sender. EventArgs e) {
Режимы ввода 173 SetInputMode(textBox3, InputModeAPI.T9); } private void mnuSpell_Click(object sender. EventArgs e) { SetInputMode(textBox3. InputModeAPI.Spel1); } private void mnuNumeric_Click(object sender, EventArgs e) { SetInputMode(textBox3. InputModeAPI.Numbers); } private void mnuText_Click(object sender, EventArgs e) { SetInputMode(textBox3, InputModeAPI.Text); } В данном примере нужный режим ввода указывается для текстово- го поля textBox3 с помощью системы меню (рис. 9.6). Рис. 9.6. Выбираем режим ввода ПРИМЕЧАНИЕ---------------------------------------- Режим T9 в эмуляторе не работает, поэтому надо проверять код на реальном устройстве. В библиотеке .NET Compact Framework 2.0 появилась возмож- ность контролировать режим ввода текста с помощью класса InputModeEditor. Данный режим распространяется только на тек- стовые поля. Предположим, что в программе есть два текстовых поля. В одном поле пользователь должен ввести свое имя, а во втором — номер телефона. В первом случае пользователь будет использовать бук- вы, а во втором случае ему необходимы только цифры. Поэтому
174 Глава 9. Программирование для смартфонов можно заранее задать нужный режим ввода текста для разных тек- стовых полей. Для этого надо указать ссылку на сборку Micro- soft. WindowsCE.Forms и задействовать класс InputModeEditor, как по- казано в листинге 9.3. Листинг 9.3 Управление режимами ввода с помощью управляемого кода private void Forml_Load(object sender, EventArgs e) { // Устанавливаем текстовый режим ввода текста InputModeEdi tor.SetInputMode(txtName, InputMode.AlphaCurrent): // Устанавливаем числовой режим ввода текста InputModeEditor.SetInputMode(txtPhone, InputMode.Numeric); } Переопределение клавиш Soft Key На смартфонах клавиши Soft Key 1 и Soft Key 2 используются для управления меню. Если попробовать переопределить эти клави- ши для других задач, то у вас ничего не получится. Дело в том, что события Key_Down не распознаются системой для этих клавиш, если на форме присутствует компонент MainMenu. Но если удалить этот компонент, устанавливаемый по умолчанию, то с этими кнопка- ми можно будет связать собственные команды, как показано в листинге 9.4. Листинг 9.4 private void Forml_KeyDown(object sender, KeyEventArgs e) { if (Ce.KeyCode — System.Windows.Forms.Keys.Fl)) { // Soft Key 1 IblTest.Text "Вы нажали на клавишу Soft Key 1"; } if ((e.KeyCode я System.Windows.Forms.Keys.F2)) { // Soft Key 2 IblTest.Text « "Вы нажали на клавишу Soft Key 2"; } }
Прокручивание формы 175 Прокручивание формы Если форма не умещается на экране целиком, го пользователь мо- жет прокрутить ее стилусом с помощью полос прокрутки. Особен- но это полезно, если учесть, что .NET Compact Framework 2.0 те- перь поддерживает свойство AutoScroll. По смартфоны не имеют сенсорного экрана, реагирующего на стилус. Для прокрутки фор- мы надо искать другой вариант. Например, можно воспользоваться обработкой события KeyDown. В тестовом проекте надо растянуть форму так, чтобы нижнюю часть не было видно на экране смартфона. На форме надо разместить не- сколько надписей, причем одна из них должна располагаться в ниж- ней части формы. Для свойства формы AutoScrol 1 надо задать значе- ние True. В листинге 9 5 приведен пример кода для прокрутки формы. Листинг 9.5 private void Forml_KeyDown(object sender. KeyEventArgs e) { if ((e.KeyCode = System.Windows.Forms.Keys.Up)) { // Up this.AutoScroll Position = new Point(-this. AutoScrol1Position.X,-this.AutoScroll Position.Y - 16): } if ((e.KeyCode == System.Windows.Forms.Keys.Down)) { // Down this.AutoScroll Position = new Point(-this. AutoScrol1Position.X,-this.AutoScrollPosition.Y + 16); } if ((e.KeyCode = System.Windows.Forms.Keys.Left)) { // Left this.AutoScrollPosition = new Point(-this. AutoScrol1Position.X - 16.-this.AutoScrollPosition.Y); } if ((e.KeyCooe = System.Windows.Forms.Keys.Right)) { // Right this.AutoScrollPosition = new Point(-this. AutoScroll Position.X + 16.-this.AutoScrollPosition.Y); } }
176 Глава 9. Программирование для смартфонов После запуска приложения можно нажимать на клавиши навигации. Написанный код позволит прокручивать форму в выбранном направ- лении. Но здесь нас подстерегает одна опасность. Код будет работать лишь тогда, когда форма имеет фокус. Если форма содержит элемен- ты управления, то фокус может находиться у данного элемента. И тогда нажатия на клавиши навигации не принесут желаемого ре- зультата. Это ограничение легко обходится добавлением соответству- ющего обработчика события, как показано в листинге 9.6. Листинг 9.6 private void Forml Load(object sender, EventArgs e) { pi ctureBoxl.Focus(): this.pictureBoxl.KeyDown += new KeyEventHandler (Forml KeyDown): } Теперь, даже если фокус находится не у формы, пользователь все равно сможет прокручивать форму при помощи клавиш навигации.
Глава 10 Windows Mobile 5.0 Первый взгляд Устройства под управлением Windows Mobile, к которым относятся КПК и смартфоны, все глубже вторгаются в нашу жизнь. Эти уст- ройства очень быстро эволюционируют, обзаводятся более совершен- ными экранами, увеличивают размер своей дисковой памяти, снаб- жаются фотокамерами и получают поддержку новых сетевых технологий. Операционная система Windows Mobile 5.0 сделала еще один шаг в развитии этих маленьких, но умных устройств. В новой платформе появилась поддержка двухмерных и 3D-изоб- ражений, появилось больше возможностей обработки мультиме- дийных файлов, намного проще стало взаимодействовать с фото- камерами и устройствами позиционирования GPS. Причем эта поддержка осуществлена на программном уровне с помощью но- вых классов и расширения функциональности старых классов. Компания Microsoft уделяет большое внимание данной платфор- ме, предоставляя разработчикам подробнейшую документацию, примеры и инструменты разработки. Главная страница для Window Mobile 5.0 находится на сайте Windows Mobile по адресу msdn.micro- soft.com/mobiLity/windowsmobile/default.aspx. На сайте можно скачать необходимые пакеты SDK, позволяющие работать с устройствами под управлением Windows Mobile 5.0. В этой главе будут рассматриваться новые возможности, заложен- ные в систему Windows Mobile 5.0, которые будут интересны про- граммистам. Улучшенная продуктивность В системе Windows Mobile 5.0 появились нововведения, которые увеличивают продуктивность труда программиста. Основные воз- можности перечислены в следующем списке. □ Появились новые API, связанные с отображением графики, уп- равлением контактами и взаимодействием с GPS.
178 Глава 10. Windows Mobile 5.0 □ Продолжено стирание граней между КПК и мобильными теле- фонами. Код программы, написанный для КПК, легко портиру- ется на смартфоны. □ Улучшена и добавлена поддержка технологий передачи данных, в том числе прием и посылка SMS и телефонных звонков. □ В Visual Studio 2005 добавлена поддержка устройств под управ- лением новой платформы с помощью SDK. Работа с данными более прозрачна, улучшен отладчик ошибок, изменен дизайн гра- фического интерфейса, который позволяет менять ориентацию экрана и его разрешения. □ Переработан эмулятор. Поддержка мультимедиа Теперь программисты могут использовать классы, взаимодействую- щие с фотокамерами. Это позволяет расширить область приложе- ния камер и использовать их в работе с изображениями и видеозапи- сями в приложениях. Разработчики могут использовать возможности музыкального плеера Windows Media Player 10 Mobile в своих при- ложениях. Технология Direct3D позволяет разработчикам создавать более совершенные игры, а библиотека DirectDraw позволяет рабо- тать с графикой на более высоком уровне. Поддержка управляемого кода Система Windows Mobile 5.0 обеспечивает первоклассную поддерж- ку программистов, работающих с управляемым кодом. Основные нововведения перечислены в следующем списке. □ Все устройства под управлением Windows Mobile 5.0 поставля- ются с исполняемой средой .NET Compact Framework 1.0 Service Pack 3, которая записана в независимую память. □ При помощи управляемого кода осуществляется работа с SMS- сообщениями, контактами Outlook Mobile и телефонными воз- можностями устройства. Windows Mobile 5.0 API Система Windows Mobile 5.0 обзавелась новыми функциями API. Программисты получили в свое распоряжение новое пространство имен MIcrosoft.WindowsMobile с множеством классов, перечислений
Первый взгляд 179 и делегатов. Кроме того, появились такие пространства имен, как Configuration, Forms, PocketOutlook, PocketOutlook .Messageinterception, Status и Telephony. Устройства под управлением Windows Mobile постоянно улучша- ются. И разработчики требуют новых возможностей для написания красивых и сложных игр. Поэтому в состав системы включена биб- лиотека Direct3D Mobile. Ее можно считать аналогом библиотеки Direct3D API, которая используется в настольных компьютерах. Для доступа к памяти, улучшенной работе со сложной графикой и видеоматериалами система Windows Mobile 5.0 предлагает вос- пользоваться DirectDraw API. Эта библиотека также может быть востребована для разработки игр, она является аналогом библио- теки DirectDraw API настольного компьютера. Все больше устройств выпускается со встроенными камерами. Раз- работчики могут воспользоваться библиотекой DirectShow API для доступа к возможностям камеры. С помощью соответствующих функций программист может управлять работой камеры, записы- вать, а потом отображать и проигрывать картинки и видеоматериа- лы. Библиотека поддерживает множество форматов и является ана- логом DirectShow настольного компьютера. Кроме того, в устройствах все чаще стали использоваться прием- ники GPS. Раньше писать приложения, работающие с технология- ми GPS, было довольно трудно. Нужно было использовать для ра- боты serial API, что требовало хорошего знания основ сетевого программирования. Система Windows Mobile 5.0 во многом облег- чила эту задачу при помощи технологии GPS Intermediate Driver. Эта технология предоставляет набор простых функций для досту- па к данным GPS. В следующем списке приведены основные функ- ции для работы с GPS Intermediate Driver: .□ GPSOpenDevice — соединение с GPS Intermediate Driver; □ GPSC1 oseDevi се — разрыв связи от GPS Intermediate Driver; □ GPSGetPosi ti on — получение текущих координат; □ GPSGetDeviceState — получение информации о текущем состоя- нии устройства. Взаимодействие с ActiveSync Разработчики теперь могут запускать и останавливать процесс синхронизации ActiveSync, используя методы ActiveSyncStart и ActiveSyncStop.
180 Глава 10. Windows Mobile 5.0 Новые возможности системы После выхода системы Windows Mobile 2005 разработчикам стали доступны многие системные возможности, что намного упростило разработку приложений с использованием передовых технологий. В этом разделе были упомянуты некоторые новые возможности, которые появились в Windows Mobile 5.0. Но их надо рассмот- реть на примерах, чтобы лучше понять преимущества новой плат- формы. Подготовка к работе Прежде чем создавать приложения для устройств под управлени- ем системы Windows Mobile 5.0, нужно установить необходимые пакеты SDK. Компания Microsoft предлагает заказать специальный диск с набором всех необходимых пакетов, сделав заказ на страни- це их сайта, которая располагается по адресу msdn.microsoft.com/ mobility/windowsmobile/howto/resourcekit/default.aspx, или самостоя- тельно скачать эти пакеты с сайта (рис. 10.1). Рис. 10.1. Веб-страница Windows Mobile 5.0
Подготовка к работе 181 Следует обратить внимание на то, что существуют отдельные версии SDK для карманных компьютеров и смартфонов. После завершения установки пакеты автоматически интегрируются в среду разработки Visual Studio 2005. В результате разработчик получает новые эмуля- торы под Windows Mobile 5.0, в систему будут добавлены новые клас- сы, а справочная система пополнится новыми статьями и примерами. Также можно скачать локализованные версии эмуляторов. К при- меру, страница для загрузки локализованной версии Windows Mobile 5.0 Pocket PC Emulator Images находится по адресу www.microsoft.com/downLoads/details. aspx?familyid=EEC33AE3-C129- 4C25-ABAA-18E8E842178F&displaylang=en. Чтобы воочию увидеть особенности Windows Mobile 5.0, надо раз- работать соответствующее приложение. Для этого следует запус- тить среду разработки Visual Studio 2005 и создать новый проект. При этом надо выбрать пункт Smart Device project, чтобы создать приложение для мобильного устройства. Затем надо выбрать плат- форму Pocket PC под управлением Windows Mobile 5.0 (рис. 10.2). После создания проекта Visual Studio 2005 отобразит пустую фор- му с установленными реальными размерами устройства. Рис. 10.2. Выбор типа проекта для Windows Mobile 5.0 Система Windows Mobile 5.0 предоставляет разработчику множе- ство новых пространств имен, классов, свойств и событий, с помо-
182 Глава 10. Windows Mobile 5.0 щью которых он может получить доступ ко многим возможностям, которые ранее приходилось реализовывать только при помощи очень сложного и громоздкого кода на C++ с применением Windows API. Теперь разработчики могут для этих целей применять управ- ляемый код .NET Compact Framework. Имеет смысл поближе по- знакомиться с этими возможностями. Microsoft.WindowsMobile.PocketOutlook С помощью пространства имен Mi crosoft. W1 ndowsMobi 1 e. PocketOut 1 ook разработчик получает доступ к модели Pocket Outlook Object Model (РООМ). А имея доступ к РООМ, можно легко получить данные из объектов Контакты, Встречи и Задачи. Также можно получить электронные адреса из адресной книги, номера отправки SMS и со- общения. В следующем списке указаны наиболее часто используе- мые классы. □ Out 1 ookSessl on — представляет собой объект Pocket Outlook для работы с контактами, встречами и задачами. Также можно по- лучить доступ к учетным записям электронной почты и SMS. □ Appoi ntment — класс, отвечающий за работу с назначенными встречами. С помощью данного класса можно редактировать данные записи и тип применяемого сигнала. □ Contact — класс для работы с контактами. Данный класс поддер- живает более 60 свойств. □ Task — класс для работы с задачами. В следующих разделах применение пространства имен Micro- soft . Wi ndowsMobi 1 е. PocketOutl ook будет рассматриваться на конкрет- ных примерах. Встречи (Appointment) При помощи объектной модели Pocket Outlook Object Model разра- ботчик может добавить новую запись в список намечаемых встреч. Сначала надо создать тестовый проект. Чтобы получить доступ к объектам Pocket Outlook, нужно добавить ссылку на соответству- ющие сборки. Для этого следует выполнить команду Project ► Add Reference. В диалоговом окне Add Reference нужно выбрать строки Microsoft.WindowsMobile.Forms и Microsoft.WindowsMobile.PocketOutlook (рис. 10.3).
Встречи (Appointment) 183 Рис. 10.3. Подключение к проекту сборок После нажатия кнопки ОК выбранные ссылки должны появиться в списке ссылок Solution Explorer, как показано на рис. 10.4. Рис. 10.4. Окно Solution Explorer Теперь в редакторе кода следует добавить объявления для про- странств имен Mlcrosoft.WindowsMobile.Forms и Microsoft.WindowMo- bile.PocketOutlook сразу после существующих объявлений. В этом случае появляется возможность работы с различными классами Pocket Outlook. Например, чтобы получить доступ к настройкам
184 Глава 10. Windows Mobile 5.0 для встреч, используется класс Appoi ntment, как показано в лис- тинге 10.1. Листинг 10.1 usi ng Mi crosoft.Wi ndowsMobi1e.Forms; usi ng Mi crosoft.Wi ndowsMobi1e.PocketOutlook; private void buttonl_Click(object sender, EventArgs e) { // Создаем встречу и устанавливаем детали Appointment appt = new Appointment!); // Тема для встречи appt.Subject = "Встреча с тещей"; // Время встречи • 8 марта 2007 в 22 часа appt.Start = new DateTime(2007, 03, 08, 22, 00, 00); И Продолжительность встречи - 3 минуты appt.Duration = new TimeSpan(00, 03, 00); // Использовать виброзвонок для напоминания appt.ReminderVibrate = true; // Повторять напоминание, пока пользователь не отреагирует appt.ReminderRepeat = true: // Создаем сессию Outlook // добавляем встречу в папку встреч Outlook using (OutlookSession session = new OutlookSessionO) { session.Appointments.Items.Add(appt); session.DisposeO; } } Рис. 10.5. Календарь с установленной записью встречи
Работа с адресной книгой 185 Нужно запустить программу и нажать кнопку Добавить встречу. Пос- ле этого можно закрыть приложение, так как свою работу оно закон- чило. Теперь следует открыть программу Календарь, которая встрое- на в систему. В календаре нужно найти дату, которая использовалась в программе. В текущем примере встреча была запланирована на 8 марта 2007 года. Если все сделано правильно, то в указанной дате должна присутствовать запись о новой встрече (рис. 10.5). Работа с адресной книгой В этом разделе будет рассмотрен пример, в котором будет добавле- на новая запись в объект Контакты. Для этого надо, как и прежде, добавить в проект ссылки на соответствующие сборки Mi с rosoft .Win- dowsMobile.Forms и Microsoft.WIndowsMobile.PocketOutlook. А в редак- торе кода надо добавить объявления для пространств имен Micro- soft .Wi ndowsMobi1е.Forms и Microsoft.Wi ndowMobi1e.PocketOut1ook сразу после существующих объявлений. Теперь можно обращаться к Контактам через объект Out 1 ookSessi on. Чтобы добавить новый контакт в коллекцию Контакты, надо раз- местить на форме кнопку с именем butAddContact и написать код, приведенный в листинге 10.2. Листинг 10.2 private OutlookSession session; public FormlO { Initial!zeComponent(); // Создаем экземпляр сессии Pocket Outlook session = new OutlookSession(); } private void butAddContact_Click(object sender, EventArgs e) { Contact contact = new Contact!); contact.FirstName = "Билл"; contact.LastName = "Гейтс"; contact.EmaillAddress = "billgatesOmicrosoft.com"; contact.Birthday = new DateTime!1955,10.28); contact.CompanyName = "Microsoft"; contact.WebPage = new Uri("http://www.microsoft.com"); session.Contacts.Iterns.Add(contact); }
186 Глава 10. Windows Mobile 5.0 Код очень прост и практически не требует комментариев. В начале работы создается переменная contact, в которой можно задавать самые различные параметры. В этом примере использовались толь- ко основные свойства. Были указаны имя, фамилия, электронный адрес, день рождения, имя компании и ее веб-страница. После того как новый контакт будет добавлен в список, нужно закрыть сессию при помощи метода D1spose(). После запуска приложения следует нажать кнопку Добавить в Кон- такты. В результате этого в списке Контакты появится новая запись (рис. 10.6). Рис. 10.6. Просмотр списка контактов Но разработчик может не только добавлять, но и получать информа- цию из имеющегося элемента списка. Для этого на форму надо по- местить список 1st Contacts и кнопку butGetlnfo. Прежде чем полу- чить информацию о нужном нам человеке, нужно сначала получить сам список контактов. И только потом, выбрав из этого списка нуж- ную запись, можно получить дополнительную информацию. Для получения полного списка контактов нужно добавить код в обработ- чик события Form Load, как это показано в листинге 10.3. Листинг 10.3 private void Forml_Load(object sender, EventArgs e) { // Получаем список контактов IstContacts.DataSource = session.Contacts.Items: } Теперь при загрузке формы список автоматически будет заполнен. Пользователь может выбрать любую запись и получить дополни-
Работа с адресной книгой 187 тельную информацию о выбранном контакте. Для этого в событии butGetlnfo Cl ick создается код, приведенный в листинге 10.4. Листинг 10.4 private void butGet I nfoCl ick (object sender, EventArgs e) { // Получим информацию о выбранном контакте session.Contacts.Items[lstContacts.Selectedlndex].ShowDialog(); } Когда пользователь выделит интересующую его запись и нажмет кнопку Получить информацию, на экран будет выведено стандартное диалоговое окно с информацией о выбранной записи. Удалить контакт из списка еще проще, чем создать его. На форму надо добавить еще одну кнопку butDel Contact, с которой будет свя- зан код, приведенный в листинге 10.5. Листинг 10.5 private void butDel ContactClick(object sender, EventArgs e) { // Удаляем выбранный контакт sessi on.Contacts.Iterns[1stContacts.Seiectedlndex].Delete(): } Также из приложения можно вызвать стандартное окно выбора контакта, используемое программой Pocket Outlook. Теперь совсем не обязательно закрывать нашу программу и открывать окно кон- тактов, как это было сделано при добавлении новой записи в спи- сок контактов. Стандартное окно имеет некоторые дополнительные возможности, которые могут пригодиться разработчикам. Доступ к данному окну осуществляется через класс ChooseContactDialog, как показано в ли- стинге 10.6. Листинг 10.6 private void butShowContactsCl ick (object sender, EventArgs e) { ChooseContactDialog contactDialog = new ChooseContactDi alog(); // Прячем пункт меню Новый контакт contactDialog.HideNew = true; // Выводим диалоговое окна на экран продолжение &
188 Глава 10. Windows Mobile 5.0 Листинг 10.6 (продолжение) contactDialog.ShowDialog(); // Показываем выбранный контакт MessageBox.Show(contactDi alog.SeiectedContactName, "Выбранный контакт”); } Электронная почта Кроме получения доступа к списку контактов и добавления новых встреч, разработчик может также отсылать сообщения по электрон- ной почте или через SMS. Для этих целей используются соответствующие пространства имен Microsoft.WindowsMobiIe.PocketOutlook.Emai 1 Account и Microsoft. Wi n- dowsMobi 1 e. PocketOutl ook. SmsAccount. Классы из этих пространств имен позволяют легко интегрировать отправку сообщений в ваши прило- жения. Например, класс Email Account позволяет создавать электрон- ные письма и присоединять к ним файлы. В следующем примере демонстрируется вызов диалогового окна ChooseContactDialog для выбора нужного адресата из списка контак- тов, которому будет отправлено электронное письмо. Приложение создаст сообщение, в коде будет указана и тема письма. Также в письмо будет добавлен вложенный файл, после чего сооб- щение будет отправлено выбранному ранее лицу. Все эти действия проиллюстрированы листингом 10.7. Листинг 10.7 private void butSendEmail_Click(object sender. EventArgs e) { ChooseContactDialog contactDialog = new ChooseContactDi alog(); contactDialog.Title = "Выберите контакт для отправки email"; if (contactDialog.ShowDialog() — DialogResult.OK) { Email Message message = new EmailMessageO; message.To.Add( new Reci pi ent(contactDi alog.SeiectedContact.Emai1lAddress)); message.Subject - "С днем рождения";
SMS-сообщения 189 message.BodyText = "Уважаемый Владимир Владимирович! Поздравляю вас с днем рождения! Посылаю ван открытку с видами Петербурга. Ваша Люда".; message.Attachments.Add( new Attachment(@"\My Documents\piter.jpg")); using (OutlookSession session = new OutlookSessionO) { session.EmailAccounts[0].Send(message); session.DisposeO; } } } После запуска программы и нажатия кнопки Послать письмо будет открыто стандартное окно Контакты, где можно выбрать адресата. После выбора получателя по его электронному адресу будет отправ- лено электронное письмо с заданным содержанием. Если надо отправить электронное письмо адресату, который не вне- сен в адресную книгу, то пример надо переработать. Новый код при- веден в листинге 10.8. Листинг 10.8 private void butSendEmai!2_Click(object sender, EventArgs e) { Recipient recipient = new Recipient("alexander.klimoffPgmail.com"); EmailMessage msg = new EmailMessageO; // Кому письмо msg.To.Add(recipient); // Тема письма msg.Subject = "О вашей книге"; // Текст письма msg.BodyText = "Спасибо за книгу"; msg.Send("ActiveSync"); } SMS-сообщения Отправка SMS-сообщения с помощью новых возможностей тоже очень и очень проста. Эти сообщения весьма популярны у владель- цев мобильных телефонов. Раньше для отсылки и приема SMS
190 Глава 10. Windows Mobile 5.0 приходилось использовать неуправляемый код, очень сложный для восприятия неопытным программистом. Теперь создать код, отсы- лающий сообщение, не сложнее, чем написать само сообщение, что иллюстрирует листинг 10.9. Листинг 10.9. private void butSendSMSClick(object sender, EventArgs e) { ChooseContactDialog contactDialog = new ChooseContactDi alog(): contactDialog.Title = "Выберите получателя"; if (contactDialog.ShowDialog() = DialogResult.OK) { // Создаем SMS-сообщение SmsMessage message - new SmsMessage( contactDialog.SeiectedContact.Mobi1eTelephoneNumber, "Купи хлеба. Жена"); message.RequestDeliveryReport = true; // Посылаем сообщение mess age. SendO; } } В этом примере SMS-сообщение отсылалось адресату, чья запись уже имелась в адресной книге. Если же требуется отправить сооб- щение, не используя окно Контакты, то придется воспользоваться другим кодом. Здесь я хочу сделать небольшое отступление и открыть вам боль- шой секрет. Разработчик может посылать SMS-сообщения самому себе при помощи эмулятора! Если послать SMS-сообщение из эму- лятора на телефонный номер 4250010001, то оно вернется на эму- лятор (рис. 10.7). Итак, необходимо отправить SMS-сообщение человеку, чья учет- ная запись не отражена в списке Контакты. Для этого используется код, приведенный в листинге 10.10. ПРИМЕЧАНИЕ----------------------------------------------------- При отладке приложения в эмуляторе надо использовать целевое ус- тройство типа «Phone Edition*. Если проверять пример в обычном эмуляторе, то будет отображено сообщение об ошибке <Could not load sms.dll*. Впрочем, это не удивительно. Если эмулятор не имеет телефонных функций, то как можно отправлять SMS-сообщение?
Прием и обработка SMS-сообщений 191 Листинг 10.10 private void butSendSMS2_Click(object sender, EventArgs e) { SmsMessage message = new SmsMessageO; // Номер получателя message.To.Add(new Recipient("4250010001")); // Текст сообщения message.Body = "Позвони домой"; // Посылаем сообщение message. SendO; } Результат выполнения этого кода приведен на рис. 10.7. Рис. 10.7. Прием SMS-сообщения Прием и обработка SMS-сообщений Итак, мы научились отправлять SMS-сообщения из своей програм- мы. Но было бы неплохо научить приложения принимать подоб- ные сообщения. Для приема сообщений существует пространство имен Messageinterception, которое находится в сборке Microsoft. Win- dowsMobi1е.PocketOutlook. Следует заметить: можно организовать прием сообщений таким об- разом, что запущенное приложение будет перехватывать нужные сообщения, содержащие ключевые слова. Причем система даже не покажет окно, уведомляющее о прибытии перехваченного события. С этой возможностью стоит познакомиться ближе. После создания нового проекта Intercept! onSMS CS, нужно добавить на форму кноп- ку для отсылки SMS-сообщения, флажок chkAlert для установки
192 Глава 10. Windows Mobile 5.0 флага срочности и текстовое поле, в котором будет содержаться текст SMS-сообщения. Затем надо задать ссылки на уже применяв- шиеся ранее сборки Microsoft.WindowsMobile и Microsoft.Windows- Mobi le. PocketOutlook. Код, отвечающий за обработку принятых со- общений, приведен в листинге 10.11. Листинг 10.11 using Microsoft.WindowsMobile; usi ng Mi crosoft.Wi ndowsMobi1e.PocketOut1ook; using Mi crosoft.Wt ndowsMobi1e.PocketOutlook.Messagelntercepti on; // Объявляем переменную private Messageinterceptor smsInterceptor; private void smsInterceptor_MessageReceived(object sender. MessagelnterceptorEventArgs e) { // Обработка входящего сообщения MessageBox.ShowCK вам пришло срочное сообщение"); } private void butSendSMS_Click(object sender, EventArgs e) { SmsMessage message = new SmsMessageO; // Номер получателя message.To.Add(new Recipient!"4250010001")); // Текст сообщения if (chkAlert.Checked) { // Если взведен флажок, то добавляем слово Срочно! message.Body = "Срочно! " + txtSMSText.Text; } else { message.Body = txtSMSText.Text; } // Посылаем сообщение message. SendO; } private void Forml_Load(object sender, EventArgs e) {
Прием и обработка SMS-сообщений 193 smsInterceptor = new Messageinterceptor(Intercept!onAction.Noti fyAndDelete. true); smsinterceptor.Messagecondition = new MessageCondition( MessageProperty.Body, MessagePropertyCompari sonType.StartsWi th, "Срочно”, true): smsInterceptor.MessageReceived += new MessagelnterceptorEventHandler (sms I nter ceptor MessageRecei ved); } При помощи ключевого слова using было объявлено несколько про- странств имен, также была добавлена переменная sms Interceptor, после чего можно было объявлять функцию обработки сообщения smsInterceptor MessageReceived. При получении SMS-сообщения с определенным текстом эта функция выводит соответствующую строку. Но самое интересное происходит в событии Form_Load. Как только устройство принимает SMS-сообщение, оно перехватывается при- ложением для дальнейшей обработки. Если сообщение начинается словом «Срочно», то пользователь предупреждается о прибытии важного сообщения, после чего это сообщение удаляется. Подоб- ное поведение обеспечивает параметр Noti fyAndDel ete. После запуска приложения на форме будут отображены текстовое поле и флажок. После ввода текста сообщения нужно нажать кноп- ку Послать SMS. Код отправки сообщения позаимствован из преды- дущего примера. Система должна отреагировать на прибытие но- вого сообщения с помощью специального уведомления, которое мы видели при разборе предыдущего примера (см. рис. 10.7). Теперь следует повторить операцию. Только на этот раз надо взвес- ти флажок Пометить как срочное. В этом случае при отправке сообще- ния в начало текста вставляется дополнительное слово «Срочно». После нажатия кнопки приложение должно перехватить прибытие SMS-сообщения, так как теперь оно содержит ключевое слово, кото- рое определялось в параметре StartWith. Как только это произойдет, сообщение будет удалено, а пользователь получит уведомление о прибытии срочного сообщения (рис. 10.8). Но следует помнить, что для перехвата сообщения приложение должно быть запущено. Данный пример предоставляет разработчику весьма широкие воз- можности. Представьте себе, что ваша компания рассылает своим 7-2873
194 Глава 10. Windows Mobile 5.0 сотрудникам особым образом отформатированные сообщения. Про- грамма может обработать эти сообщения и автоматически создать новые записи в списках Контакты или Встречи. И теперь сотруд- нику достаточно только взглянуть на экран, чтобы увидеть прият- ную новость, что сегодня компания выдает премию, за которой нуж- но подъехать в офис. Рис. 10.8. Прием срочного сообщения ВНИМАНИЕ---------------------------------------------- Для примеров, связанных с SMS-сообщениями, нужно использовать эмуляторы и устройства, имеющие возможность работы с SMS. Телефония Разработчик может использовать возможности телефонии при по- мощи класса Mi с rosoft.WindowsMobile. Tel ephony. Phone. Используя ме- тод этого класса Talk, можно программно набрать нужный телефон- ный номер. При использовании класса Phone перед началом работы нужно установить ссылку на сборку Mi crosoft. Wi ndowsMobi 1 e. Tel ephony. Пример использования этого метода приведен в листинге 10.12. Листинг 10.12 using Microsoft.WindowsMobi1e.Telephony; // Объявляем переменную Phone phone = new PhoneO; // Набираем номер // Перед набором запрашиваем подтверждение phone.Taik("4255551212", true);
State and Notifications Broker 195 Обратите внимание на набираемый номер. С помощыр данного но- мера разработчик может делать звонок на эмуляторе. Эмулятор сначала запросит подтверждение набора номера (рис. 10.9) и затем будет звонить по указанному номеру. Рис. 10.9. Запрос набора указанного номера После подтверждения будет установлено соединение с неизвест- ным абонентом 4255551212 (рис. 10.10). «х • «лф 4! ^5 м а ^ЯИ-41 ВИ* е* = И четы Рис. 10.10. Соединение с абонентом State and Notifications Broker В Windows Mobile 5.0 появилась новая технология, получившая название State and Notifications Broker. Использование данной тех- нологии позволяет управлять состоянием устройства. Раньше для доступа к системным настройкам приходилось использовать неуп- равляемый код. Но теперь можно использовать возможности тех- нологии State and Notification Broker. 7*
196 Глава 10. Windows Mobile 5.0 При помощи этой технологии можно обрабатывать информацию о различных состояниях устройства и постоянно отслеживать изме- нения этих состояний. Если будут обнаружены какие-либо изме- нения, то система сообщит об этом приложению. Использование данных технологий открывает широкие возможности для увеличе- ния функциональности программ. Например, разработчик сможет определять силу сигнала от сотовой станции, значение текущего заряда батареи, узнать, подключен ли крэдл, отслеживать состоя- ние ActiveSync, определять наличие подключаемой клавиатуры, фотокамеры и гарнитуры. Также с помощью этой технологии мож- но определять число Bluetooth-соединений, отображать список те- кущих сетевых соединений, получать информацию об ориентации экрана. Разработчик может получать информацию о следующей назначенной встрече (Appointment), обрабатывать информацию о проигрываемом музыкальном файле, получать информацию о контактах и количестве не прочитанных электронных писем, SMS- сообщений и пропущенных телефонных звонков. Чтобы использовать возможности State and Notifications Broker в приложениях, надо добавить ссылку на сборку Microsoft.Win- dowsMobile. Status. Также необходимо добавить ссылку на сборку Microsoft. WindowsMobi 1 е. После этого программа готова использовать классы пространства имен Microsoft. Wi ndowsMobi 1 e. Status. Конечно, без наглядного примера обойтись просто нельзя. Предпо- ложим, что нас интересует информация о владельце устройства и необходимо отслеживать изменение этой информации. Для этого надо создать новый проект и добавить на форму элемент Label. Этого вполне достаточно для работы примера. Также необходимо доба- вить ссылки на сборки Microsoft.WindowsMobile и Microsoft.Win- dowsMobile. Status при помощи команды меню Project ► Add Reference. Нас интересует изменение электронного адреса владельца устрой- ства. Для этого используется код, приведенный в листинге 10.13. Листинг 10.13 usi ng Mi crosoft.Wi ndowsMobi1e.Status; private Systemstate sysState: sysState = new SystemStateCSystemProperty.OwnerEmail. true): sysState.Changed += new ChangeEventHandler(sysStateChanged); private void sysState_Changed(object sender. ChangeEventArgs args) { IblOwnerEmail.Text - SystemState.OwnerEmai1: }
State and Notifications Broker 197 Протестируем пример. После запуска приложения с ним не нужно ничего делать. Следует нажать кнопку Пуск и выбрать пункт меню Настройка. На вкладке Личные нужно активировать пиктограмму Данные о владельце. В соответствующем текстовом поле Эл.почта следует изменить указанный адрес электронной почты. Если теперь вернуться к приложению, то можно будет увидеть, что изменилось содержимое надписи IblOwnerEmai 1. Таким образом, программа ав- томатически отреагировала на изменение данных в настройках си- стемы. Конечно, можно получать данные об электронном адресе владельца в принудительном порядке. Для этого используется код, приведенный в листинге 10.14. Листинг 10.14 private void butGetEmail Click(object sender, EventArgs e) { //Получим email владельца устройства IblOwnerEmail.Text = SystemState.OwnerEmail; } Да, с помощью этого кода можно получить интересующие данные, но в этом случае нельзя узнать, когда эти данные изменятся. При- дется через определенные промежутки времени проверять, не из- менился ли адрес у владельца устройства. Но стоит вернуться к примеру уведомления об изменении элект- ронного адреса владельца устройства. Отслеживанием изменений в системе занимается класс SystemState. Данный класс содержит множество статичных свойств для получения различных настроек системы. Но кроме этого класс SystemState содержит очень важное событие Changed. Для обработки данного события нужно сначала создать экземпляр класса SystemState и передать ему соответствую- щее свойство: sysState = new SystemStateCSystemProperty.OwnerEmail, true); Затем нужно присоединить делегат к новому экземпляру события Changed: sysState.Changed += new ChangeEventHandler(sysStateChanged); А уже после этого можно перехватывать изменение состояния нуж- ных параметров: private void sysState Changed(object sender, ChangeEventArgs args) { IblOwnerEmail.Text = SystemState.OwnerEmail;
198 Глава 10. Windows Mobile 5.0 Пример с электронным адресом был приведен лишь для ознаком- ления. На самом деле, с помощью соответствующих свойств можно получить доступ более чем к ста системным настройкам. Наиболее внимательные читатели могут заметить, что State и Notifications Broker порой дублируют функциональность, которую можно вос- произвести при помощи других средств. Например, текущую ори- ентацию экрана можно узнать с помощью функции API Get- SystemMetrics или с помощью вызова Scгееп. Pci maryScreen. Bounds. А информацию о заряде батареи можно узнать с помощью функ- ции GetSystemPowerStatusEx. Но зачем понадобилось создавать еще одну дополнительную воз- можность извлечения информации? Причин для такого шага было несколько. Прежде всего, новые возможности удобны и просты. В предыдущем примере было показано, что для получения элект- ронного адреса владельца устройства достаточно вызвать одно со- ответствующее свойство. Для получения других значений также вы- зываются соответствующие свойства. Причем названия этих свойств говорят сами за себя и не требуют наличия под рукой спра- вочной литературы. Для закрепления материала нужно дополнить программу еще не- сколькими примерами получения различных свойств. Можно до- бавить отображение уровня заряда батареи, текущего состояния батареи, наличия радиоприемника и фотокамеры, названия сото- вого оператора и определение текущей ориентации экрана. Все это делает код, приведенный в листинге 10.15. Листинг 10.15 private void butGetInfo_Click(object sender, EventArgs e) { Istlnfo.Items.Add("Название оператора: " + SystemState.PhoneOperatorName); Istlnfo.Items.Add("Наличие радио: " + SystemState.PhoneRadioPresent); Istlnfo.Items.Add("Наличие камеры: " + SystemState.CameraPresent); Istlnfo.Items.Add("Ориентация экрана " + SystemState.Di splayRotati on); } private void butBattery Click(object sender, EventArgs e) {
State and Notifications Broker 199 // Уровень заряда батареи BatteryLevel batteryLevel = SystemState.PowerBatteryStrength; BatteryState batteryState = SystemState.PowerBatteryState; string strBatteryLevel = "Уровень заряда"; switch (batteryLevel) { case BatteryLevel.VeryLow: strBatteryLevel = "Уровень заряда: Очень низкий (0- 20*)"; break; case BatteryLevel.Low: strBatteryLevel = "Уровень заряда: Низкий (21-401)"; break; case BatteryLevel.Medium: strBatteryLevel = "Уровень заряда: Средний (41-60*)"; break; case BatteryLevel.High: strBatteryLevel = "Уровень заряда: Высокий (61-80)"; break; case BatteryLevel.VeryHigh: strBatteryLevel = "Уровень заряда: Очень высокий (81- 100*)"; break; } // Состояние батареи string strBatteryState = "Состояние батареи: "; if ((batteryState & BatteryState.Normal) = BatteryState.Normal) strBatteryState "Нормальное "; if ((batteryState & BatteryState.NotPresent) = BatteryState.NotPresent) strBatteryState += "Батарея отсутствует "; if ((batteryState & BatteryState.Charging) — BatteryState.Chargi ng) strBatteryState += "Заряжается "; if ((batteryState & BatteryState.Low) = BatteryState.Low) strBatteryState += "Низкий заряд "; if ((batteryState & BatteryState.Critical) = BatteryState.Critical) strBatteryState -н» "Критическое"; MessageBox.Show(strBatteryLevel + "\n" + strBatteryState);
200 Глава 10. Windows Mobile 5.0 Мультимедиа Система Windows Mobile 5.0 обеспечивает еще более глубокую под- держку мультимедиа, чем предыдущие версии операционных сис- тем. Теперь разработчики имеют возможность напрямую работать с фотокамерой, встраивая в свои программы взаимодействие с ка- мерой и обработку картинок и видеороликов. Технология Microsoft DirectShow дает возможность управлять потоковыми мультиме- дийными материалами. Программа Microsoft Windows Media Player 10 Mobile позволяет интегрировать функциональность музыкаль- ного плеера в собственные приложения. Технология Microsoft DirectDraw предоставляет доступ к графической системе на более высоком уровне, а библиотека Microsoft Direct3D позволяет созда- вать очень сложные динамические игры, используя управляемый код. Эти возможности стоит рассмотреть подробнее. Выбор изображения В операционной системе Windows Mobile 5.0 стало поразительно легко работать с коллекцией фотографий и рисунков. При помощи стандартного диалогового окна выбора рисунка можно легко выб- рать нужный рисунок. Доступ к стандартному окну выбора рисунка осуществляется при помощи класса Mi crosoft. Wi ndows Mobi 1 e. Forms. Se- lectPictureDialog. Но лучше работу с диалоговым окном выбора картинки рассмот- реть на примере. На форме надо разместить метку 1 bl Sei ectedPi cture и графическое поле pi cSelectImage. Не забудьте перед началом со- здания приложения установить ссылку на пространство имен Mi crosoft. Wi ndowsMobi 1 e. Forms. Соответствующий код приведен в ли- стинге 10.16. Листинг 10.16 private void butSelectPicture_Click(object sender, EventArgs e) { SelectPictureDialog selectPictureDialog new SeiectPi ctureDi alog(); // Задаем фильтр selectPictureDialog.Filter = "Рисунки(*.ВМР;*.JPG)|*.BMP;*.JPG"; // Задаем папку для обзора selectPictureDialog.InitialDi rectory = @"\Windows";
Выбор изображ ' 201 // Заголовок для диалогового окна selectPictureDialog.Title = "Выберите рисунок": if (selectPictureDialog ShowDialogО = DialogResult.OK && selectPictureDialog.FileName.Length > 0) { // Получим расширение выбранного файла string fileExtension - Path.GetExtensi on(seiectPi ctureDi alog.Fi1 eName); // Выводим путь выбранного файла 1ЫSeiectedPicture.Text = "Выбранный файл: " + seiectPi ctureDi alog.Fi1eName; // Если выбран файл JPG. то выводим на экран if (fileExtension.ToLowerO = ".jpg") picSelectedlmage.Image = new Bi tmap(seiectPictureDi alog.Fi1 eName); } } В начале работы создается объект SelectPictureDialog, а затем для него задаются нужные свойства. С помощью свойства Fi Iter огра- ничивается выбор файлов. Пользователь может загружать изоб- ражения с расширениями .BMP и .JPG. Затем указывается старто- вая папка. Строго говоря, в Windows Mobile для хранения картинок используется папка Мои картинки. Но приложение, ра- ботающее с изображениями, может использовать свою собствен- ную папку. Рис. 10.11. Выбор изображения Потом в заголовке диалогового окна выводится текст, поясняю- щий пользователю дальнейшие действия Это был минимально необходимый при использовании класса SelectPictureDialog код.
202 Глава 10. Windows Mobile 5 0 Если пользователь выбрал картинку и нажал на кнопку ОК, то надо распознать выбранный файл. С помощью метода Path. Get Extension можно получить расширение файла. В текстовой метке ТЫ Sei ес- tedPicture отображается полный путь к выбранному файлу, а в гра- фическом поле pi cSel ectedImage размещается сама картинка. Но для этого она должна иметь расширение .JPG (рис. 10.11). Следует обратить внимание на то, что диалоговое окно выбора рисунка позволяет выбирать картинки из любой папки устрой- ства. Работа с фотокамерой Мобильные устройства все чаще снабжаются фотокамерами. При- чем многие пользователи отсутствие камеры на смартфоне счита- ют очень большим недостатком. Система Windows Mobile 5.0 пред- лагает поддержку работы с камерой, чтобы разработчики могли использовать ее возможности в своих приложениях. Диалоговое окно захвата изображения позволяет интегрировать фотографии и видеоматериал в приложения. При этом разработ- чик получает возможность управлять поведением камеры. Доступ к возможностям камеры осуществляется при помощи класса Microsoft. Wi ndowsMobi 1 е. Forms. CameraCaptureDi а 1 од. Класс CameraCap- tureDialog очень похож на класс SelectPictureDialog. Свойство Mode позволяет управлять режимом съемки. Камера мо- жет работать, как обычный фотоаппарат, что задается значением CameraCaptureMode.Still, или записывать видеоролик. Диалоговое окно вызывается методом ShowDi al од, который возвращает значение, показывающее, как было закрыто окно. Если пользователь выбрал кнопку ОК, то возвращается значение Di al ogResul t. OK. Имя выбран- ной картинки записывается в свойство Fi 1 eName. В листинге 10.17 приведен пример работы с фотокамерой. Листинг 10.17 private void butPhotoMake_Click(object sender, EventArgs e) { CameraCaptureDialog cameraCaptureDialog = new CameraCaptureDi alog(); cameraCaptureDialog.Owner = this; cameraCaptureDialog.Title = "Фотограф”; cameraCaptureDialog.Mode e CameraCaptureMode.Still;
Работа с фотокамерой 203 if (cameraCaptureDialog.ShowDialog() = Di alogResult.OK && cameraCaptureDialog.FileName.Length > 0) { pictureBox.Image = new Bitmap (cameraCaptureDialog.FileName); MessageBox.Show("Снято!"): } } Для записи видеоролика используется аналогичный способ, но надо поменять режим съемки. Так, для записи видеоматериала вместе со звуком используется режим VideoWI thAudi о. Пример записи ви- деоролика приведен в листинге 10.18. Листинг 10.18 private void butCapture_Click(object sender, EventArgs e) { CameraCaptureDialog cameracapture = new CameraCaptureDi alog(): cameraCapture.Owner = null; cameracapture.InitialDirectory = @"\My Documents"; cameraCapture.DefaultFileName = @"test.3gp"; cameraCapture.Title » "Камера - Демонстрация"; cameraCapture.VideoTypes • CameraCaptureVi deoTypes.Messagi ng; cameraCapture.Resolution = new Size(176, 144); // Лимит в 10 секунд для видео cameraCapture.VideoTimeLimit - new TimeSpan(O, 0, 10); cameraCapture.Mode = CameraCaptureMode.VideoWithAudio; if (DialogResult.OK — cameraCapture.ShowDialogO) { MessageBox.Show("Картинка или видео успешно записаны в:\п{0}", cameraCapture.Fi1 eName); } } Легко заметить, что эти два примера практически идентичны. Су- ществует еще режим записи видео без звукового сопровождения. В этом случае для свойства Mode задается значение CameraCap- tureMode.VideoOnly. Если перед вызовом метода ShowDi al од использо- вать свойство Defaul tFi 1 eName, то указанное имя будет использовать- ся как имя файла для записи новых фотографий или видеоматериа- ла. Свойство Initial Di rectory позволяет указать папку, в которой будут сохраняться отснятые материалы. Свойство Resolution позво-
204 Глава 10. Windows Mobile 5.0 ляет задать разрешение снимаемого материала, что иллюстрирует следующая строка кода: cameraCaptureDialog.Resolution = new Size(320, 240): Свойство StillQuality позволяет установить качество сжатия для фотографий при помощи перечисления CameraCaptureSti11 Quality. Используемые значения перечислены в следующем списке: □ High — указывает на наилучшее качество картинки с минималь- ным сжатием; □ Normal — среднее качество картинки; □ I ow — высокая степень сжатия, плохое качество. Свойство VideoTimeLimit позволяет установить максимальную про- должительность записи видеоматериала. По умолчанию использу- ется нулевое значение, что означает отсутствие временного огра- ничения. В этом случае запись съемки будет вестись до тех пор, пока позволяют ресурсы системы. Свойство VideoTypes позволяет выбрать тип видеоматериала. На устройствах под управлением Windows Mobile 5.0 используется видеоматериал двух типов — Multimedia Messaging Service (MMS) и Windows Media Video (WMV). Повторение пройденного Примеры доступа к объектам Pocket Outlook рассматривались при- менительно к карманным компьютерам. Но теперь надо воссоздать их, опираясь уже на смартфоны. Сам код примеров останется прак- тически неизменным. Но при этом изменится логика управления программой. Как уже говорилось ранее, управление в смартфонах сводится к обработке событий для пунктов меню. Встречи Сначала рассмотрим пример с использованием объекта Pocket Outlook. На этот раз надо получить доступ к списку встреч (Appoint- ment). Перед началом изучения примера вам нужно убедиться, что список событий имеет хотя бы одну запись. Если там ничего нет, то следует создать несколько записей самостоятельно. После создания нового проекта на форме надо разместить элемент Li stVi ew. Свойство Vi ew должно получить значение Detai 1 s. В коллек- ции Columns надо задать заголовки Дата, Время и Тема (рис. 10.12). Преж- де всего потребуется задать переменную для экземпляра сессии Out-
Встречи 205 look. Сразу же после вызова метода Initia- 1 i zeComponent в конструкторе формы объяв- ляем экземпляр для сессии PocketOut 1 ook, как показано в листинге 10.19. Листинг 10.19 private OutlookSession session; public FormlO { InitializeComponent(); // Создаем экземпляр сессии Pocket Outlook session = new OutlookSessionO; } Теперь программист получил доступ к коллекции событий через объект Out- lookSessi on. Для коллекции Appoi ntment со- здается соответствующая переменная, при помощи которой можно получить каждый элемент коллекции, что иллюстрирует код, приведенный в листинге 10.20. Рис. 10.12. Внешний вид приложения Листинг 10.20 private void menuAppointments Click(object sender. EventArgs e) { AppAppts = session.Appointments.Items; // Проходим через все элементы коллекции foreach (Appointment appt in AppAppts) { // Создаем объект ListViewItem Ivltems = new ListViewItemO; // Разделяем полученные результаты по колонкам Ivltems.Text = appt.Start.ToShortDateString(); 1vlterns.SubItems.AddCappt.Start.ToShortTi meStri ng()): 1vIterns.SubItems.Add(appt.Subject); // Добавляем в ListView 1vContacts.Iterns.Add(1vIterns): } // He забываем закрыть сессию PocketOutlook session.DisposeO; }
206 Глава 10. Windows Mobile 5.0 Также мы можем получить информацию об имеющихся контактах. Но в этом случае рассматривать код не нужно, так как он полнос- тью повторяет пример для КПК. Отсылка письма Рассматриваемый пример покажет, как можно посылать электрон- ное письмо любому человеку, чья запись присутствует в списке Контакты. При этом разработчик может присоединять к отправля- емому сообщению файл. В этом примере будет применен другой подход к дизайну програм- мы. Так как средства навигации в смартфоне довольно скудны, же- лательно сводить к минимуму число нажатий на клавиши. Напри- мер, одна и та же клавиша может запускать разные функции. После создания нового проекта SendEma 11 Sma rtphone CS на форме надо разместить текстовое поле txtContact, в котором будет отображать- ся выбранный электронный адрес. Также потребуется изменить код из предыдущего примера. Нужно переместить код из обработчика события menuSoftKeyl_С11ск в от- дельный метод Sei ectContact (). Это делается для того, чтобы мож- но было более гибко настраивать программу под свои нужды. Со- ответствующий код приведен в листинге 10.21. Листинг 10.21 private void SeiectContactО { // Создаем экзменпляр окна выбора контактов ChooseContactDialog contactDial - new ChooseContactDialogO; // а также убираем возможность создания новых контактов contactDial.HideNew = true; // выводим диалоговое окно на экран if (contactDial.ShowDialog() = DialogResult.OK) { seiContact contactDial.SeiectedContact; txtContact.Text = seiContact.FileAs; menuSoftKeyl.Text = Послать; } } Следует обратить особое внимание на строку menuSoftKeyl.Text = "Послать";
Отсылка письма 207 Когда пользователь выберет пункт Контакты, а затем нужный кон- такт, то текст в пункте меню menuSoftKeyl меняется на строчку По- слать. Также надо добавить новый пункт меню для очистки тексто- вых полей. Это позволит пользователю выбрать новый контакт для отправки письма. Надо открыть файл Forml.cs в дизайнере формы. На правой стороне меню к уже имеющемуся пункту Выход следует добавить новый пункт меню Очистить. Созданный пункт получит имя mnuClear. Код для метода mnuClear_Click приведен в листинге 10.22. Листинг 10.22 private void mnuClear Click(object sender. EventArgs e) { txtContact.Text = string.Empty; menuSoftKeyl.Text = "Контакты"; } Это позволить очистить текстовое поле и в пункте меню menuSoftKeyl отобразить строку Контакты. Теперь можно писать функцию, отправляющую электронное пись- мо. В примере сообщение будет отправляться с вложенными фай- лами. Для примера можно использовать одну из картинок, входя- щих в состав Windows Mobile 5.0. Для отправки письма используется класс EmailMessage. Чтобы ис- пользовать этот класс в нашем примере, надо сначала установить ссылку на пространство имен System.Messaging, выполнив команду меню Project ► Add Reference. После этого можно пользоваться дан- ным пространством имен при помощи ключевого слова using: using System.Messaging; Код метода SendEmai 1 (), который будет отвечать за отправку пись- ма, приведен в листинге 10.23. Листинг 10.23 private void SendEmailO { // Создаем экземпляр класса EmailMessage EmailMessage message e new EmailMessageO; // Тема письма message.Subject = "Поздравление"; // Текст письма message.BodyText = "Поздравляю с Днем Варенья!"; // Выбираем адресата Recipient client = new Recipient(seiContact.EmaillAddress); message.To.Add(client); a продолжение W
208 Глава 10. Windows Mobile 5.0 Листинг 10.23 (продолжение) 11 добавляем в письмо вложенный файл Attachment image = new Attachment(@"\My DocumentsXMy Pictures\Flower.jpg"); message.Attachments.Add(image); message.Send("ActiveSync"); txtContact.Text = string.Empty; menuSoftKeyl.Text = "Контакты"; } Итак, в методе SendEmai 1 объявляется и создается экземпляр класса Email Message. В свойствах Subject и BodyText задаются тема и текст письма. Электронный адрес из выбранного контакта записывается в свойстве Email Message. То. Для этого создается экземпляр класса Recipient и передается свойство selContact.EmaillAddress. Теперь можно добавить в письмо вложенный файл. Для этого со- здается экземпляр класса Attachment, которому в конструктор пере- дается полное имя выбранного файла. После этого свойству Email Message. Attachment передается значение экземпляра. Теперь для отправки письма все готово. Следует вызвать метод message. Send и очистить текстовое поле. Также надо восстановить в меню строку Контакты. Так как для menuSoftKeyl используются два метода, SendEmai 1 и Sei ectContact, то нужно определиться, когда ка- кой метод следует использовать. Для этого нужно получить значе- ние свойства menuSoftKeyl. Text, как показано в листинге 10.24. Листинг 10.24 private void menuSoftKeyl Click(object sender, EventArgs e) { if (menuSoftKeyl.Text = "Послать”) SendEmailO; else SelectContactO; } Настало время проверить нашу программу. После запуска приложе- ния надо выбрать адресата, которому предназначено письмо. Для отображения окна выбора контакта следует нажать кнопку Soft Key 1. Можно выбрать любой контакт из имеющегося списка. После этого в текстовом поле появится выбранный контакт. При этом пункт меню обретет название Послать. Затем надо снова нажать кнопку Soft Key 1. Кнопка Soft Keyl примет первоначальный вид, в меню будет отображаться строка Контакт,
Мелочь, а приятно 209 а текстовое поле будет очищено. Выходим из программы. Но нам надо убедиться, что письмо было отправлено. Поэтому следует пе- рейти на экран Сегодня и нажать кнопку Пуск, после чего активиро- вать пиктограмму Сообщения. В появившемся списке надо выбрать пункт Эл.п. Outlook, а из пункта Меню перейти в подменю Папки. За- тем осталось перейти в папку Исходящие. В ней должно находиться новое сообщение. Мелочь, а приятно В блоге blogs.msdn.com/anthonywong/, который ведет Энтони Вонг (Anthony Wong), я нашел несколько интересных заметок, расска- зывающих об исправленных ошибках или улучшенных возможно- стях, которые стали доступны в Windows Mobile 5.0. Метод Directory.Exists На устройствах под управлением Windows СЕ 4.Х метод Directo- ry. Exists О по-разному обрабатывал имена путей, которые закан- чивались обратным слэшем. В качестве примера можно рассмот- реть следующее выражение: Di rectory. Exi sts (" Wtemp"); Это выражение возвращает значение True, если папка temp суще- ствует. Добавим в предыдущее выражение символ обратной черты. Di rectory.Exists!"\\temp\\") Теперь данный метод возвратит Fal se, даже если папка существует. На устройствах под управлением Windows Mobile 5.0 платформа .NET Compact Framework исправила это противоречие, и теперь метод Di rectory. Exi sts () возвращает True вне зависимости от нали- чия замыкающего обратного слэша. Метод Bitmap.Save() На старых устройствах также отсутствовали конвертеры графичес- ких изображений, что не позволяло сохранять изображения в фор- матах GIF, JPG или PNG. Разработчикам приходилось довольство- ваться только форматом BMP. Причем при написании программы среда разработки позволяла писать неправильный код, выводя со- ответствующие подсказки. Однако при вызове этого метода про- грамма выводила сообщение об ошибке. В Windows Mobile 5.0 те- перь поддерживаются все четыре формата.
Глава 11 Создание игр Игры на мобильных устройствах Создание игр — одно из самых любимых занятий для программис- тов. При создании новой игры автору приходится быть и художни- ком, и композитором, и дизайнером, и бухгалтером. Естественно, в данном случае речь идет о программисте-одиночке. Разработка игр для мобильных устройств не требует больших финансовых зат- рат, которые имеют место при создании игр для настольных компь- ютеров крупными компьютерными фирмами. На мой взгляд, кар- манные компьютеры идеально подходят для логических и аркадных игр, с помощью которых можно скоротать время во время путеше- ствия, долгого ожидания в очередях или при поездке на работу. Рис. 11.1. Раздел MSDN, посвященный играм
Продуктовая аркада 211 Если в вашей коллекции уже есть игры для настольных компьюте- ров, написанные с использованием .NET Framework, то в большин- стве случаев вам не составит труда портировать их для мобильных устройств. Я хочу познакомить вас с играми, которые уже написа- ны для КПК и смартфонов. Надо сказать, что существует опреде- ленная категория программистов, которые не читают документа- цию и ищут материалы по заданной теме в книгах и на сайтах. Но это не самое правильное поведение. Компания Microsoft очень час- то размещает примеры написания игр в своих справочных систе- мах. Очень много статей на тему разработки игр можно найти в MSDN. В этой коллекции статей и документации есть целый раз- дел, посвященный созданию игр, под названием «Graphics, Audio and Gaming» (рис. 11.1). Продуктовая аркада Для начала имеет смысл рассмотреть игру Bouncer, которую мож- но найти на веб-странице по адресу msdn.microsoft.com/library/ default.asp?url-/library/en-us/dnnetcomp/html/gamesprogwithcheese.asp. Автор игры Роб Майлз (Rob Miles) написал четыре большие ста- тьи об этой игре, которая использует в качестве игровых объектов кусочки сыра, батон хлеба и яблоки. Интересно, что сначала статьи писались о версии игры для .NET Compact Framework 1.0 для смар- тфонов под управлением Win- dows Mobile 2003 с использованием Visual Studio .NET 2003. Но к настоящему моменту игра была пере- писана для смартфонов под управлением Windows Mobile 5.0. ПРИМЕЧАНИЕ------------------------------------------------ К слову сказать, когда я читал эти статьи в 2004 году, у меня еще не было смартфона. И тогда я переписал игру для своего карманного компьютера, чтобы поиграть в аркаду на настоящем устройстве. Автор шаг за шагом раскрывает перед программистом тонкости процесса разработки игры, начиная с создания пустого проекта и заканчивая написанием полноценной игровой модели. Роб Майлз любезно разрешил использовать исходный код игры на страни- цах книги, а в архиве программ вы найдете установочный файл, содержащий исходные коды игры. Когда вы запустите установоч- ный файл, то программа установки скопирует файлы с примера- ми в папку C:\Program Files\Windows Mobile Developer Samples\Games Programming With Cheese Part 1. В этом каталоге будут расположены
212 Глава 11. Создание игр еще семь папок с проектами, которые шаг за шагом ведут програм- миста к написанию игры. Так как сама статья написана на английском языке, то придется приложить некоторые усилия для понимания текста. Но автор так понятно и доходчиво объясняет материал, сопровождая его иллюс- трациями и строчками кода, что разобраться в нем сможет даже начинающий программист. Начало работы Итак, прежде всего нужно создать новый проект для смартфона под управлением Windows Mobile 5.0 с использованием платформы .NET Compact Framework 2.0. Этот проект должен получить имя Bouncer. Добавление изображения в программу Наше приложение будет использовать графические изображения. Картинки, используемые в игре, хранятся в файле самой програм- мы в виде ресурсов. Сначала надо подготовить сами рисунки для игры. Автор программы решил использовать для игры различные виды продуктов. Возьмем, к примеру, изображение кусочка сыра. Файл с изобра- жением сыра надо скопировать в папку, в которой хранятся фай- лы проекта. Затем следует щелкнуть правой кнопкой мыши на названии проекта Bouncer в окне Solution Explorer, выбрать пункт контекстного меню Add, а затем перейти к пункту подменю Add Existing Item. В диалоговом окне Add Existing Item надо выбрать файл cheese.gif. После этого остается нажать кнопку Add. Кар тинка теперь добавлена в проект, но еще не является частью про- граммы. Необходимо указать, что графический файл будет храниться в виде встроенного ресурса. Нужно щелкнуть правой кнопкой мыши на значке графического файла в окне Solution Explorer и выполнить ко- манду контекстного меню Properties. В разделе Build Action по умол- чанию используется пункт Content. Но в данном случае нужно ука- зать пункт Embedded Resource. Теперь картинка является частью сборки, и для распространения программы нам понадобится единственный исполняемый файл, в котором будут содержаться все необходимые изображения.
Продуктовая аркада 213 Использование встроенных ресурсов При работе программы необходимо получить доступ к графичес- кому файлу из ресурсов и вывести изображение на экран. Для это- го сначала необходимо получить ссылку на сборку. Соответствую- щий код приведен в листинге 11.1. Листинг 11.1 // Получим ссылку на сборку System.Reflection.Assembly execAssem = System.Reflecti on.Assemblу.GetExecut1ngAssemblу(); Метод System. Reflection.Assembly.GetExecut 1 ngAssemblу возвращает сборку, из которой выполняется текущий код. Получив в программе ссылку на сборку, можно получить доступ к встроенным ресурсам, в том числе к изображению сыра. Метод GetMani festResourceStream по- зволяет извлекать указанный ресурс из сборки. Для этого нам надо указать имя файла и название пространства имен. В нашем случае это будет Bouncer. cheese .gif, как показано в листинге 11.2. Листинг 11.2 // <summary> /// Изображение сыра /// </summary> private Image cheeseimage = null; public FormlO { Ini ti al1zeComponent(); // Получим ссылку на сборку System.Reflection.Assemblу execAssem = System.Reflecti on.Assemblу.GetExecuti ngAssemblу(); // Получим доступ к картинке с сыром cheeseimage = new System.Drawing.Bitmap! execAssem.GetMani festResourceStream(@"Bouncer.cheese.gi f") ); } Вывод картинки на экран При запуске программа загружает из ресурсов картинку. Теперь надо вывести изображение на экран. Для этого нужно воспользо- ваться событием Paint, как показано в листинге 11.3.
214 Глава 11. Создание игр Листинг 11.3 private void Forml_Paint(object sender, PaintEventArgs e) { e.Graphics.DrawImage(cheeseImage, 0, 0); } После запуска программы в левом углу экрана будет отображен кусочек сыра (рис. 11.2). мшм Рис. 11.2. Вывод изображения на экран Создание анимации Теперь нужно научиться перемещать объект по экрану. Если это делать достаточно быстро, то у пользователя создается ощущение непрерывного воспроизведения анимации. Для этого следует соз- дать метод updatePositions, который позволит перемещать изобра- жение. Пока ограничимся движением вниз и вправо. Соответству- ющий код приведен в листинге 11.4. Листинг 11.4 /// <surnmary> /// Координата X для рисования сыра /// </summary> private int ex « 0; /// <summary> /// Координата Y для рисования сыра /// </summary> private int су - 0; private void updatePositions () { cx++; CyH-; }
Продуктовая аркада 215 Переменные сх и су содержат текущие координаты кусочка сыра. Меняя значения этих координат, можно управлять расположени- ем изображения на экране. Теперь нужно переписать код для собы- тия Forml Pai nt, как это показано в листинге 11.5. Листинг 11.5 private void FormlPaint (object sender. System.Wi ndows.Forms.PaintEventArgs e) { // Текущая позиция сыра e.Graphics.Drawlmage(cheeseimage, ex, cy); } Теперь при каждом вызове метода Paint программа перерисовывает изображение сыра в указанном месте. Но программа должна само- стоятельно перемещать изображение через определенные промежут- ки времени. Также нужно иметь возможность управлять скоростью перемещения картинки. Для этой задачи подойдет объект Timer. Со- ответствующий элемент нужно добавить на форму. Следует помнить, что во время работы таймера смартфон не может использовать сберегающий энергорежим, так как устройство счи- тает, что программа находится в активном состоянии, даже если она свернута. Это негативно влияет на работу аккумуляторов, сокра- щая срок работы без подзарядки. Поэтому нужно останавливать таймер, когда программа работает в фоновом режиме, и включать его снова при активации приложения. Но вернемся к настройкам таймера. Интервал срабатывания тай- мера должен составлять 50 миллисекунд, а свойство Enabl ed долж- но получить значение False. Когда таймер будет включен, код в методе Ti ck будет срабатывать 20 раз в секунду. При создании тай- мера нельзя для свойства Enable устанавливать значение True, так как метод timerl Tick попытается отобразить изображения до того, как они будут загружены. Включать таймер можно только тогда, когда все необходимые картинки будут загружены, иначе програм- ма выдаст сообщение об ошибке. В нашем примере таймер активи- руется в конструкторе формы после загрузки изображения сыра, как это показано в листинге 11.6. Листинг 11.6 public FormlO { // // Required for Windows Form Designer support. л продолжение тУ
216 Глава 11. Создание игр Листинг 11.6 (продолжение) И Ini tiali zeComponent(); // Получим ссылку на сборку System.Reflection.Assemblу execAssem = System.Reflecti on.Assemblу.GetExecuti ngAssemblу(); // Получим доступ к картинке с сыром cheeseimage = new System.Drawing.Bitmap (execAssem.GetMani festResourceStream(@"Bouncer.cheese.gi f”)): // Включаем таймер this.timerl.Enabled = true; } Теперь при запуске программы конструктор загружает картинку и включает таймер. Настало время создать код для события Tick. Система перерисовы- вает содержимое экрана только при определенных условиях. Мы можем заставить систему перерисовать экран при каждом измене- нии местоположения картинки с помощью метода Invalidate. Та- ким образом, через определенные промежутки времени приложе- ние меняет координаты изображения и обновляет экран, чтобы пользователь увидел картинку на новом месте. Соответствующий код приведен в листинге 11.7. Листинг 11.7 private void timerl_Tick (object sender. System.EventArgs e) { updatePositionsO; InvalidateO; } После запуска программы кусочек сыра по диагонали переместит- ся в правый нижний угол экрана. Когда изображение достигнет края экрана, оно продолжит свое движение и скроется. При движении изображение сыра немного мерцает, что очень раздражает всех пользователей. В дальнейшем этот недостаток будет исправлен. Отражения Нужно запрограммировать обработку отражений объекта от стенок. Для этого надо отслеживать текущую позицию объекта и направ-
Продуктовая аркада 217 ление движения. Когда объект достигнет края стенки, нужно изме- нить направление движения. Для начала упростим код программы, отвечающей за отражения. Пусть координаты объекта при движе- нии увеличиваются на единицу, когда кусочек сыра движется впра- во и вниз, и уменьшаются на единицу при движении влево и вверх. Новый код метода updatePositions приведен в листинге 11.8. Листинг 11.8 /// <summary> /// Направление движения по оси X /// «/summary* private bool goingRight = true : /// «summary* III Направление движения по оси Y /// «/summary* private bool goingDown - true ; private void updatePositions () { if ( goingRight ) { cx++: } else { ex--; } if (( ex + cheeseimage.Width ) >= this.Width ) { goingRight = false; } if ( ex <= 0 ) { goingRight = true; } if (goingDown) { cy++; } el se { Cy- - • продолжение
218 Глава 11. Создание игр Листинг 11.8 (продолжение) } if (( су + cheeseimage.Height ) >= this.Height ) { goingDown = false; } if ( су <= 0 ) { goingDown = true; } } Обратите внимание на то, что в коде используются ширина и высо- та изображения и экрана. Не прописывая жестко величины разме- ров экрана и изображения, мы можем быть уверенными в том, что программа будет работать корректно в устройствах с любыми раз- решением экрана и размерами картинки. После запуска приложения можно увидеть, что изображение сыра корректно отражается от краев экрана при перемещении. Управление скоростью движения объекта Рассматривая поведение программы, вам, вероятно, хотелось бы ускорить процесс движения объекта. Чтобы игра была динамичной и увлекательной, нужно постепенно увеличивать сложность игро вого процесса для пользователя. Одним из таких способов являет- ся ускорение движения. На данный момент кусочек сыра проходит расстояние от одного угла до другого за 5 секунд. Увеличить ско- рость перемещения картинки очень просто. Достаточно увеличи- вать значение текущей позиции объекта не на один пиксел, а на несколько. Нужно объявить новые переменные xSpeed и ySpeed, ко- торые будут отвечать за увеличение или уменьшение скорости дви- жения объекта. Соответствующий код приведен в листинге 11.9. Листинг 11.9 /// <summary> /// Скорость движения сыра по горизонтали /// </summary> private int xSpeed = 1; /// <summary> /// Скорость движения сыра по вертикали
Продуктовая аркада 219 /// </summary> private int ySpeed = 1; private void updatePositionsO { if (goingRight) { ex += xSpeed; } else { ex -- xSpeed: } if ((ex + cheeseimage.Width) >= this.Width) { goingRight - false: } if (ex <» 0) { goingRight - true: } if (goingDown) { cy += ySpeed: } el se { cy -= ySpeed; } if ((cy + cheeseimage.Height) >= this.Height) { goingDown « false; } if (cy <= 0) { goingDown = true: } } Изменяя значения переменных xSpeed и ySpeed, мы можем по свое- му желанию увеличивать или уменьшать скорость движения ку- сочка сыра. Для этого надо создать новую функцию, код которой приведен в листинге 11.10.
220 Глава 11. Создание игр Листинг 11.10 private void changeSpeed ( int change ) { xSpeed += change; ySpeed += change; } Теперь можно вызывать этот метод для изменения скорости дви- жения изображения. Для уменьшения скорости надо передавать в функцию отрицательные значения. Чтобы управлять скоростью во время игры, можно использовать клавиши Soft Key, расположен- ные под экраном. Следует создать простое меню, содержащее команды Быстрее и Мед- леннее. Если пользователь нажмет на левую кнопку, то скорость движения сыра будет увеличиваться. При нажатии на правую кноп- ку скорость уменьшится. Соответствующий код приведен в листин- ге 11.11. Листинг 11.11 private void menuIteml_Click(object sender, System.EventArgs e) { changeSpeed (1); } private void menuItem2_Click(object sender. System.EventArgs e) { changeSpeed (-1); } В данной ситуации значения в методе changeSpeed не отслеживают- ся. Это может привести к ситуации, когда пользователь будет по- стоянно уменьшать скорость и значение скорости может стать от- рицательным. В этом случае движение объекта будет совсем не таким, как это планировал разработчик. А при значительном уве- личении скорости движение изображения теряет гладкость. Добавляем новый объект Итак, в результате наших усилий по экрану движется кусочек сыра. Настало время добавить новый объект, которым пользователь бу- дет отбивать сыр. Для наших целей вполне подойдет батон хлеба. Вспоминаем предыдущие упражнения, где мы выводили кусочек сыра на экран, и повторяем шаги в той же последовательности для батона хлеба.
Продуктовая аркада 221 □ Добавляем графический файл в проект в виде ресурса. □ Получаем в коде ссылку на файл из сборки. □ Объявляем две переменные, содержащие координаты батона хлеба. Соответствующий код приведен в листинге 11.12. Листинг 11.12 /// <summary> /// Изображение, содержащее батон хлеба /// </summary» private Image breadimage = null; // Получаем изображение батона хлеба breadimage - new System.Drawing.Bitmap( execAssem.GetMani festResourceStream(@"Bouncer.bread.gi f") ); /// <summary> /// Координата X для батона хлеба III </summary» private int bx = 0, /// <summary> /// Координата Y для батона хлеба /// </summary> private int by = 0: На рис. 11.3 показан внешний вид программы на этом этапе. Рис. 11.3. Изображения хлеба и сыра Устранение мерцания Несмотря на то что мы проделали уже очень большую работу, наша программа по-прежнему не лишена недостатков. При запуске про-
222 Глава 11. Создание игр граммы изображения постоянно мерцают, раздражая пользователя. Это связано с перерисовкой экрана через заданные интервалы вре- мени. Каждые 50 миллисекунд экран закрашивается белым фоном, а затем на экран выводятся два объекта. Если не устранить этот не- достаток, то никто не захочет играть в игру. Решение проблемы лежит в использовании специальной техники, на зываемой двойной буферизацией. Двойная буферизация обеспечивает плавную смену кадров. Технология позволяет рисовать необходимые изображения в специальном буфере, который находится в памяти ком- пьютера. Когда все необходимые изображения будут выведены в бу- фере, то готовое окончательное изображение копируется на экран. Процесс копирования идет очень быстро, и эффект мерцания пропа- дет. Для реализации этой идеи надо создать новый объект Bi trap. Имен- но на нем будут отображаться все рисунки, а потом останется только скопировать объект в нужную позицию. Также потребуется перепи- сать метод Forml.Paint, как показано в листинге 11.13. Листинг 11.13 /// <summary> /// картинка-буфер /// </summary> private Bitmap backBuffer = null ; private void FormlPaint(object sender, System.Wi ndows.Forms.Pai ntEventArgs e) { // Создаем новый буфер i f (backBuf fer^nul 1) { backBuffer e new Bitmap(this.Clientsize.Width, this.ClientSize.Height); } using ( Graphics g « Graphics.Fromlmage(backBuffer) ) { g.Clear(Color.White); g.DrawImage(breadImage,bx. by): g.Draw!mage(cheeseimage, ex, cy); } e.Graphics.Drawlmage(backBuffer, 0, 0); } При первом вызове метода Forml_Pai nt создается буфер для приема изображений, который объявлен как переменная backBuffer Затем
Продуктовая аркада 223 данный буфер использует контекст устройства для вывода изобра- жений. И, наконец, метод Drawlmage из графического контекста фор- мы копирует изображение из буфера и выводит его на экран. После запуска программы станет понятно, что окончательно изба- виться от мерцания не удалось. Хотя улучшения есть, тем не менее, небольшое мерцание объектов все же осталось. Это связано с осо- бенностью перерисовки на уровне системы. Когда Windows рисует объекты на экране, она сначала заполняет его цветом фона. Затем при наступлении события Paint система рисует игровые элементы поверх фона. Поэтому, несмотря на наши ухищрения, мы по-преж- нему видим неприятный эффект мерцания. Нужно сделать так, чтобы система Windows не перерисовывала экран. Для этого следует переопределить метод OnPaintBackground, отвечающий за перерисовку экрана, причем новая версия метода вообще ничего не будет делать, что иллюстрирует листинг 11.14. Листинг 11.14 protected override void OnPaintBackground(PaintEventArgs pevent) { // He разрешаем перерисовывать фон } После добавления этого метода в программу мерцание исчезнет. Кусочек сыра теперь движется без всякого мерцания. Но теперь появилась другая проблема. Когда кусочек сыра прохо- дит через батон хлеба, то виден прямоугольник, обрамляющий изоб- ражение сыра. Кроме того, по условиям игры, сыр не может прохо- дить через батон, а должен при столкновении изменить свое направление и двигаться в другую сторону. Именно этой пробле- мой и нужно заняться сейчас. Хлеб — всему голова Наша программа должна уметь перемещать батон хлеба таким об- разом, чтобы игрок мог отбивать кусок сыра, как будто играя им в теннис. Для этой цели игрок будет использовать клавиши нави- гации на телефоне. Чтобы управлять батоном хлеба, придется ис- пользовать события KeyDown и КеуКр. Событие KeyDown наступает, когда пользователь нажимает на заданную кнопку. Событие KeyUp ини- циируется при отпускании кнопки.
224 Глава 11. Создание игр Необходимо установить флаг, который будет отслеживать нажатия и отпускания клавиш. Когда флаг будет активирован, это будет оз- начать, что пользователь нажал на клавишу, и батон должен дви- гаться в указанном направлении. Когда пользователь отпустит кла- вишу, то флаг сбрасывается и объект прекращает движение. Обработчики событий используют перечисления Keys, показываю- щие конкретные кнопки навигации. Соответствующий код приве- ден в листинге 11.15. Листинг 11.15 /// <summary> /// Используем keyArgs в качестве флага /// </summary> private System.Windows.Forms.KeyEventArgs keyArgs « null: private void FormlKeyDown (object sender. System.Wi ndows.Forms.KeyEventArgs e) { keyArgs = e; } private void Forml KeyUp (object sender. System.Wi ndows.Forms.KeyEventArgs e) { keyArgs = null; } Когда программа получает вызов события Forml KeyDown, флаг keyArgs ссылается на класс KeyEventArgs. При наступлении события Forml_KeyUp флаг keyArgs сбрасывается в nul 1, и код нажатых кла- виш игнорируется. Теперь надо переписать метод updatePositions, как показано в листинге 11.16. Листинг 11.16 private void updatePositions О { // Код для кусочка сыра остался прежним // Для батона хлеба if ( keyArgs != null ) { switch ( keyArgs.KeyCode ) {
Продуктовая аркада 225 case Keys.Up: by-=ySpeed; break; case Keys.Down: by+=ySpeed; break; case Keys.Left: bx-=xSpeed; break ; case Keys.Right: bx+=xSpeed; break; } } } В данном коде используется оператор swi tch, который определяет действия программы в зависимости от нажатой клавиши. Батон хлеба движется с той же скоростью, что и кусочек сыра. На этой стадии при запуске программы пользователь может перемещать батон хлеба по всему экрану, в то время как кусочек сыра по-преж- нему самостоятельно двигается по экрану. Обнаружение столкновений Для контроля столкновений в играх используются прямоуголь- ные области. Конечно, здесь далеко до реализма, так как предме- ты не всегда имеют прямоугольную форму. Но в некоторых слу- чаях пользователь может и не заметить этого. Ограничивающий прямоугольник вокруг изображения хлеба выглядит так, как по- казано на рис 11.4. (bx + batWidth. by + batHeight) Рис. 11.4. Ограничивающий прямоугольник для объекта Две точки позволяют оперировать координатами верхнего лево- го и нижнего правого углов прямоугольника. В .NET Compact Framework существует структура RECTANGLE, использующая эти координаты для реализации прямоугольника. Несколько мето- 8-2873
226 Глава 11. Создание игр дов используют эту структуру для обнаружения пересечения двух прямоугольников. С их помощью и можно обнаружить столкнове- ние объектов. Ранее использовавшиеся переменные надо заменить структурой RECTANGLE, в которой будет содержаться Информация о местонахождении объекта. Соответствующий код приведен в ли- стинге 11.17. Листинг 11.17 /// <summary> /// Позиция и ограничивающий прямоугольник для сыра /// </summary> private Rectangle cheeseRectangle; /// <summary> /// Позиция и ограничивающий прямоугольник для батона хлеба /// </summary> private Rectangle breadRectangle; Сразу после загрузки изображений надо ввести код, приведенный в листинге 11.18. Листинг 11.18 // Получим координаты и ограничивающие прямоугольники cheeseRectangle = new Rectangle(O, О, cheeseimage.Wi dth.cheeseimage.Hei ght); breadRectangle s new Rectangle!0, 0, breadimage.Width, breadImage.Height); Теперь для вывода картинок на экран надо использовать в методе Forml_Paint код, приведенный в листинге 11.19. Листинг 11.19 g.DrawImage(breadImage, breadRectangle.X, breadRectangle.Y); g.DrawImage(cheeseImage, cheeseRectangle.X, cheeseRectangle.Y); При помощи свойств X и Y этих прямоугольников можно переме- щать объекты по экрану. В методе updatePosi ti on надо заменить часть кода, отвечающую за движение сыра и батона, с учетом созданных переменных, как показано в листинге 11.20. Листинг 11.20 private void updatePositions!) {
Продуктовая аркада 227 // Движение кусочка сыра if (goingRight) { cheeseRectangle.X += xSpeed; } el se { cheeseRectangle.X -= xSpeed; } if ((cheeseRectangle.X + cheeselmage.Width) >= this.Width) { * ‘ goingRight = false: } if (cheeseRectangle.X <= 0) { goingRight = true; * } if (goingDown) { cheeseRectangle.Y +« ySpeed; } else { cheeseRectangle.Y -- ySpeed; } if ((cheeseRectangle.Y + cheeseimage.Height) >e this.Height) { goingDown = false; } if (cheeseRectangle.Y <= 0) G { goingDown = true; // Управление батоном if (keyArgs !• null) { switch (keyArgs.KeyCode) продолжение & 8*
228 Глава 11. Создание игр Листинг 11.20 (продолжение) case Keys.Up: breadRectangle.Y -= ySpeed; break; case Keys.Down: breadRectangle.Y += ySpeed; break; case Keys.Left: breadRectangle.X -= xSpeed; break; case Keys.Right: breadRectangle.X += xSpeed; break; } } /// и далее... Когда сыр ударяется о батон хлеба, он должен отскочить. Этого эффекта можно добиться, просто изменив направления движения по оси Y в методе updatePosi tion, как показано в листинге 11.21. Листинг 11.21 // Проверка на столкновение if ( cheeseRectangle.IntersectsWith(breadRectangle)) { goingDown - !goingDown; } Метод IntersectsWith принимает параметры прямоугольников. Если они пересекаются, то возвращается значение True, после чего меня- ется направление движения сыра. Запустите программу и попытайтесь отбить батоном движущийся кусочек сыра. Вы увидите, как сыр отскочит после столкновения. Столкновения батона и мяча Хотя код вполне нормально работает, все-таки хочется больше реа- лизма. Отвлечемся на минутку и рассмотрим пример столкнове- ний мячей с круглым предметом (рис. 11.5). Когда мяч ударяется о круглый объект, он отскакивает обратно, как показано на рисунке. Программа должна уметь определять вид столкновения для каждого мяча. По схожему принципу должна работать и наша программа.
Продуктовая аркада 229 Рис. 11.5. Столкновение круглых объектов На рис. 11.6 показаны использующиеся три вида столкновений. Первое столкновение происходит при наезде правой нижней части сыра на прямоугольник батона. Во втором случае оба нижних угла изображения сыра одновременно пересекаются с прямоугольником батона. И третий случай реализуется, когда изображение сыра ле- вой частью попадает на блокирующий прямоугольник. 1 2 з Рис. 11.6. Виды столкновений Нужно снова переписать код метода updatePositlon для новой реа- лизации модели столкновений, как показано в листинге 11.22. Листинг 11.22 if (goingDown) { // если сыр движется вниз if (cheeseRectangle.IntersectsWith(breadRectangle)) { // столкновение bool rightin = breadRectangle.Contains(cheeseRectangle. Ri ght, cheeseRectangle.Bottom); bool leftin = breadRectangle.Contains(cheeseRectangle. Left, cheeseRectangle.Bottom); // способ отражения if (rightin & leftin) { // отражается вверх n л r r продолжение ту
230 Глава 11. Создание игр Листинг 11.22 {продолжение) goingDown = false; } el se { // отражается вверх goingDown = false; // в зависимости от вида столкновений if (rightin) { goingRight = false; } if (leftin) { goingRight = true; } } } } Обратите внимание на то, что сыр отскакивает только при движе- нии в нижнюю часть экрана. Используя подобный подход, можно создать игру, в которой пользователь будет стараться не дать сыру упасть на дно экрана, отбивая его батоном. Новые объекты , 1 .;. 4.. Продолжим улучшать игру. Теперь в игру будут введены и поми- доры. Их изображения тоже надо ввести в состав проекта, как по- казано в листинге 11.23. Листинг 11.23 /// <summary> /// Изображение, содержащее помидор /// </summary> private Image tomatoimage = null; // Получаем изображение помидора tomatoimage - new System.Drawing.Bitmap! execAssem.GetMani festResourceStream(@"Bouncer.tomato.gi f”) ); Следует нарисовать несколько помидоров в верхней части экрана. Помидоры будут использоваться в качестве мишеней, которые нуж- но уничтожать, сбивая их кусочком сыра.
Продуктовая аркада 231 Для отслеживания попаданий нужно знать позицию каждого по- мидора и определять момент столкновения. Можно было создать массив, содержащий координаты каждого помидора, но лучше вос- пользоваться структурой, приведенной в листинге 11.24. Листинг 11.24 /// <summary> /// Позиция и состояние помидора /// </summary> struct tomato { public Rectangle rectangle; public bool visible; } Использование структуры позволит хранить позицию помидора и определять его видимость. При столкновении сыра с помидором овощ должен исчезнуть, позволяя тем самым игроку заработать очки. Размещение помидоров Нужно создать массив помидоров для размещения на экране, как показано в листинге 11.25. Листинг 11.25 /// <summary> /// Расстояние между помидорами. /// Устанавливаем один раз для игры /// </summary> private int tomatoSpacing = 4; /// <summary> /// Высота, на которой рисуется помидор /// Высота может меняться в процессе игры /// Начинаем с верхней части экрана /// </summary> private int tomatoDrawHeight = 4; /// <summary> III Количество помидоров на экране. /// Устанавливается при старте игры /// методом initialiseTomatoes. /// </summary> продолжение
232 Глава 11. Создание игр Листинг 11.25 (продолжение) private int noOfTomatoes: /// <summary> /// Позиции всех помидоров на экране /// </summary> private tomato[] tomatoes; При усложнении игры помидоры должны отображаться все ниже и ниже, заставляя пользователя действовать интуитивно. Перемен- ная tomatoDrawHeight будет отвечать за эту задачу. Для инициализа- ции местоположения помидоров нужно создать функцию initialiseTomatos, которая использует размеры помидоров и экра- на. Ее код приведен в листинге 11.26. Листинг 11.26 /// <summary> /// Вызывается один раз для установки всех помидоров /// </summary> private void initialiseTomatoesO { noOfTomatoes = (this.ClientSize.Width - tomatoSpacing) / (tomatoimage.Width + tomatoSpacing); // создаем массив, содержащий позиции помидоров tomatoes « new tomato[noOfTomatoes]; // Координата x каждого помидора int tomatoX = tomatoSpacing / 2; for (int i = 0; i < tomatoes.Length; i++) { tomatoes[i].rectangle = new Rectangle(tomatoX, tomatoDrawHeight, tomatoimage.Width, tomatoimage.Height); tomatoX = tomatoX + tomatoimage.Width + tomatoSpacing; } } Вызов этого метода следует разместить в конструкторе формы. Метод подсчитывает количество помидоров, создает массив струк- тур и задает прямоугольники, определяющие позицию каждого по- мидора на экране. Теперь их надо разместить на форме в один ряд. Код, отвечающий за эти действия, приведен в листинг 11.27. Листинг 11.27 /// <summary> /// Вызывается для создания ряда помидоров.
Продуктовая аркада- 233 /// </summary> private void placeTomatoesO { for (int i =0; i < tomatoes.Length; i++) { tomatoes[i].rectangle.Y = tomatoDrawHei ght; tomatoes[i].visible = true; } } Этот метод вызывается один раз при старте игры, а после этого он запускается после уничтожения очередного ряда томатов. Метод обновляет высоту с новым значением и делает изображения тома- тов видимыми. Вызов данного метода также размещается в конст- рукторе формы Итак, сейчас позиции всех томатов определены. Нужно вывести их изображения помидоров на экран. Код, приведенный в листинге 11.28, встраивается в обработчик события Forml Paint. Листинг 11.28 for ( int i = 0 ; i < tomatoes.Length ; i++ ) { if (tomatoes[i].visible) { g.DrawImage(tomatoImage, tomatoes[i].rectangle.X, tomatoes[i].rectangle.Y ); } } Каждый раз, когда страница перерисовывается, этот код перерисо- вывает все видимые томаты. Естественно, для отображения всех томатов используется одно и то же изображение. Чтобы сделать игру реалистичнее, нужно переместить началь- ную высоту батона чуть ниже, чтобы игрок мог сразу играть в игру с более подходящей позиции. Этот код приведен в листин- ге 11.29. Листинг 11.29 breadRectangle - new Rectanglе( (this.Clientsize.Width - breadimage.Width) / 2. this.ClientSize.Height - breadimage.Height. breadimage.Width. breadimage.Height );
234 - Глава 11. Создание игр Теперь игра выглядит так, как показано на рис. 11.7. я Рис. 11.7. Внешний вид игры Уничтожение томатов К сожалению, в данный момент при столкновении сыра с помидо- рами ничего не происходит. Ситуацию надо исправить при помо- щи кода, добавленного в метод updatePosition, который приведен в листинге 11.30. Листинг 11.30 // Уничтожаем помидоры при столкновении с сыром for ( int i = 0 ; 1 < tomatoes.Length ; i++ ) { if ( !tomatoes[i].visible ) { continue: } if ( cheeseRectangle.IntersectsWith(tomatoes[i].rectangle) ) { // прячем томат tomatoes[i].visible = false; // отражаемся вниз goingDown = true ; // только удаляем помидор break; } } Код выполняется, когда сыр двигается вверх. При этом проверяют- ся позиции каждого помидора и куска сыра при помощи метода IntersectsWith. Если произошло столкновение сыра с томатом, то томат делается невидимым, для чего свойству Visible присваивает-
Продуктовая аркада 235 ся значение False. При следующей перерисовке экрана этот томат не появится на экране. Сыр должен отскакивать от помидора, как от стенок или от батона. Счет игры Итак, это уже похоже на игру. Но пока ей не хватает увлекатель- ности. Нужно добавить подсчет результатов. Отображение резуль- татов игры — не самая сложная задача. Мы можем выводить текст на экран с помощью метода DrawString. Но при этом потребуется указать шрифт, кисть и координаты вывода текста. Начать стоит со шрифта. Его надо инициализировать в конструкторе формы при помощи кода, приведенного в листинге 11.31. Листинг 11.31 /// <summary> /// Шрифт для вывода счета /// </summary> private Font messageFont = null; // Создадим шрифт для показа набранных очков messageFont = new Font(FontFamily.GenericSansSerif, 10, FontStyle.Regular); Теперь необходимо выбрать прямоугольник, в котором будет ото- бражаться текст. Нужно зарезервировать 15 пикселов в верхней части экрана для отображения текущего счета. При этом потребу- ется модифицировать игру, чтобы двигающиеся объекты не попа- дали в эту область. Используя переменную для хранения этой высоты, можно легко изменить размеры информационной панели, если понадобится. Прямоугольник инициализируется при загрузке формы, как пока- зано в листинге 11.32. Листинг 11.32 /// <summary> /// Прямоугольник, в котором будет отображаться счет игры /// </summary> private Rectangle messageRectangle ; III <summary> /// Высота панели для счета. продолжение #
236 Глава 11. Создание игр Листинг 11.32 (продолжение) /// </summary> private int scoreHeight = 15; // Устанавливаем размеры прямоугольника для счета messageRectangle = new Rectanglе(0. О, this.ClientSize.Width, scoreHeight); Если прямоугольник будет слишком мал для текста, то текст будет обрезаться при отображении. После того как будут заданы шрифт и область для отображения тек- стовой информации, пора позаботиться о кисти. Выбирая тип кис- ти, одновременно указывайте цвет и узор для рисования, как пока- зано в листинге 11.33. Листинг 11.33 /// <summary> /// Кисть, используемая для отображения сообщений /// </summary> private SolidBrush messageBrush; // Выбираем красную кисть messageBrush = new SolidBrush(Color.Red); Текст счета игры на экране будет отображаться красным цветом. Чтобы вывести сообщение на экран, понадобится вызвать метод DrawString в событии Forml Paint, как показано в листинге 11.34. Листинг 11.34 /// <summary> /// Строка для »ывода сообщений /// </summary» private string messagestring = "Нажмите Старт для начала игры"; g.DrawString(messageString. messageFont, messageBrush. messageRectangle); Созданная переменная messageString применяется для вывода со- общений на экран во время игры. Ведение счета Теперь нужно научиться обновлять счетчик столкновения томатов в методе updatePosition. Код для этого приведен в листинге 11.35.
Продуктовая аркада 237 Листинг 11.35 /// <summary> /// Счет в игре /// </summary> private int scoreValue = 0; private void updatePositionsO { if (cheeseRectangle.IntersectsWith(tomatoes[i].rectangle)) { // прячем томат tomatoes[i].visible * false; // отражаемся вниз goingDown = true; // обновляем счет scoreValue = scoreValue + 10; messagestring = "Счет: " + scoreValue; break; } } За каждый уничтоженный томат начисляется 10 очков. Эти дан- ные постоянно обновляются и выводятся на экран. Звуковые эффекты Неплохо бы добавить в игру звуковые эффекты. К сожалению, биб- лиотека .NET Compact Framework пока не поддерживает воспро- изведение звуковых файлов при помощи управляемого кода. По- этому придется воспользоваться механизмом Platform Invoke (Р/ Invoke). В главе, посвященной вызовам функций Windows API, эта тема будет освещаться подробнее. Для воспроизведения звуков можно встроить звуковой файл в саму программу, как это делалось с изображениями, либо проиг- рывать сам звуковой файл, который расположен где-то в файло- вой системе. В этом проекте требуется создать отдельный класс для воспроизве- дения звуков. Нужно щелкнуть правой кнопкой мыши на проекте Bouncer в окне Solution Explorer и выполнить команду контекстного меню Add ► New Item... В открывшемся окне нужно выбрать элемент Class и задать имя Sound.cs. После нажатия кнопки Add новый класс будет добавлен в проект.
238 Глава 11. Создание игр Класс Sound будет иметь два метода. Один метод создает экземп- ляр класса Sound, читая данные из заданного файла. Второй метод предназначен для проигрывания звука. Также в составе класса будет находиться свойство, позволяющее настраивать громкость звука. В начале файла Sound.cs надо расположить строки для подклю- чения используемых пространств имен, как показано в листинге 11.36. Листинг 11.36 using System.Runtime.InteropServices; using System.10; Наш пример co звуком просто хранит в памяти байтовый массив с аудиоматериалом. Для обращения к этому блоку используется функция операционной системы, способная производить звуки. В классе Sound блок памяти объявляется так, как показано в лис- тинге 11.37. Листинг 11.37 /// <summary> /// массив байтов, содержащий данные о звуке /// </summary> private byte[] soundBytes; Эта конструкция не создает массив, а только объявляет его. Мас- сив будет создан при конструировании экземпляра класса, ведь из- начально размер звукового файла неизвестен. Код Конструктора приведен в листинге 11.38. Листинг 11.38 /// <summary> /// Создание экземпляра sound и хранение данных о звуке /// </summary> /// <param name»"soundstream">Поток для чтения звука</рагат> public Sound(Stream soundstream) { // создаем массив байтов для приема данных soundBytes - new byte[soundStream.Length]; // читаем данные из потока soundstream.Read(soundBytes, 0, (int)soundstream.Length); }
Продуктовая аркада 239 Поток связывается с файлом или другим источником данных. Он имеет свойство Length, определяющее размер массива. Метод Read применяется для получения информации, после чего прочитанные байты сохраняются в массиве. Звуковые файлы хранятся в виде ресурсов, как и изображения. В проект надо добавить звуковые файлы click.wav и burp.wav и для их свойства Build Action задать значение Embedded Resources. Теперь доступ к звуковым файлам получить очень просто, что иллюстри- рует код, приведенный в листинге 11.39. Листинг 11.39 /// <summary> /// Звук, воспроизводимый при столкновении с батоном хлеба /// </summary> private Sound batHitSound: /// <summary> /// Звук, воспроизводимый при столкновении с помидором /// </summary> private Sound tomatoHitSound; // Получим звук при столкновении с батоном хлеба batHitSound = new Sound (execAssem.GetManifestResourceStream(@nBouncer.click.wav")); // Получим звук при столкновении с помидором tomatoHitSound = new Sound (execAssem.GetMam festResourceStream(@”Bouncer.burp.wav")); Для воспроизведения звука в класс Sound надо добавить метод Р1 ау, как показано в листинге 11.40. Листинг 11.40 /// <summary> /// Управление звуком в игре (Включать или выключать) /// </summary> public static bool Enabled = true; /// <summary> /// Проигрываем звук /// </summary> public void PlayO {
240 Глава 11. Создание игр Листинг 11.40 (продолжение) if (Sound.Enabled) { WCEPlaySoundBytes( soundBytes. IntPtr.Zero, (int)(Flags.SND_ASYNC | Flags.SNDMEMORY)); } } Метод Pl ay проверяет флаг переменной Enabl ed. С его помощью мож- но легко включать или выключать звук в игре. Воспроизведение зву- ка обеспечивается вызовом функции Windows API WCE Pl aySoundBytes, что иллюстрирует код, приведенный в листинге 11.41. Листинг 11.41 private enum Flags SNDSYNC - 0x0000, SND-ASYNC = 0x0001, SND NODEFAULT = 0x0002, SNDMEMORY = 0x0004, SNDLOOP = 0x0008, SNDNOSTOP = 0x0010, SNDNOWAIT » 0x00002000, SND_ALIAS = 0x00010000, SNDALIASID = 0x00110000, SND_FILENAME = 0x00020000, SNDRESOURCE = 0x00040004 } /// <summary> /// Функция Windows API для воспроизведения звука. /// </summaгу> /// <param name="szSound">MaccnB байтов, содержащих данные /// </param> /// <param пате="ЬЬЫ">Дескриптор к нодулю, содержащему звуковой /// pecypc</param> /// <param name=”flags">0narn для управления звукон</рагат> /// <returns></returns> EDI 1 Import("CoreDll.DLL", EntryPoint = "PlaySound", SetLastError - true)] private extern static int WCEPlaySoundBytes( byte[] szSound,
Продуктовая аркада 241 IntPtr hMod, int flags): Теперь, когда создан экземпляр класса Sound, можно воспроизво- дить звук при столкновении сыра с батоном хлеба. Соответствую- щий код приведен в листинге 11.42. Листинг 11.42 // если сыр движется вниз if (cheeseRectangle.IntersectsWith(breadRectangle)) { // столкновение // воспроизводим удар batHitSound.PlayО; } Можете запустить проект, чтобы проверить работу звука. Также можно добавить звук при столкновении сыра с помидорами. Этот код приведен в листинге 11.43. Листинг 11.43 if (cheeseRectangle.IntersectsWith(tomatoes[i].rectangle)) { // воспроизводим звук столкновения сыра с помидором tomatoHi tSound.Pl ay(); } Дальнейшие улучшения Но игру все еще можно улучшить. В следующем списке указаны дополнительные возможности, которые необходимо реализовать. □ Режим «attract», включающийся, когда пользователь не играет. □ Потеря жизни, если сыр ударился о нижнюю границу экрана. □ При уничтожении всех томатов они должны появиться чуть ниже, и скорость игры должна возрасти. □ Добавление в игру случайных элементов. В программу надо ввести булеву переменную gameLi ve, которая име- ет значение True, когда пользователь ведет игру. Если значение пе- ременной равно False, то сыр будет двигаться по экрану, но ника- ких игровых действий производиться не будет. Для этого потребуется изменить метод, выполняющийся при стар- те игры. Новая версия приведена в листинге 11.44.
242 Глава 11. Создание игр Листинг 11.44 /// <summary> /// True, если игра запущена на экране. /// «/summary* private bool gameLive = false; /// «summary* /// Число оставшихся жизней. /// «/summary» private int livesLeft; /// «summary* /// Число жизней, доступных для игрока. /// «/summary* private int startLives = 3; private void startGame () { // Устанавливаем число жизней, счет и сообщения livesLeft = startLives; scoreValue = 0; messagestring = “Счет: 0 Жизнь: " + livesLeft; // Располагаем помидоры наверху экрана tomatoDrawHeight - tomatoLevelStartHeight; placeTomatoesO; // Поместим батон в центре экрана breadRectangle.X = (this.ClientSize.Width-breadRectangle.Width) / 2; breadRectangle.Y - this.ClientSize.Height / 2; // Поместим сыр над батоном в центре экрана cheeseRectangle.X = (this.ClientSize.Width-cheeseRectangle.Width) / 2; cheeseRectangle.Y - breadRectangle.Y - cheeseRectangle. Height: // Установим начальную скорость xSpeed = 1; ySpeed = 1; // Установим флаг, позволяющий начать игру gameLive - true; }
Продуктовая аркада 243 Этот код возвращает все объекты на исходные позиции и начина- ет новую игру. Батон располагается в середине экрана, а сыр чуть выше него. Этот метод связан с пунктом меню, позволяющим на- чать игру. Теперь надо добавить код, который проверяет, не коснулся ли сыр нижней границы экрана В этом случае вызывается метод loseLi fe, который уменьшает количество жизней у игрока. Соответствующий код приведен в листинге 11.45. Листинг 11.45 if ( ( cheeseRectangle.Y + cheeselmage.Height ) >= this.Height ) { // сыр достиг нижней границы экрана loseLifeO; goingDown - false; } Метод loseLi fe подсчитывает количество оставшихся жизней и за- канчивает игру, если все жизни были израсходованы. Также метод может показывать лучший достигнутый счет игры. Его код приве- ден в листинге 11.46. Листинг 11.46 private void loseLifeO { if (JgameLive) { return; } // Потеряли еще одну жизнь livesLeft--; if (livesLeft > 0) { // обновим сообщение на экране messagestring = "Счет: " + scoreValue + " Жизнь: " + livesLeft; } el se { // Останавливаем игру garnetive - false; продолжение &
244 Глава 11. Создание игр Листинг 11.46 (продолжение) И сравниваем с лучшим результатом if (scoreValue > highScoreValue) { highScoreValue = scoreValue; } // меняем сообщение на экране messagestring = "Лучший результат: " + highScoreValue; } } Этот код не выполняется, если игра не запущена. При вызове ме- тод уменьшает количество жизней и подсчитывает оставшееся чис- ло. Пока есть жизни, игра продолжается. В противном случае об- новляется счет и игра выключается. Последний метод в нашей игре отвечает за перерисовку томатов, когда они все уничтожены. Чтобы отследить эту ситуацию, в метод Forml Paint добавлен очень простой код, который приведен в лис- тинге 11.47. Листинг 11.47 bool gotTomato - false ; for ( int i » 0 ; i < tomatoes.Length ; i++) { if (tomatoes[i].visible) { gotTomato = true; g.DrawImage(tomatoImage, tomatoes[i].rectangle.X, tomatoes[ij.rectangle.Y ); } } if ( ’gotTomato ) { newLevel (); } Если пользователь выбил все томаты, то вызывается метод newLevel. Метод просто перерисовывает томаты и увеличивает скорость, как показано в листинге 11.48. Листинг 11.48 private void newLevel О {
Продуктовая аркада 245 if ( !gameLive ) { return; } // Рисуем помидоры чуть ниже tomatoDrawHeight += tomatoSpacing ; if ( tomatoDrawHeight > ( ClientSize.Height- (breadRectangle.Height+tomatoImage.Height) ) ) { // Рисуем помидоры снова верхней части экрана tomatoDrawHeight = tomatoLevelStartHeight; } placeTomatoesO; // Увеличиваем скорость if ( xSpeed < maxSpeed ) { xSpeed++; ySpeed++; } } Метод перемещает томаты все ниже и ниже. Когда они почти достиг- нут края экрана, то будут снова перемещены в верхнюю часть экрана. Тестирование Игра практически готова. Теперь нужно протестировать ее. Чтобы не играть самому несколько часов, надо поручить эту работу ком- пьютеру. Достаточно лишь изменить метод updatePosition, как по- казано в листинге 11.49 Листинг 11.49 Тестирование программы в автоматическом режиме /// <summary> /// Тестирование программы. Батон автоматически отслеживает /// движение сыра /// </summary> private bool testingGame - true ; if ( testingGame ) breadRectangle.X = cheeseRectangle.X; продолжение &
246 Глава 11. Создание игр Листинг 11.49 (продолжение) breadRectangle.Y = ClientSize.Height - breadRectanglе.Hei ght; } Булева переменная testingGame может принять значение True. В этом случае позиция батона всегда будет соответствовать позиции сыра. В этом состоянии игра будет действовать сама, без участия пользова- теля и без потери жизней. Можно откинуться на спинку кресла и от- дыхать. И опять добавляем новые объекты На данный момент игра довольно прямолинейна. Надо добавить ей сложности для повышения зрелищности. В игру нужно ввести допол- нительный бонус в виде кусочка ветчины, который будет периодичес- ки появляться на экране. Если игрок сумеет коснуться его батоном, то заработает несколько дополнительных очков. Но при этом игрок не должен забывать отбивать сыр, чтобы не потерять жизнь. Ветчина по- является на экране на короткое время, и игрок должен сам решить, нужно ему охотиться за ветчиной или отбивать сыр. Сначала надо добавить графическое изображение ветчины в про- грамму как ресурс. Затем потребуется создать несколько перемен- ных, с помощью которых можно контролировать свойства нового объекта. Соответствующий код приведен в листинге 11.50. Листинг 11.50 /// <summary> /// Изображение ветчины /// </summary> private Image bonusHamlmage = null; /// <summary> /// Позиция и ограничивающий прямоугольник для ветчины /// </summary> private Rectangle bonusHamRectangle; /// <summary> /// Звук, воспроизводимый при столкновении с ветчиной /// </summary> private Sound bonusHamSound; // Получим изображение ветчины
Продуктовая аркада 247 bonusHamlmage = new System.Drawing.Bitmap( ехесА55ет.6е1Мап1Те51Ке5оигсе51геат(&"Воипсег.Ьат.д1Ти) ); // Создадим прямоугольник для ветчины bonusHamRectanglе = new Rectangle( 0. 0, bonusHamlmage.Width,bonusHamlmage.Height ); // Получим звук при столкновении с ветчиной bonusHamSound = new Sound(execAssem.GetManifestResourceStream(@"Bouncer.pig.wav")); Для управления изображением ветчины надо создать новый метод, код котрого приведен в листинге 11.51. Листинг 11.51 /// <summary> /// True, если ветчина на экране /// </summary> private bool hamPresent = false; /// <summary> /// Интервал от 0 до 10. Чем выше значение. /// тем чаще ветчина появляется на экране /// </summary> private int hamLikelihood = 5; /// <summary> /// Отчет времени перед исчезновением ветчины. /// Устанавливаем случайное число при появлении ветчины. /// </summary> private int hamTimerCount; /// <summary> /// Случайное число. /// </summary> private Random randomNumbers; /// <summary> /// Вызывается для активизации ветчины /// </summary> private void startHam () { // не продолжать, если ветчина уже есть на экране продолжение &
248 Глава 11. Создание игр Листинг 11.51 (продолжение) if (hamPresent) { return : } И решаем, как часто выводить ветчину на экран if ( randomNumbers.Next(lO) > hamLikelihood ) { // не выводить ветчину на экран return; } // позиция ветчины в случайной позиции на экране bonusHamRectangle.X = randomNumbers.NextCClientSize.Width - bonusHamRectangle. Width ); bonusHamRectangle.Y = randomNumbers.Next (Clientsize.Height - bonusHamRectangle. Height ); // как долго держится изображение ветчины на экране // (по крайне мере 50 тиков) hamTimerCount = 50 + randomNumbers.Next(100); // делаем ветчину видимой hamPresent « true; } На первый взгляд код кажется сложным. Но все очень просто. Ме- тод вызывается каждый раз при столкновении сыра с томатом. Если ветчина уже отображается на экране, то метод ничего не делает. Если ветчины на экране нет, то программа использует случайное число для принятия решения, нужно ли показывать на экране изоб- ражение. Генерируется случайное число в промежутке от 0 до 10. Ветчина не выводится, если это число больше, чем заданная пере- менная. В нашем случае значение hamLikel ihood равно 5. Это означает, что ветчина будет появляться в половине случаев. При помощи этой переменной можно регулировать частоту появления изображения ветчины на экране. Если метод решит вывести ветчину на экран, он выбирает случайную позицию и устанавливает расположение кар- тинки. Также метод инициализирует счетчик таймера для отчета длитель- ности присутствия ветчины на экране. Программа использует ми- нимальное время вкупе со случайным периодом. Таким образом,
Продуктовая аркада 249 пользователь никогда не будет знать, как долго ветчина будет ви- дима. Каждый раз при обновлении игры программа должна обнов- лять состояние куска ветчины. Если игрок коснулся изображения ветчины, то надо увеличить счет и удалить изображение. Соответ- ствующий код приведен в листинге 11.52. Листинг 11.52 /// <summary> /// Обновляем состояние ветчины /// </summary> private void hamTickO { // ничего не делаем, если ветчина невидима if ( ’hamPresent) { return ; } if ( breadRectangle.IntersectsWith(bonusHamRectangle) ) { 11 при касании игроком куска ветчины // прибавляем 100 очков scoreValue = scoreValue + 100; messageString = "Счет: " + scoreValue + " Жизнь: " + livesLeft: // звук касания ветчины bonusHamSound.Pl ay(); // прячем ветчину с экрана hamPresent = false; } else { // Отчитываем время назад hamTimerCount--; if ( hamTimerCount = 0 ) { // время вышло - удаляем ветчину hamPresent = false; } } } Также надо изменить код методов Forml_Pa1 nt и updatePosi ti on. Если изображения батона и ветчины пересекаются, то нужно увеличить счет и удалить изображение ветчины. В ином случае надо умень-
250 Глава 11. Создание игр шить время отображения ветчины или удалить это изображение, если соответствующий период времени уже закончился. Соответ- ствующий код приведен в листинге 11.53. Листинг 11.53 //(FormlPaint) // Выводим на экран кусок ветчины if ( hamPresent ) { g.DrawImage(bonusHamImage. bonusHamRectangle.X, bonusHamRectangle.Y); } //(updatePosition) // Активизируем ветчину startHamO; //(timerTick) hamTickO; Но мы можем продолжить улучшение игры, добавляя в нее новые возможности. Все изменения по-прежнему будут происходить в про- екте Bouncer. Теперь предстоит создать таблицу лучших результатов, улучшить работу графики и разобраться с применением спрайтов. Управление таблицей результатов Созданная программа может показывать лучший результат, достиг- нутый пользователем. Но после ее перезапуска лучшие результаты теряются, и все приходится начинать сначала. Для устранения этого недостатка нужно добавить возможность сохранения имени пользо- вателя при достижении высокого результата. Также следует сохра- нять результат и имя пользователя при выходе из программы. Поэтому понадобится еще одна форма для отображения имен иг- роков. При достижении лучшего результата эта форма будет пока- зана на экране, чтобы пользователь мог ввести свое имя. Новую форму надо добавить в проект и задать для нее имя High- Score.cs. На созданной форме следует разместить текстовое поле для ввода имени и меню, которое сигнализирует об окончании ввода. Созданная форма будет отображаться при достижении высокого ре- зультата. В этом случае игрок-рекордсмен вводит свое имя и нажи- мает на пункт меню ОК для закрытия формы и сохранения имени.
Продуктовая аркада 251 Переключение между формами Программа должна выводить форму с результатами поверх основ- ной формы игры, чтобы позволить игроку ввести имя, а затем вер- нуться к игре. Когда форма с лучшими результатами появляется на экране, основная форма должна быть скрыта. И наоборот, при за- крытии окна с результатами основная форма восстанавливается. При загрузке формы генерируется событие Load. При закрытии формы генерируется событие Closing. Программа должна контро лировать эти события для реализации поставленной задачи. При старте программы создается экземпляр формы HighScore. Дан- ный экземпляр имеет ссылку на родительскую форму. При дости- жении высокого результата форма HighScore выводится на экран. При этом выполняется метод HighScore Load, который скрывает ро- дительскую форму. На экране появляется форма, отображающая лучшие результаты, игрок вводит свое имя и выполняет команду меню ОК. При этом срабатывает обработчик события для меню ОК, которое закрывает форму HighScore. При закрытии формы выпол- няется метод HighScore_Closi ng. Основное окно формы снова появ- ляется на экране. Код главной формы извлекает имя игрока из фор- мы HighScore. Итак, метод HighScore Load должен скрыть родительскую форму. Для этого метод должен использовать ссылку на главное окно. Ссылка на родительское окно передается в форму Hi ghScore при ее созда- нии, как показано в листинге 11.54. Листинг 11.54 /// <summary> /// Родительское окно, из которого вызывается данное окно. /// Используется при закрытии данного окна. /// </summary> private Form parentForm; public HighScore(Form inParentForm) { // Сохраняем родительское окно при закрытии окна лучших // результатов. parentForm = inParentForm; Initial!zeComponent(); } Этот код является конструктором формы HighScore. Когда идет соз- дание формы, то передается ссылка на родительскую форму.
252 Глава 11. Создание игр Код метода HighScore_Load приведен в листинге 11.55. Листинг 11.55 private void HighScore_Load(object sender. System.EventArgs e) { parentForm.HideO; } При загрузке формы родительское окно автоматически прячется. При закрытии формы надо вернуть родительскую форму на экран. Для этого применяется код, приведенный в листинге 11.56. Листинг 11.56 private void HighScore_Closing(object sender, System.ComponentModel.CancelEventArgs e) { parentForm.ShowO; } После ввода имени игрок выполняет команду меню ОК для закры- тия формы. Обработчик этого события приведен в листинге 11.57. Листинг 11.57 private void doneMenuItem_Click(object sender. System.EventArgs e) { CloseO; } После закрытия окна вызывается обработчик события, который выводит главное окно на экран. Отображение дочернего окна Программа должна получить имя игрока при достижении им высо- кого результата. Для этого создается копия формы HighScore. Про- грамма должна создать форму при старте и хранить ссылку на нее. Экземпляр формы Hi ghScore создается при старте основной програм- мы, вызывая конструктор и передавая ссылку на родительскую фор- му, в нашем случае на саму себя, как показано в листинге 11.58. Листинг 11.58 /// <summary> /// Форма для ввода имени игрока с лучшим результатом.
Продуктовая аркада 253 /// </summary> private HighScore highScore: // Создаем форму для лучших результатов highScore = new HighScore(this): В этом коде ключевое слово th 1 s является ссылкой на текущий эк- земпляр основной формы, который должен быть закрыт при откры- тии формы highScore и восстановлен при закрытии формы highScore. Код для отображения формы highScore приведен в листинге 11.59. Листинг 11.59 if ( scoreValue > highScoreValue ) { timerl.Enabled=false: // Показываем форму для лучших результатов hi ghScore.ShowDi alog(); ti mer1.Enabled=true: } Если игрок побил текущий лучший результат, то программа оста- навливается при помощи отключения таймера. Для отображения формы hi ghScore вызывается метод ShowDi al од. Игра должна сделать паузу, пока игрок вводит свое имя. После этого игра продолжается. Получение имени игрока Игрок вводит свое имя в текстовое поле формы highScore. Чтобы получить доступ к имени пользователя во время игры, необходимо иметь доступ к экземпляру формы Hi ghScore. В классе Hi ghScore надо создать свойство, с помощью которого можно получить введенное пользователем имя. Этот код приведен в листинге 11.60. Листинг 11.60 /// <summary> /// Имя игрока, введенное в текстовом поле. /// </summary> public string PlayerName { get { return nameTextBox.Text: } }
254 Глава 11. Создание игр Свойство Name извлекает имя из текстового поля nameTextBox и воз- вращает его тому, кто вызывал данное свойство. Это свойство ис- пользуется в программе, как показано в листинге 11.61. Листинг 11.61 /// <summary> /// Имя игрока, достигшего лучшего результата. /// </summary> private string highScorePlayer = "Rob": if ( scoreValue > highScoreValue ) { highScoreValue = scoreValue ; ti merl.Enabled=false: hi ghScore.ShowDi alog(); ti merl.Enabled-true; highScorePlayer = highScore.PlayerName; } Теперь с помощью переменной hi ghScorePl ayer можно выводить имя лучшего игрока во время игры. Хранение лучших результатов Теперь игроку может указывать свое имя при достижении хороше- го результата. Но нужно как-то сохранять это имя и достигнутый результат. Эту информацию будем хранить в той же папке, где и саму программу. Значит, наша программа должна автоматически определять свое местонахождение в файловой системе, чтобы знать, где хранить эту информацию. За это отвечает код, приведенный в листинге 11.62. Листинг 11.62 /// <summary> /// Папка, в которой находится программа. /// Используется как место для хранения настроек игры. /// </summary> private string applicationDirectory: // Получим имя файла программы из текущей сборки string appFilePath - execAssem.GetModules()[0].Ful1yQuali fi edName; // Выделяем из полного пути имени файла только путь к файлу
Продуктовая аркада 255 applicationDirectory = System.lO.Path.GetDirectoryName(appFilePath); // Обязательно должен быть разделитель в конце пути if (!applicationDirectory.EndsWith(@”\")) { applicationDirectory += } С помощью данного кода можно получить ссылку на первый модуль в программной сборке. Затем с помощью свойства Ful lyQual i f iedName можно получить полный путь к файлу программы. Текущий каталог можно получить с помощью свойства Get Di rectoryName. Также нам нужно быть уверенным, что путь к файлу заканчивается обратным слэшем. Небольшой код с проверкой решит эту проблему. Метод сохранения информации очень прост. Он приведен в листинге 11.63. Листинг 11.63 /// <summary> /// Имя файла для хранения лучших результатов. /// </summary> private string highScoreFile = "highscore.bin": /// <summary> /// Сохраняем лучший результат в файле. /// </summary> public void SaveHighScoreO { System.10.Textwriter writer = null: try { writer = new System.10.StreamWriter( applicationDirectory + highScoreFile): wri ter.Wri teLine(hi ghScorePlayer): wri ter.Wri teLi ne(hi ghScoreValue): } catch {} finally { if ( writer != null) { writer.CloseO; } } }
256 Глава 11. Создание игр Метод сохранения результата в файле вызывается при выходе из программы. Загрузка лучших результатов выполняется при старте программы с помощью метода LoadHi ghScore, код которого приве- ден в листинге 11.64. Листинг 11.64 /// <summary> /// Загружаем лучший результат из файла. /// </summary> public void LoadHighScore 0 { System.10.TextReader reader - null; try { reader = new System.10.StreamReader( applicationDirectory + highScoreFile ); highScorePlayer = reader. ReadLineO; string highScoreString - reader.ReadLineO; highScoreValue - int.Parse(highScoreString); } catch {} finally { if ( reader != null ) { reader.CloseO; } } } Улучшение графики На данный момент игра достаточно увлекательна, но графика ос- тавляет желать лучшего. Когда объекты проходят друг через друга, можно увидеть ограничивающие прямоугольники объекта. Надо ис- править эту ситуацию. Для решения проблемы можно использовать прозрачность. Прин- цип работы с прозрачностью очень прост. Надо выбрать один или несколько цветов, после чего остается указать, что они объявляют- ся прозрачными. В этом случае прозрачные пикселы не участвуют в отображении картинок. Когда картинка рисуется без прозрачных цветов, она просто ко- пируется в память. Применение прозрачных цветов заставляет ма- шину проверять каждый пиксел для перерисовки, что увеличива-
Продуктовая аркада 257 ет нагрузку на процессор. В полной версии библиотеки .NET Framework разработчик может несколько цветов делать прозрач- ными. В библиотеке .NET Compact Framework это можно сделать с одним цветом. Использование прозрачности реализуется при помощи класса ImageAttrlbutes пространства имен System. Drawl ng. Нужно создать новую переменную transparentWhite, так как белый цвет в изобра- жениях будет считаться прозрачным. Экземпляр класса создается при старте программы, как показано в листинге 11.65. Листинг 11.65 /// <summary> /// Маска для белого цвета, который будет считаться прозрачным /// «/summary* private System.Drawing.Imaging.ImageAttributes transparentWhi te; // Задаем белую маску. transparentWhite = new System.Drawing.Imaging.ImageAttributes!); transparentWhite.SetColorKey(Col or.White. Col or.White): Напомню, что в .NET Framework метод SetColorKey принимает ряд цветов, а в .NET Compact Framework один и тот же цвет дается дваж- ды. Этот цвет будет прозрачным для всех картинок, отображаемых с помощью класса ImageAttribute. Если в игре понадобятся белые цвета, то они не должны быть совершенно белыми. Объекты игры были созданы так, чтобы их фон был абсолютно бе- лым. Значения атрибутов, используемых при рисовании кусочка сыра, реализованы так, как показано в листинге 11.66. Для других объектов код будет абсолютно таким же. Листинг 11.66 // Выводим на экран кусочек сыра g.DrawImage( cheeseimage, // Image cheeseRectangle, // Dest. rect. 0. // srcX 0. // srcY cheeseRectangle.Width, // srcWidth cheeseRectangle.Height, // srcHeight GraphicsUnit.Pixel, // srcUnit transparentWhite); // ImageAttributes 9-2873
258 Глава 11. Создание игр В ранней версии игры вызывалась другая версия метода Drawlmage. Теперь же задается прямоугольник и указывается прозрачный цвет. Чтобы прозрачность работала должным образом, сыр должен ри- соваться на экране после отображения батона. Итак, мы рисуем прозрачные области для батона, куска сыра и вет- чины. Мы обошли вниманием помидоры, которые пока не перекры- ваются. Этот недостаток будет исправлен чуть позже. В качестве украшения надо добавить фоновую картинку в виде красочной ска- терти (рис. 11.8). Рис. 11.8. Фон для игры Картинка должна иметь размер клиентской части экрана с белым пространством в верхней части для ведения счета. Добавить фон не так уж и трудно. Вместо заливки экрана белым цветом в каждом кадре надо просто отрисовать этот узор. Следует объявить новую переменную backgroundimage для картинки-фона, загрузить изображение из ресурсов и изменить код в методе Forml_Paint, как показано в листинге 11.67. Листинг 11.67 /// <summary> /// Изображение, содержащее фон игры. /// </summaгу> private Image backgroundImage = null; // Полунин изображение фона игры backgroundimage = new System.Drawing.Bitmap! execAssem.GetMani festResourceStream(@"Bouncer.tablecloth.gif“) ): g.DrawImage(backgroundImage, 0. 0); Код загружает картинку как ресурс. Программа теперь может ис- пользовать прозрачность для отображения томатов.
Продуктовая аркада 259 Программа неплохо работает в эмуляторе, но не очень хорошо на настоящем КПК, так как процесс рисования все еще имеет некото- рые недочеты. Для их устранения следует применять спрайты. Спрайты Предыдущие версии программы выводили на экран каждое имею- щееся изображение не самым лучшим образом. Скатерть, помидо- ры, хлеб, сыр и ветчина постоянно перерисовываются при обнов- лении экрана. Однако проще использовать экран в виде ряда слоев, как показано на рис. 11.9. Нижний слой — это фоновая картинка. Этот слой рисуется один раз в начале загрузки программы. Библиотека спрайтов содержит класс Background для работы с фоном. Средний слой — это спрайты, которые неподвижны. Их не нужно постоянно перерисовывать. Они меняют свое состояние только при ударах кусочка сыра или при запуске нового уровня. За них отве- чает класс BackSprite. Рис. 11.9. Структура экрана Верхний слой — это спрайты, которые постоянно перемещаются по экрану. Они должны постоянно перерисовываться. Данные спрай- ты реализуются классом ForeSprite. Классы Background, BackSpri te и ForeSpri te находятся в базовом классе Sprite, который используется программой для хранения информа- ции о картинках и их расположении на экране. Также библиотека содержит класс Play Field, который поддерживает список спрайтов и управляет их видом на экране. Нам придется переписать почти весь код с учетом нового добавленного класса. Основной движок игры просто управляет движением передних спрайтов, а также отслеживает состояние и позицию фоновых 9*
260 Глава 11. Создание игр спрайтов. Данная версия библиотеки спрайтов немного отличается от прежней версии игры. Сыр теперь уничтожает томаты при движе- нии вниз к нижней части экрана. Сыр может застрять позади линии томатов, набирая тем самым призовые очки. Автор игры автор Роб Майлз предлагает изучить применение спрайтов на примере другой игры, «Salad Rescue». Вам придется самостоятельно изучить эту игру. Версия игры, использующая спрайты, располагается в папке BouncerSprite, которая входит в состав материалов для книги, рас- положенных на сайте издательства «Питер». Другие игры Как уже говорилось ранее, в документации MSDN имеется множе- ство примеров различных игр. Если вы проявите настойчивость, то самостоятельно найдете эти примеры и сможете разобрать их. Также стоит посетить сайт CodeProject, где по адресу www.codeproject.com/ netcf/#Games расположился специальный подраздел, посвященный играм для .NET Compact Framework (рис. 11.10). haetFMI 6f»hoY Ublb 3.» 3.38 George ' Marr-afeAe до eputt ejonit». гмм* месп «жк e. ин StaOr Be* fai it* rocfcet PC лмчгмее Рис. 11.10. Сайт CodeProject, посвященный играм для .NET Compact Framework
Глава 12 Связь Инфракрасное соединение Несмотря на растущую популярность Wi-Fi, Bluetooth и других беспроводных технологий, по-прежнему не сдает своих позиций и передача данных через инфракрасный порт. Например, все мы каждый день применяем инфракрасный порт при использовании дистанционного пульта телевизора! Вы можете использовать этот способ работы в своих приложениях для передачи разных типов данных. Так как в этой технологии для передачи данных используется свет, то необходимо прямое соединение устройств, чтобы между ними не было препятствий. Несмотря на подобное ограничение, соеди- нение через инфракрасный порт по-прежнему широко использу- ется в цифровых камерах, КПК и ноутбуках. В этой главе будет показано, как использовать инфракрасный порт при помощи клас- са IrDACl lent, входящего в библиотеку классов .NET Compact Frame- work. История и теория Основанная в 1993 году как некоммерческая организация, Ассо- циация инфракрасной передачи данных (Infrared Data Association, или сокращенно IrDA) является международной ассоциацией (www.irda.org), создающей и продвигающей стандарты инфракрас- ной связи, позволяющие пользователям соединять устройства для передачи данных. Стандарты Infrared Data Association поддержи- вают огромное число устройств. На данный момент существует несколько версий технологии IrDA, которые различаются скоро- стью передачи данных. Протокол IrDA позволяет соединяться с другим устройством без проводов при помощи ИК-излучения. Порт IrDA позволяет уста- навливать связь на расстоянии до 1-2 метров. Интерфейс IrDA предполагает малую мощность потребления, что позволяет созда- вать недорогую продукцию.
262 Глава 12. Связь Класс IrDAClient Практически все устройства под управлением Windows Mobile име- ют встроенные инфракрасные порты. Библиотека .NET Compact Framework имеет в своем составе классы, позволяющие работать с инфракрасной связью. Инфракрасная связь осуществляется между двумя устройствами по принципу «сервер-клиент». Устройство, работающее как сервер, предлагает другому компьютеру установить связь для передачи дан- ных через инфракрасный порт. Для осуществления передачи необ- ходимо передать идентификатор устройства и имя устройства. Кли- ент ждет вызова необходимой службы и откликается на ее запрос. В результате между двумя компьютерами устанавливается связь За инфракрасное соединение отвечает специальный класс I гDAC1 i ent, который может выступать и в роли сервера, и в роли клиента. Дан- ный класс входит в библиотеку System.Net.IrDA.dll Таким образом, при использовании класса IrDAClient необходимо добавить в проект ссылку на указанную библиотеку. Для чтения и передачи данных используется метод GetStream, рабо- тающий с основным потоком данных. Компьютер-клиент должен знать имя устройства, с которым нужно установить связь. Програм- ма может поочередно опросить все доступные устройства и выбрать нужное устройство для связи. Алгоритм подключения устройства к инфракрасному порту другого устройства приведен далее. 1. Создать новый экземпляр класса I rDACl 1 ent. 2. Получить список доступных устройств с помощью метода IrDAClient.DiscoverDevices. Можно ограничить количество оп- рашиваемых устройств при помощи параметра maxDevices. Ме- тод DiscoverDevices возвращает массив объектов IrDADevicelnfo. 3. Нужно исследовать каждый объект IrDADevicelnfo из получен- ного массива, чтобы найти необходимое устройство для связи. 4. Если подобное устройство найдено, то при помощи метода IrDAClient .Connect производится соединение. При этом необхо- димо указать имя службы Создание программы для работы с ИК-связью В этом разделе будет создано приложение, которое будет соединять- ся с другим устройством и пересылать ему текстовый файл. Прежде
Создание программы для работы с ИК-связью 263 всего нужно создать новый проект IrDA CS. На форме надо размес- тить три кнопки, список и строку состояния. Кнопка butFindDevs предназначена для поиска устройств, кнопка butSend — для отправки текстового сообщения, а кнопка butRecei ve служит для приема сообщения. В списке 11 stBoxl будет отображать- ся информация об обнаруженных устройствах, а в строке состояния будут отображаться сообщения о производимых операциях. Для пе- редачи данных и работы с файлами нам необходимо импортировать несколько пространств имен, как это показано в листинге 12.1. Листинг 12.1 Imports System.Net Imports System.10 Imports System.Net.Sockets Для работы с инфракрасной связью необходимо подключить к про- екту класс IrDACllent. Для этого выполним команду меню Project ► Add Reference и в диалоговом окне выберем пункт System.Net.IrDa. Теперь нужно объявить переменные на уровне класса, как показа- но в листинге 12.2 Листинг 12.2 private IrDAListener irListen; private IrDAClient irClient; private IrDAEndPoint irEndP; private IrDADeviceInfo[] irDevices: string fileSend; string fi1 eReceive; string irServiceName; int buffers!ze; В конструкторе формы надо создать экземпляр класса IrDACllent, задать имена файлов для приема и отправки сообщения, указать имя службы, установить размер буфера для передаваемого файла и так- же временно сделать недоступными кнопки для отправки и посылки сообщения. Соответствующий код приведен в листинге 12.3. Листинг 12.3 public FormlO { Initial!zeComponent(); продолжение &
264 Глава 12. Связь Листинг 12.3 (продолжение) irClient = new IrDAClientO; // Файлы, предназначенные для отправки и приема fileSend = ”.\\Му DocumentsWsend.txt"; fileReceive = ".\\Му DocumentsWreceive.txt"; // Задаем имя для службы IrDA // Это может быть любое слово // Другие устройства для примера должны использовать это же // слово irServiceName = "IrDAFtp"; // Устанавливаем максимальный размер буфера для передаваемого // файла buffersize = 256: // Делаем недоступными кнопки отправки и посылки сообщений // до тех пор. пока не будут обнаружены устройства butSend.Enabled « false: butReceive.Enabled = false: } Обнаружение устройств Теперь надо написать код для кнопки butFindDevs, предназначен- ной для обнаружения устройств. При тестировании примера не- обходимо направить инфракрасные порты устройств друг на дру- га. Код, ответственный за выполнение этой задачи, приведен в листинге 12.4. Листинг 12.4 private void butFindDevs_Click(object sender, EventArgs e) { // Ищем доступные устройства с инфракрасной связью // и помещаем их в список // Поиск не более трех доступных устройств irDevices = irClient.DiscoverDevices(2); // Если устройства не найдены, то выводим сообщение if (irDevices.Length = 0) { MessageBox.Show("Устройства с ИК-портами не обнаружены!"); return;
Создание программы для работы с ИК-связью 265 } // Перечисляем массив IrDADeviceInfo // и выводим информацию о каждом устройстве е список string device; int ID; listBoxl.Iterns.Clear(); foreach (IrDADevicelnfo irDevice in irDevices) { ID = BitConverter.ToInt32(irDevice.DevicelD. 0); device = ID.ToStringO + " ” + irDevice.DeviceName + " " + irDevice.CharacterSet + ” " + irDevice.Hints; 1i stBoxl.Iterns.Add(device); } listBoxl.Selectedlndex = 0; if (irDevices.Length > 0) statusBarl.Text = irDevices.Length.ToStringО + ” устройств(а)"; 11 Делаем доступными кнопки для отправки и посылки сообщения butSend.Enabled = true; butReceive.Enabled = true; } Передача данных Код для отправки и посылки файлов приведен в листинге 12.5. Листинг 12.5 private void butSend_Click(object sender, EventArgs e) { 11 Открываем файл для отправки и получаем его поток Stream fileStream; try { fileStream = new FileStream(fileSend, FileMode.Open); } catch (Exception exFile) { MessageBox.ShowCHe могу открыть " + exFile.ToString()); return; } // Создаем IrDA-клиент с установленным именем службы, продолжение &
266 Глава 12. Связь Листинг 12.5 (продолжение) И которое должно совпадать с именем службы на другом И IrDA-клиенте try { irClient = new IrDAClient(irServiceName); } catch (SocketException exS) { MessageBox.Show("Ошибка сокета: ” + exS.Message + - Вы щелкнули на кнопке Получить на другом устройстве?”); return; } // Получим поток Stream baseStream = irClient.GetStreamO; // Получим размер отправляемого файла // и запишем это значение в поток byte[] length = Bi tConverter.GetBytes((i nt)fi1eStream.Length); baseStream.Write(length, 0, length.Length); // Создаем буфер для чтения файла byte[] buffer = new byte[buffersize]; // Показываем число отправленных байт int fileLength = (int)fileStream.Length: statusBarl.Text = "Отправлено ” + fileLength + " байт"; // Читаем файловый поток в базовом потоке while (fileLength > 0) { int numRead = fileStream.Read(buffer, 0, buffer.Length); baseStream.Write(buffer, 0, numRead); fileLength -= numRead; } filestream.CloseO; baseStream.CloseO; irClient. CloseO; statusBarl.Text = "Файл отправлен";
Создание программы для работы с ИК-связью 267 } private void butReceive_Click(object sender, EventArgs e) { // Создаем поток для записи файла Stream writeStream; try { writeStream = new FileStream(fileReceive, FileMode. OpenOrCreate): catch (Exception) { MessageBox.ShowC’He йогу открыть "+ fileReceive + " для записи"); return: } // Создаем соединение с помощью класса IrDAEndPoint // для выбранного устройства из списка // Начинаем прослушку входящих сообщении // из устройства с объектом IrDAListener try { int i = 1istBoxl.SelectedIndex: irEndP « new IrDAEndPoint(irDevices[i].DeviceID, irServiceName); irListen = new IrDAListener(irEndP): irListen.StartO: } catch (SocketException exSoc) { MessageBox.ShowC’He могу прослушивать на службе "+irServiceName + ": " + exSoc.ErrorCode): } // Показываем прослушивание выбранного устройства statusBarl.Text = "Прослушка " + 1i stBoxl.Seiectedltem.ToStri ng(): // Создаем соединение // для службы, обнаруженной прослушкой продолжение &
268 Глава 12. Связь Листинг 12.5 (продолжение) IrDACllent irClient; try { irClient = irListen. Accept IrDACl lento; } catch (SocketException exp) { MessageBox.Show!"He могу принять сокет "+ exp.ErrorCode); return; } // Показываем, идет ли передача файла if (irListen.Pending!) = true) statusBarl.Text = "Передача из " + i rCli ent.RemoteMachi neName; else statusBarl.Text = "Нет передачи из " + i rCli ent.RemoteMachi neName; 11 Получим поток из клиента Stream baseStream = irClient.GetStreamO; int numToRead; // Создаем буфер для чтения файла byte[] buffer = new byte[buffersize]; // Читаем поток данных, который содержит // данные из передающего устройства numToRead = 4; while (numToRead > 0) { int numRead = baseStream.Read(buffer, 0, numToRead); numToRead -= numRead; } // Получим размер буфера для показа И числа байт для записи файл numToRead = BitConverter.Tolnt32(buffer, 0); statusBarl.Text = "Записываем "+ numToRead + " байт"; // Записываем поток в файл до тех пор, И пока не будут прочитаны все байты while (numToRead > 0) { int numRead = baseStream.Read(buffer, 0, buffer.Length); numToRead -= numRead;
Технология Bluetooth 269 writeStream.Write(buffer, 0, numRead); } // Сообщаем, что файл получен statusBarl.Text = "Файл получен"; baseStream.Close(); wri teStream.Close(); irListen.StopO; irClient.CloseO; } Итак, можно запустить приложение на двух устройствах и попро- бовать отправить и принять файл. Перед тестированием программы нужно создать текстовый документ send.txt с любым содержанием. Затем нужно повернуть друг к другу инфракрасные датчики двух уст- ройств и на первом устройстве нажать кнопку Искать. Если поиск за- вершился успешно, то в списке отобразится имя второго устройства. Затем на втором устройстве надо нажать кнопку Принять, а на пер- вом устройстве нажать кнопку Отправить. В результате ваших дей- ствий текст сообщения из файла send.txt должен быть передан на другое устройство и сохранен в файле receive.txt. К сожалению, данный пример нельзя тестировать на эмуляторе. Для проведения эксперимента вам необходимо иметь два настоящих ус- тройства. Так как у меня нет второго КПК, я решил воспользоваться в качестве второго устройства своим смартфоном под управлением Windows Mobile 2005. Поскольку графический интерфейс программ для смартфонов не поддерживает кнопки, мне пришлось добавить в решение новый проект IrDA_Smartphone_CS и частично переписать код программы. Вместо кнопок использовалось меню, а вместо элемента управле- ния ListBox — элемент ComboBox. Но можно было обойтись и без со- здания текстовых файлов, а просто считывать данные из потока. В этом случае наша программа приобрела бы черты чата. Также можно написать какую-нибудь игру, в которой участвуют два игро- ка. С помощью инфракрасной связи вы можете передавать инфор- мацию, например, о сделанном ходе в шахматах. Технология Bluetooth Несмотря на свою дешевизну и простоту, инфракрасное соедине- ние имеет несколько существенных недостатков. К ним относятся
270 Глава 12. Связь маленький радиус действия и возможность связи в пределах пря- мой видимости. Этих недостатков лишено Bluetooth-соединение. Но и тут не обошлось без ложки дегтя в бочке меда. Во-первых, су- ществует два различных подхода к реализации Bluetooth-соедине- ний, которые не совместимы друг с другом. Во-вторых, пока не су- ществует поддержки этой технологии в управляемом коде .NET Compact Framework. Примеры с Bluetooth-связью мы будем приво- дить для устройств под управлением Windows Mobile 5.0, так как они гарантированно используют одну и ту же реализацию Bluetooth- технологии. Так как библиотека .NET Compact Framework не имеет в своем составе классов, работающих с Bluetooth, то придется вос- пользоваться вызовами функций Windows API, как показано в лис- тинге 12.6. Листинг 12.6 public enum RadioMode { Off = 0. Connectable = 1, Discoverable = 2 } /// <summary> /// Получает текущий статус bluetooth /// </summary> /// <param name="dwMode">0narM</param> /// <returns></returns> [DI1Import("BthUti1.dl1")] public static extern int BthGetMode(out RadioMode dwMode); /// <summary> /// Устанавливает новый режим bluetooth /// </summary> /// <param name="dwMode">флаги для установки режииа</рагат> /// <returns></returns> [DI 1 Import ("Bthllti 1 .dll “)] public static extern int BthSetMode(RadioMode dwMode); private void mnuOn_Click(object sender. EventArgs e) { BthSetModeCRadi oMode.Connectable); 1 bl Status. Text - Radi oMode. Connectabl e. ToStringO; }
Несколько слов о связи 271 private void Forml_Load(object sender, EventArgs e) { RadioMode mode; int ret = BthGetMode (out mode); IblStatus.Text = mode.ToStringO; } private void mnuOff_Click(object sender, EventArgs e) { BthSetMode(RadioMode.Off); IblStatus.Text = RadioMode. Off. ToStringO; } В этом примере после запуска приложения текущий режим Bluetooth определяется при помощи функции BthGetMode, а с помощью команд меню пользователь может включать или выключать Bluetooth-соеди- нение, используя функцию BthSetMode. Несколько слов о связи Несомненно, маленькие мобильные устройства, будь то смартфон или КПК, идеально подходят на роль коммуникационных уст- ройств. В этой главе были приведены только самые простые при- меры использования связи между устройствами. В последнее вре- мя набирают обороты такие виды связи, как Wi-Fi, GPS и GPRS. Кроме того, мобильные устройства имеют в своем составе браузе- ры для путешествия по Всемирной паутине. Таким образом, серь- езному разработчику необходимо освоить весь спектр технологий, связанных с обменом данными между устройствами.
Глава 13 Использование неуправляемого кода Несмотря на то что библиотека .NET Compact Framework имеет множество классов для выполнения самых разных задач, во мно- гих случаях приходится прибегать к вызовам функций Windows API. А в некоторых случаях использование функций Windows API даже предпочтительнее, чем использование аналогичных методов управляемого кода, так как они позволяют оптимизировать и по- высить производительность приложения. Тема применения функций Windows API в .NET Compact Framework практически неисчерпаема. В некоторых случаях ис- пользование этих функций оправданно, так как других вариантов для выполнения тех или иных задач просто не существует. В то же время библиотека .NET Compact Framework постоянно развивает- ся, и часть задач с успехом решается с помощью встроенных клас- сов, добавляемых в каждой новой версии .NET Compact Framework Поэтому разработчику придется постоянно проводить ревизию сво- их программ, заменяя в случае необходимости трудный код с ис- пользованием Windows API на код с использованием безопасного управляемого кода .NET Compact Framework. Вызов функций Windows API Для вызовов функций Windows API используется механизм P/Invoke. Большинство часто вызываемых функций находится в библиотеке coredlLdLL Разработчики, которые пользовались функциями API в настоль- ной версии Windows, наверняка обратят внимание на то, что эта библиотека coredll.dll содержит множество знакомых функций из библиотек kernel32.dLL, gdi32.dll и user32.dll Поэтому во многих случаях довольно легко будет перенести свои наработки из про- грамм для настольного компьютера в приложения для мобиль- ных устройств.
Определение платформы 273 Определение платформы Если нужно определить, на какой платформе запущено ваше при- ложение, то здесь вам не обойтись без вызова функции Windows API SystemParametersInfo. Для начала нужно создать новый класс PlatformDetector, в кото- ром следует объявить функцию SystemParametersInfo и методы оп- ределения платформы. А в обработчике события Load основной формы надо вызвать метод GetPlatform, чтобы узнать платформу сразу же после загрузки приложения, как это показано в листин- ге 13.1. Листинг 13.1 using System; using System.Col1ecti ons.Generi c; using System.Text; using System.Runtime.InteropServices; namespace Pl atformDetectorCS { class PlatformDetector { [DI1 Import("coredl1. dl 1") ] private static extern bool SystemParametersInfo! int uiAction, int uiParam, StringBuilder pvParam, int fWinlni); private static int SPIGETPLATFORMTYPE = 257; public static Platform GetPlatform!) { Platform plat = Platform.Unknown; swi tch !System.Envi ronment.OSVersion.Platform) { case PlatformID.Win32NT: plat - Platform.Win32NT; break; case PlatformID.WinCE: plat - CheckWinCEPlatform!); break; продолжение &
274 Глава 13. Использование неуправляемого кода Листинг 13.1 (продолжение) } return plat; static Platform CheckWinCEPlatformO { Platform plat = Pl atform.WindowsCE; StringBuilder strbuild = new StringBuilder(200); SystemParametersInfo(SPI GETPLATFORMTYPE, 200, strbuild, 0); string str = strbuild.ToStringO; switch (str) case "PocketPC": plat = Platform.PocketPC; break; case "SmartPhone": // Note that the strbuild parameter from the // PInvoke returns "SmartPhone" with an 11 upper case P. The correct casing is // "Smartphone" with a lower case p. plat = Platform.Smartphone; break; } return plat; public enum Platform { PocketPC, WindowsCE, Smartphone, Win32NT, Unknown } } using System: usi ng System.Col1ections.Generi c; using System.ComponentModel; using System.Data; using System.Drawing; using System.Text; using System.Windows.Forms; namespace Pl atformDetector CS
Пароли 275 { public partial class Forml : Form { public Forml() { Initial!zeComponent(); } private void Forml_Load(object sender, EventArgs e) { try { MessageBox.Show("Платформа: " + PlatformDetector.GetPlatform()); } catch (Exception ex) { MessageBox.Show(ex.Message.ToStri ng()); } } } } Особое внимание следует обратить на комментарий. Параметр strbuild после вызова функции возвращает значение SmartPhone с большой буквой «Р», хотя более правильным вариантом считает- ся слово с маленькой буквой «р». Пароли Как вы, вероятно, знаете, пользователь может установить пароль на свой карманный компьютер. Для этого ему нужно зайти в раз- дел Password при помощи последовательности команд Start ► Settings ► Password и указать четырехсимвольный пароль. С помо- щью четырех функций API можно получить сведения о пароле и даже попытаться угадать его! Для тестирования этой возможности на форме надо разместить че- тыре кнопки и текстовое поле. Соответствующий код приведен в листинге 13.2.
276 Глава 13. Использование неуправляемого кода Листинг 13.2 // Функция для установления нового системного пароля [DllImport("coredll.dll")] private static extern bool SetPassword(string IpszOldpassword, stri ng 1spzNewPassword); 11 Функция для активации или блокировки текущего пароля [Dl1 Import("coredl1.dl1")] private static extern bool SetPasswordActive(bool bActive, string IpszPassword); // Функция для определения текущего состояния пароля [Dll Import("coredll.dll")] private static extern bool GetPasswordActiveO; // Функция для проверки пароля [Dillmport("coredl1.dll")] private static extern bool CheckPassword(string IpszPassword); private void butCheckPass_Click(object sender, EventArgs e) { txtinfo.Text ="Активность пароля: " + GetPasswordActiveO .ToStringO; } private void butNewPass_Click(object sender. EventArgs e) { MessageBox.Show("Установка нового пароля " + SetPassword("Активность пароля: False", txtinfo.Text) .ToStringO); } private void butSetStateClick (object sender, EventArgs e) { MessageBox.Show("Отключение пароля: " + SetPasswordActi ve(false, txtlnfo.Text) .ToStringO); } private void butFindPass_Click(object sender, EventArgs e) { MessageBox.Show("Угадали пароль? " + CheckPassword(txtlnfo.Text) .ToStringO); }
Перезагрузка КПК 277 ВНИМАНИЕ-------------------------------------------- Будьте осторожны с данными функциями на реальном устрой- стве. Если вы случайно установите новый пароль, не запомнив его, то вам придется применить жесткую перезагрузку с потерей всех данных! Перезагрузка КПК Для карманных компьютеров может применяться как жесткая, так и мягкая перезагрузка. Жесткая перезагрузка возвращает устрой- ство в первоначальное состояние, удаляя все установленные про- граммы. Делать жесткую перезагрузку без особой необходимости не следует. Мягкая перезагрузка является более безопасной опера- цией, которую часто выполняют при появлении различных сбоев в работе программ. Если разработчику необходимо программно перезагрузить устрой- ство, то необходимо воспользоваться функцией Kernel loControl. В листинге 13.3 приведен небольшой пример, демонстрирующий мягкую перезагрузку. Листинг 13.3 public const uint FILEDEVICE HAL = 0x00000101: public const uint METHODBUFFERED = 0: public const uint FILE_ANY_ACCESS = 0; public uint CTL_CODE(uint DeviceType, uint Function, uint Method, uint Access) { return ((DeviceType « 16) | (Access « 14) | (Function « 2) | Method): } [Dlllmport("Coredl1.dll")] public extern static uint KernelloControl ( uint dwIoControlCode, IntPtr IpInBuf, uint nlnBufSize, IntPtr IpOutBuf, uint nOutBufSize, продолжение &
21% Глава 13. Использование неуправляемого кода Листинг 13.3 (продолжение) ref uint IpBytesReturned ); private void butReset_Click(object sender. EventArgs e) { uint bytesReturned = 0; uint IOCTL_HAL_REBOOT = CTL_CODE(FILE_DEVICE_HAL, 15. METHOD-BUFFERED. FILE ANY ACCESS); Kernel loControlUOCTLHALREBOOT, IntPtr.Zero. 0. IntPtr.Zero, 0, ref bytesReturned); } Еще раз о перезагрузке Для устройств, работающих под управлением Windows Mobile 5.0, существует более удобный способ перезагрузки. Он очень похож на код перезагрузки настольных компьютеров с использованием функции ExitWindowsEx. При этом надо обратить внимание на раз- личия карманных компьютеров и смартфонов. Если КПК можно только перезагрузить, то смартфон можно и перезагрузить, и вы- ключить. Соответствующий код приведен в листинге 13.4. Листинг 13.4 [DI1Import!"aygshel1.dl1")] public static extern System.Boolean ExitWindowsEx (int uFlags, int dwReserved); const int EWX REBOOT = 2; // перезагрузка private void butReboot_Click(object sender, EventArgs e) { ExitWindowsEx(EWX_REBOOT, 0); } Поворот экрана Начиная с версии операционной системы PocketPC 2003 Second Edition, карманные компьютеры научились изменять ориента- цию экрана на системном уровне. Эту возможность часто исполь- зуют при создании игр, просмотре видеоматериалов или отобра- жении текстов. Если вы планируете писать программу с учетом
Поворот экрана 279 поворота экрана, то будет нужно проверить, поддерживает ли це- левое устройство данную функциональность. Ведь многие пользо- ватели еще владеют КПК на базе PocketPC 2000, PocketPC 2002 и PocketPC 2003. Для поворота экрана, а также для проверки возможности такого по- ворота используется функция API ChangeDisplaySettingsEx. Данная функция использует структуру DEVMODE. В первую очередь, в этой структуре нас интересует поле Fi el ds, в котором хранится значение Di spl ayQueryOri entati on. Этот флаг отвечает за поддержку смены ориентации экрана и передает значение в поле IpDevMode.dmDIs- pl ayOri entati on. Например, значение DMO_0 говорит о том, что пово- рот экрана не поддерживается. В листинге 13.5 приведен код, который проверяет, поддерживается ли системой изменение ориентации, и в случае положительного ответа поворачивает экран на 90°. Листинг 13.5 // Флаг, определяющий поддержку поворота экрана private static Int32 DisplayQueryOrientation = 0x01000000: private static Int32 CDS TEST = 2; // запоминаем настройки экрана ScreenOrientation initialOrientation = SystemSetti ngs.ScreenOri entati on: [Dl1 Import!"coredl1.dll", SetLastError = true)] private extern static Int32 ChangeDisplaySettingsEx! String deviceName, ref DeviceMode deviceMode, IntPtr hwnd, Int32 flags. IntPtr param): struct DeviceMode { [MarshalAsIUnmanagedType.ByValTStr. SizeConst = 32)] public String DeviceName; public I nt 16 SpecVersion; public Intl6 Driverversion: public I nt 16 Size: public I nt 16 DriverExtra; продолжение &
280 Глава 13. Использование неуправляемого кода Листинг 13.5 (продолжение) public Int32 Fields: public Intl6 Orientation: public Int16 PaperSize: public Intl6 PaperLength: public Int16 PaperWidth; public Int16 Scale: public Int16 Copies; public Intl6 Defaultsource: public Intl6 PrintQuality: public Int16 Color; public Int16 Duplex: public Int16 YResolution: public Int16 TTOption; public Intl6 Collate; [MarshalAsCLInmanagedType.ByValTStr, SizeConst = 32)] public String FormName; public Intl6 LogPixels: public Int32 BitsPerPel; public Int32 PelsWidth; public Int32 PelsHeight; public Int32 DisplayFlags; public Int32 DisplayFrequency; public Int32 DisplayOrientation; } private void butCheckRotate_Click(object sender. EventArgs e) { // подготавливаем структуру DeviceMode DeviceMode devMode = new DeviceModeO; devMode.Size = (Intl6)Marshal.Size0f(devMode); devMode.Fields = DisplayQueryOrientation; // Проверяем, поддерживает ли система поворот экрана Int32 result = ChangeDisplaySettingsEx( null, ref devMode, IntPtr.Zero. CDSTEST. IntPtr.Zero); if (result — 0) { // Если вызов функции прошел успешно.
Прячем кнопку Start 281 11 то проверяем поддержку поворота экрана // Если параметр DisplayOrientation имеет ненулевое // значение то поворот экрана возможен if (devMode.DisplayOrientation != 0) { MessageBox.Show("Поворот экрана поддерживается"): } } else { MessageBox.Show("Поворот экрана не поддерживается”); } } private void butRot90_Click(object sender. EventArgs e) { SystemSetti ngs.ScreenOri entati on = ScreenOri entati on.Angle90: } private void butRestore_Click(object sender, EventArgs e) { if (SystemSettings.ScreenOrientation != initialOrientation) { try { SystemSettings.ScreenOrientation = initialOrientation; } catch (Exception) { // Unable to change the orientation back // to the original configuration. MessageBox.Show("This sample was unable to set the " + "orientation back to the original state."); } } } Прячем кнопку Start Функция SHFul 1 Screen позволяет прятать и показывать кнопку Start и пиктограмму виртуальной клавиатуры SIP. Соответствующий код приведен в листинге 13.6.
282 Глава 13. Использование неуправляемого кода Листинг 13.6 /// <summary> /// Функция используется для изиенения вида экрана. /// Вы можете модифицировать панель задач, панель ввода, значок /// Пуск /// </summary> /// <param name="hwndRequester">flecKpnnTop окна</рагаго> /// <param name="dwState">OnpeflenfleT состояние окна</рагаш> /// <returns>B успешном случае возвращается True, иначе - /// False</returns» [Dll Import!"aygshell.dll")] static extern uint SHFullScreen!IntPtr hwndRequester, uint dwState); const uint SHFS SHOWTASKBAR = 0x0001; const uint SHFSHIDETASKBAR - 0x0002; const uint SHFSSHOWSIPBUTTON = 0x0004; const uint SHFSHIDESIPBUTTON = 0x0008; const uint SHFSSHOWSTARTICON = 0x0010; const uint SHFS HIDESTARTICON = 0x0020; private void butHideStart_Click(object sender, EventArgs e) { IntPtr hwnd = this.Handle; //прячем кнопку Start SHFul1Screen!hwnd, SHFS_HIDESTARTICON); //прячем SIP //SHFul1Screen!hwnd, SHFS_HIDESIPBUTTON); } private void butShowStart_Click(object sender. EventArgs e) { //показываем кнопку Start IntPtr hwnd = this.Handle; SHFul1Screen(hwnd, SHFS_SHOWSTARTICON); //показываем SIP //SHFul1Screen!hwnd, SHFS_SHOWSIPBUTTON); } В примере показано, как прятать кнопку Start. Если нужно спря- тать пиктограмму SIP, то надо убрать комментарии при втором вызове функции. На рис. 13.1 показан внешний вид экрана со спря- танной кнопкой Start.
Панель задач 283 Рис. 13.1. Скрытие кнопки Start Панель задач Очень часто программисты в качестве шутки создают программы, которые прячут привычные для пользователя элементы интерфей- са. В предыдущем примере было показано, как можно скрыть кноп- ку Start. Теперь нужно рассмотреть пример работы с панелью задач. Для создания тестового приложения на форме надо разместить две кнопки. Одна из них будет скрывать панель задач, а вторая — пока- зывать ее. Соответствующий код приведен в листинге 13.7. Листинг 13.7 /// <summary> /// Скрывает одно окно и активирует другое /// </summary* private const int SW_HIDE = 0; /// <summary> /// Активирует окно /// </summary> private const int SW_SHOW = 5; [Dll Import("coredl1.dll")] private static extern IntPtr FindWindow(string ClassName. string WindowName); [Dl1 Import("coredl1.dll")] private static extern bool ShowWindow(IntPtr hwnd. int nCmdShow); /// <summary> продолжение &
284 Глава 13. Использование неуправляемого кода Листинг 13.7 (продолжение) III Прячем панель задач, чтобы пользователь не мог /// нажать кнопку Start /// </summary> public static void HideTaskbar() { IntPtr h = FindWindowCHHTaskBar", ShowWindow(h, SWHIDE); } /// <summary> /// Показывает панель задач /// </summary> public static void ShowTaskBarO { IntPtr h = FindWindowCHHTaskBar", ShowWindow(h, SW_SHOW); } private void butHideTaskbar_Click(object sender, EventArgs e) { HideTaskbarO; } private void butShowTaskbar_Click(object sender, EventArgs e) { ShowTaskBarO; } На самом деле с помощью функций FindWindow и ShowWindow можно показывать и скрывать не только панель задач, но и окна других приложений. Запуск других приложений Иногда требуется запустить из своей программы другое прило- жение. В этом случае можно призвать на помощь функцию API CreateProcess. В листинге 13.8 приведен код примера, который может запустить калькулятор, календарь и даже послать файл через инфракрасное соединение мобильному телефону. Для за- пуска всех этих функций на форме надо разместить всего три кнопки.
Запуск других приложений 285 Листинг 13.8 public class Processinfo { public IntPtr hProcess; public IntPtr hThread: public Int32 Processld; public Int32 Threadld; } [Dll Import("CoreDll.DLL", SetLastError = true)] private extern static int CreateProcess(String imageName, String cmdLine, IntPtr 1pProcessAttri butes. IntPtr 1pThreadAttributes. Int32 boolInheritHandles, Int32 dwCreationFlags, IntPtr IpEnvironment, IntPtr 1pszCurrentDir, byte[] si. Processinfo pi): private void butCalc_Click(object sender, EventArgs e) { //Запускаем калькулятор Processinfo pi = new ProcessInfoO; CreateProcessCcalc.exe", IntPtr.Zero, IntPtr.Zero. 0, 0. IntPtr.Zero. IntPtr.Zero, new Byte[128], pi); } private void butCalendar_Click(object sender, EventArgs e) { //Запускаем календарь Processinfo pi = new ProcessInfoO; CreateProcessC'calendar.exe", "", IntPtr.Zero, IntPtr.Zero, 0. 0, IntPtr.Zero, IntPtr.Zero, new Byte[128], pi); } private void butInfra_Click(object sender. EventArgs e) { //Посылаем файл через инфракрасное соединение _ * про должение &
ъ-» 286 Глава 13. Использование неуправляемого кода Листинг 13.8 {продолжение) Processinfo pi = new ProcessInfoO: CreateProcess("Beam.exe". "\\windowsWAlarml.wav". IntPtr.Zero, IntPtr.Zero, 0, 0. IntPtr.Zero, IntPtr.Zero, new Byte[128], pi); } Приведенный код достаточно прост. Нужно вызвать функцию CreateProcess с именем исполняемого файла в первом параметре. В методе для отправки файла также используется второй параметр, в котором указываем имя отсылаемого файла. Названия специальных файлов В Windows существует ряд специальных папок, в которых содер- жатся файлы определенной категории. Например, в папке Избран- ное содержатся ссылки на любимые сайты пользователя. Проблема заключается в том, что в локализованных версиях Windows эти папки зачастую имеют разные названия. Так, в аме- риканской версии Windows упомянутая папка имеет название Favorites. И если ваша программа ссылается на файл, находящий- ся в специальной папке, то необходимо точно узнать, как назы- вается эта папка на конкретном устройстве. Код проверки при- веден в листинге 13.9. Листинг 13.9 // Константы /// <summary> /// Папка, содержащая файлы и папки, которые появляются на /// экране Сегодня /// «/summary* const int CSIDL-DESKTOPDIRECTORY » 0x0010; /// <summary> /// Папка Избранное /// «/summary* const int CSIDLJFAVORITES - 0x0006; /// «summary* /// Папка \Мои документы /// «/summary*
Названия специальных файлов 287 const int CSIDL_PERSONAL = 0x0005: /// <summary> /// Папка Программы в папке Главное меню /// (\Windows\Start MenuXPrograms) /// </summary> const int CSIDL_PROGRAMS = 0x0002; III <summary> /// Папка Recent (содержит последние из открывавшихся /// документов) /// </summary> const int CSIDL_RECENT = 0x0008: /// <summary> /// Папка Главное меню /// (\Windows\Start Menu) /// </summary> const int CSIDL STARTMENU = 0x000b; /// <summary> /// Папка Автозагрузка для программ, /// которые автоматически загружаются при запуске Windows /// \Windows\StartUp /// </summary> const int CSIDL_STARTUP = 0x0007; /// <summary> /// Папка, в которой хранятся шаблоны документов /// </summary> const int CSIDLJEMPLATES = 0x0015; /// <summary> /// Функция получения имен специальных папок /// </summary> [Dll Import("Coredll .dll")] static extern int SHGetSpecialFolderPath (IntPtr hwndOwner, StringBuilder IpszPath, int nFolder, int fCreate); const int MAX_PATH = 260; private void Forml_Load(object sender, EventArgs e) { 11 Папка Избранное StringBuilder strFavorites = new StringBuilder(MAX PATH); продолжение &
288 Глава 13. Использование неуправляемого кода Листинг 13.9 (продолжение) SHGetSpeci al FolderPath(this.Handle. strFavori tes. CSIDLFAVORITES, 0); MessageBox.Show("Избранное: “ + strFavorites.ToStringO); // Папка Программы StringBuilder strPrograms = new StringBuilder(MAXPATH); SHGetSpeci al FolderPath(this.Handle. strPrograms. CSIDL_PROGRAMS. 0); MessageBox.Show("Программы: " + strPrograms.ToStringO); // Мои документы StringBuilder strMyDocs = new StringBuilder(MAX PATH); SHGetSpeci alFolderPath(thi s.Handle. strMyDocs. CSIDL PERSONAL. 0); MessageBox.Show("Мои документы: " + strMyDocs.ToStringO): } Использование звуковых файлов Мир современных компьютеров трудно представить без мультиме- дийных возможностей; однако проигрывание звуковых файлов не поддерживалось в библиотеке .NET Framework 1.0. Подобный под- ход Microsoft удивил многих программистов. В этом случае прихо- дилось использовать неуправляемый код с вызовом функции PlaySound. С выходом .NET Framework 2.0 ситуация изменилась в лучшую сто- рону. Но легкая поддержка звуковых файлов остается прерогати- вой настольных систем. В библиотеке .NET Compact Framework по- прежнему отсутствует поддержка проигрывания звуковых файлов. А ведь для разработки игры наличие звуковых эффектов является обязательным условием, иначе игра будет просто неинтересна! Поэтому нужно устранить недоработку разработчиков из Microsoft. В новом примере будут использоваться два способа воспроизведе- ния звуков. В первом случае программа будет извлекать звуковой фрагмент из ресурсов. Во втором случае программа будет проигры- вать звук из обычного WAV-файла. Итак, нужно создать новый проект с именем PLaySound_CS. К проек- ту надо добавить новый класс с именем Sound. Объявление функ- ции PlaySound, необходимой для проигрывания звуков, нужно по- местить в класс Sound, как показано в листинге 13.10.
Использование звуковых файлов 289 Листинг 13.10 private enum Flags { SND SYNC " 0x0000. SNDASYNC = 0x0001, SNDNODEFAULT - 0x0002, SND_MEMORY = 0x0004, SNDLOOP = 0x0008, SND_NOSTOP - 0x0010, SND_NOWAIT = 0x00002000. SND_ALIAS = 0x00010000. SND ALIASJD = 0x00110000. SNDFILENAME = 0x00020000. SND_RESOURCE = 0x00040004 } [DllImport("CoreDll.DLL", EntryPoint = "PlaySound”, SetLastError = true)] private extern static int PlaySound(string szSound, IntPtr hMod, int flags); [Dlllmport("CoreDll.DLL”, EntryPoint = “PlaySound". SetLastError = true)] private extern static int PlaySoundBytes(byte[] szSound, IntPtr hMod, int flags); Данная функция использует для параметра fl ags несколько пре- допределенных констант. Более подробную информацию о назна- чении флагов этой функции можно найти в документации. После этого создаются два конструктора с разными параметрами, которые будут использоваться для разных методов воспроизведения звука, и метод Play. Теперь нужно перейти к основной форме и раз- местить на ней две кнопки. Первая кнопка, butResource, будет проиг- рывать звуковой фрагмент, который хранится в ресурсах приложе- ния. Кнопка but Fi 1 е запустит метод, который проигрывает аудиофайл. Для того чтобы пример работал, понадобятся два звуковых фай- лов. В состав Windows ХР входит несколько звуковых файлов. Для данного примера использовался файл chimes.wav. Его нужно доба- вить в проект. Чтобы включить файл chimes.wav в проект как ре- сурс, надо в свойствах файла выбрать пункт Build Action и устано- вить значение Embedded Resource. 10-2873
290 Глава 13. Использование неуправляемого кода В качестве внешнего аудиофайла будет использоваться файл aiarm3.wav, входящий в состав Windows Mobile. Этот файл находит- ся в папке Windows При желании можно использовать свой файл, но при этом надо в коде указать путь к нему. Теперь достаточно прописать код для обработки события Click созданных кнопок, как показано в листинге 13.11, — и приложение готово. Листинг 13.11 using System; usi ng System.Col1ecti ons.Generi c; using System.Text; using System.10; usi ng System.Runti me.InteropServi ces; namespace PlaySound CS { public class Sound { private byte[] m_soundBytes; private string m_fileName: private enum Flags { SNDSYNC = 0x0000, SNDASYNC = 0x0001, SNDNODEFAULT = 0x0002, SNDMEMORY = 0x0004, SND LOOP - 0x0008, SNDNOSTOP = 0x0010, SNDNOWAIT = 0x00002000, SNDALIAS = 0x00010000, SND_ALIAS_ID * 0x00110000, SNDFILENAME - 0x00020000, SNDRESOURCE = 0x00040004 } [DI1Import!"CoreDll.DLL", EntryPoint = “PlaySound", SetLastError - true)] private extern static int PlaySound(string szSound. IntPtr hMod, int flags); [DI1 Import("CoreDll.DLL", EntryPoint = "PlaySound”, SetLastError
Использование звуковых файлов 291 - true)] • private extern static int PlaySoundBytes(byte[] szSound, IntPtr hMod, int flags); /// <summary> /// Конструктор объекта Sound, который проигрывает звук из /// указанного файла /// </summary> public Sound(string fileName) { mfileName = fileName; } /// <summary> /// Конструктор объекта Sound, который проигрывает звук из /// ресурсов /// </summary> public Sound(Stream stream) { // читаем данные из потока m_soundBytes - new byte[stream.Length]; stream. Read (msoundBytes, 0, (int)stream.Length); } /// <summary> /// Воспроизводим звук /// </summary> public void PlayO { // Если из файла, то вызываем PlaySound, // если из ресурсов, то PlaySoundBytes. if (m fileName != null) PlaySound(m_fileName, IntPtr.Zero, (int)(Flags.SND_ASYNC I Flags.SND_FILENAME)); else Pl aySoundBytes(m soundBytes, IntPtr.Zero, (int)(Flags.SND_ASYNC | Flags.SND_MEMORY)); } } } io*
292 Глава 13. Использование неуправляемого кода Теперь нужно перейти к самой форме. Код для нее приведен в листин- ге 13.12. Листинг 13.12 using System: usi ng System.Col1ecti ons.Generi c; using System.ComponentModel; using System.Data: using System.Drawing; using System.Text; using System.Windows.Forms: using System.Reflection: namespace PlaySoundCS { public partial class Forml : Form { public Forml() { Initial!zeComponent(); Initial!zeComponent(); #if DEBUG MinimizeBox = false; #el se MinimizeBox - true: #endif } private void butResourceCl ick (object sender, EventArgs e) { Sound sound new Sound(Assembl у. GetExecut! ngAssembl y(). GetMani festResourceStream( " Pl aySoundCS. chi mes. wa v")); sound.Play(); } private void butFile_Click(object sender, EventArgs e) { Sound sound e new Sound("WindowsWalarm3.wav"); sound.Play(); } } }
Системное время 293 Системные звуки Также разработчик может использовать функцию MessageBeep, по- зволяющую проигрывать системные звуки. Код, использующий эту функцию, приведен в листинге 13 13. Листинг 13.13 [Dll Import!"coredll.dll")] extern static void MessageBeep(uint BeepType); private void butBeep_Click(object sender. EventArgs e) { MessageBeep(O); } Системное время Чтобы получить или установить системное время на устройстве, нужно использовать функции GetSystemTime и SetSystemTime. Следу- ет учитывать, что функция GetSystemT 1 me возвращает время по Грин- вичу, а не местное время. Код, иллюстрирующий применение этих функций, приведен в листинге 13.14. Листинг 13.14 using System.Runtime.InteropServices; [DI1 Import("coredl1.dlГ)] private extern static void GetSystemTime(ref SYSTEMTIME IpSystemTime); [Dl1Import("coredl1.dll")] private extern static uint SetSystemTime(ref SYSTEMTIME IpSystemTime); private struct SYSTEMTIME { public ushort wYear; public ushort wMonth; public ushort wDayOfWeek; public ushort wDay; public ushort wHour; public ushort wMinute; public ushort wSecond; продолжение &
294 Глава 13. Использование неуправляемого кода Листинг 13.14 (продолжение) ; v public ushort wMilliseconds; } private void GetTimeO { // Получим системное время SYSTEMTIME st = new SYSTEMTIMEO; GetSystemTimeCref st); DateTime dt « DateTime.UtcNow.ToLocalTimeO; // Выводим сообщение MessageBox.Show( "Текущее время: ” + st. wHour. ToStringO + ":" + st. wMinute. ToStringO); private void SetTimeO { 11 Сначала получим системное время SYSTEMTIME st - new SYSTEMTIMEO; GetSystemTime(ref st); // А теперь прибавим один час st.wHour = (ushort)(st.wHour + 1 % 24); . •, , SetSystemTime(ref st); MessageBox.Show("Новое время: " + st.wHour.ToStringO + ":" + st.wMinute.ToStringO); } private void butGetTime_Click(object sender, EventArgs e) { GetTimeO: } private void butSetTime_Click(object sender, EventArgs e) { SetTimeO; } Создание ярлыка В некоторых случаях программисту необходимо создать ярлык к какой-либо программе. В этом случае можно воспользоваться спе-
Количество строк в текстовом поле 295 циальной функцией SHCreateShortcut, применение которой демон- стрируется в листинге 13.15. Листинг 13.15 /// <summary> /// Функция для создания ярлыка /// </summary> /// <param name="szShortcut">CTpOKa, содержащая /// путь и имя создаваемого ярлыка. ///</рагаш> /// <param name="szTarget">CTpoKa, содержащая /// путь и аргументы для ярлыка. /// Размер строки ограничен 256 символами. /// </param> /// <returns>B успешном случае возвращается TRUE, /// в случае ошибки возвращается FALSE /// </returns> [Dl1 Import("coredl1.dll", EntryPoint - "SHCreateShortcut”)] private static extern bool SHCreateShortcut(string szShortcut. string szTarget): private void butCreateShortcutClick(object sender, EventArgs e) { // Создадим ярлык к калькулятору bool success = SHCreateShortcut CWMy DocumentsWShortcut .Ink", " WWi ndowsWcal c. exe\""): } В этом примере создается ярлык Shortcut. Ln к для стандартного каль- кулятора, чей исполняемый файл носит имя windows\calc.exe. Количество строк в текстовом поле Если у текстового поля свойство Multiline имеет значение True, то свойство Li nes возвращает массив строк в текстовом поле. Но у дан- ного свойства есть два недостатка. Во-первых, свойство Lines не поддерживается библиотекой .NET Compact Framework, а во-вто- рых, это свойство не учитывает перенос слов. Для подсчета количе- ства строк в многострочном текстовом поле можно использовать сообщение EM_GETLINECOUNT. Соответствующий код приведен в лис- тинге 13.16.
296 Глава 13. Испо 1ьзование неуправляемого кода Листинг 13.16 [Dll Import("coredll.dll")] static extern int SendMessage(IntPtr hwnd, int msg. int wParam. int IParam); const int EMGETL INECOUNT = OxOOBA; private void butGetNumber Click(object sender. EventArgs e) { // Узнаем число строк в текстовом поле int numberOfLines - SendMessage(textBoxl.Handle. EMGETLINECOUNT, 0. 0): sblnfo.Text - "Число строк: " + numberOfLines.ToStringO; } Реестр Реестр является важной частью любой операционной системы се- мейства Windows. Не является исключением и система Windows Mobile, в которой тоже имеется собственный реестр. Однако раз- работчики компании Microsoft не стали включать редактор реест- ра в состав Windows Mobile. Поэтому для доступа к разделам рее- стра приходится устанавливать программы от сторонних произво- дителей. Однако любой программист может написать свой редактор реестра, используя возможности .NET Compact Framework. При этом следу- ет учитывать, что в библиотеке .NET Compact Framework 2.0 появи- лись классы для работы с разделами реестра. Если же вы продолжаете писать программы с использованием .NET Compact Framework 1.0, то придется вызывать функции Windows API. В листинге 13.17 приведен код, который будет работать в любой версии .NET Compact Framework. Листинг 13.17 using System; usi ng System.Col1ections.Generi c; using System.Text; using System.Runtime.InteropServices; namespace Registry CS { class Registry { /// <summary>
Реестр 297 Создает ключ </summary> <param name="keyName">HMfl создаваемого ключа</рагат> <returns>B успешном случае возвращается ERROR SUCCESS</returns> /// /// /// /// Ш public static int CreateKey(UIntPtr root, string keyName) { UlntPtr hkey = UlntPtr.Zero; uint disposition = 0: try { return RegCreateKeyExCroot, keyName, 0, null, 0, KeyAccess.None, IntPtr.Zero, ref hkey, ref disposition); } finally { if (UlntPtr.Zero != hkey) { RegCloseKey(hkey); } } } /// <summary> /// Удаляет ключ /// </summary> III <param паше="кеуМате">Имя ключа</рагат> /// <returns>B успешном случае возвращается /// ERROR_SUCCESS</returns> public static int DeleteKey(UlntPtr root, string keyName) { return RegDeleteKey(root, keyName); } /// <summary> /// Создает строковой параметр в заданном ключе /// </summary> /// <param name="keyName">HMfl ключа</рагат> /// <param name="valueName">klMB параметра</рагат> /// <param name»"striпд0а1а”>Значение параметра</рагат> /// <returns>B успешном случае возвращается /// ERROR_SUCCESS</returns> public static int CreateValueString(string keyName, string valueName, продолжение
298 Глава 13. Использование неуправляемого кода - Листинг 13.17 (продолжение) string stringData) { LHntPtr hkey - UlntPtr.Zero: try { int result = RegOpenKeyEx(root, keyName, 0. KeyAccess.None. ref hkey); if (ERROR SUCCESS != result) return result; byte[] bytes = Encoding.Unicode.GetBytes(stringData): return RegSetValueEx(hkey. valueName, 0. KeyType.String, bytes, (uint)bytes.Length); } finally { if (UlntPtr.Zero != hkey) { RegCloseKey(hkey); } } } /// <summary> /// Создает параметр типа DWORD в заданном ключе /// </summary> /// <param name="keyName">Имя ключа</рагат> /// <param name="valueName">MMfl параметра</рагат> /// <param name="dwordData">3начение параметра</рагат> /// <returns>B успешном случае возвращается /// ERROR_SUCCESS</returns> public static int CreateValueDWORD(UIntPtr root, string keyName. string valueName, uint dwordData) { UlntPtr hkey = UlntPtr.Zero; try { int result = RegOpenKeyEx(root. keyName. 0, KeyAccess.None, ref hkey); if (ERROR_SUCCESS !- result) return result; byte[] bytes = BitConverter.GetBytes(dwordData);
Ре р 299 return RegSetValueEx(hkey, valueName, 0, KeyType.Dword, bytes, (uint)bytes.Length); } finally { . if (UlntPtr.Zero !» hkey) Г' RegCloseKey(hkey): /// <summary> /// Создает двоичный параметр в заданном ключе /// </summary> /// <param пате="кеуНате">Имя ключа</рагат> /// <рагат пате="уа1иеМатпе”>Имя параметра</рагат> /// <param name="dwordData">3Ha4eHMe лараметра</рагат> /// <returns>B успешном случае возвращается /// ERROR_SUCCESS</returns> public static int CreateValueBinary(UIntPtr root, string keyName, string valueName. uint binData) { UlntPtr hkey = UlntPtr.Zero; try { int result = RegOpenKeyEx(root, keyName. 0, KeyAccess.None. ref hkey): if (ERRORSUCCESS != result) return result: byte[] data = BitConverter.GetBytes(binData); return RegSetValueEx(hkey. valueName. 0. KeyType.Binary. data, (uint)data.Length): } finally { if (UlntPtr.Zero != hkey) { RegCloseKey(hkey); } } } /// <summary> продолжение
300 Глава 13. Использование неуправляемого кода Листинг 13.17 (продолжение) III Получает значение строкового параметра /// </summary> /// <param пате="кеу№те">Имя ключа</рагат> /// <рагат name="valueName">l4MM параметра</рагат> /// <рагат name="stringResult">CTpoKOBbie данные</рагат> /// <returns>B успешном случае возвращается /// ERROR_SUCCESS</returns> public static int GetStringValue(UlntPtr root, string keyName, string valueName, ref string stringResult) { UlntPtr hkey = UlntPtr.Zero; try { int result » RegOpenKeyExCroot, keyName, 0, KeyAccess.None, ref hkey); if (ERROR SUCCESS != result) return result; byte[] bytes *= null; uint length = 0; KeyType keyType » KeyType.None; result - RegQueryValueEx(hkey, valueName, IntPtr.Zero, ref keyType, null, ref length); if (ERROR_SUCCESS != result) return result; keyType = KeyType.None; bytes = new byte[length]; result = RegQueryValueEx(hkey, valueName, IntPtr.Zero, ref keyType, bytes, ref length); if (ERROR SUCCESS != result) return result; stringResult = Encoding.Unicode.GetString(bytes, 0, bytes.Length); return ERROR SUCCESS;
Реестр 301 finally { if (UlntPtr.Zero 1= hkey) { RegCloseKey(hkey); } } } /// «summary* /// Получает заданное значение типа DWORD /// «/summary» /// «param name="keyName,‘>MHs ключа«/рагат> /// «param name»"valueName">MMfl паранетра«/рагат> /// «param name-"dwordResult">3Ha4eHne паранетра«/рагат> /// <returns>B успешной случае возвращается /// ERROR_SUCCESS«/returns> public static int GetDWORDValue(UIntPtr root, string keyName. string valueName, ref uint dwordResult) { UlntPtr hkey UlntPtr.Zero; try { int result - RegOpenKeyEx(root, keyName, 0, KeyAccess.None, ref hkey); if (ERROR_SUCCESS !» result) return result; byte[] bytes = null; uint length » 0; KeyType keyType = KeyType.None: result - RegQueryValueEx(hkey, valueName, IntPtr.Zero, ref keyType, null, ref length); bytes = new byte[Marshal.SizeOf(typeof(uint))J; length = (uint)bytes.Length: keyType - KeyType.None; result - RegQueryValueEx(hkey, valueName. IntPtr.Zero, ref keyType, продолжение
302 Глава 13. Использование неуправляемого кода Листинг 13.17 {продолжение) bytes, ref length); if (ERROR SUCCESS != result) return result; dwordResult = BitConverter.ToUInt32(bytes, 0); return ERRORSUCCESS; } finally { if (UlntPtr.Zero != hkey) { RegCloseKey(hkey); /// <summary> /// Удаляет заданный параметр из раздела реестра /// </summary> /// <param пате="кеу№те">Имя ключа</рагаш> /// <param name="valueName">HMfl параметра</рагаш> /// <returns>B успешном случае возвращается /// ERROR_SUCCESS</returns> public static int DeleteValue(UIntPtr root, string keyName, string valueName) < UlntPtr hkey = UlntPtr.Zero; int result » RegOpenKeyEx(root, keyName, 0, KeyAccess.None, ref hkey): if (ERROR SUCCESS != result) return result; return RegDeleteValue(hkey, valueName); } finally if (UlntPtr.Zero !a hkey) {
Реестр 303 RegCloseKey(hkey): } } } /// <summary> /// Типы ключей /// </summary> public enum KeyType : uint { None = 0, String s 1. Binary = 3. Dword = 4. } /// <summary> /// Тип доступа /// </summary> public enum KeyAccess : uint { None = 0x0000, QueryValue = 0x0001, SetValue = 0x0002, CreateSubKey - 0x0004, EnumerateSubKeys = 0x0008, Notify = 0x0010, CreateLink - 0x0020 } /// <summary> /// HKEYCLASSESROOT /// </summary> public static UlntPtr HKCR = new UIntPtr(0x80000000): /// <summary> /// HKEY CURRENT USER /// </summary> public static UlntPtr HKCU = new UlntPtr(0x80000001); /// <summary> /// HKEY_LOCAL_MACHINE /// </summary> public static UlntPtr HKLM = new UIntPtr(0x80000002); /// <summary> продолжение &
304 Глава 13. Использование неуправляемого кода Листинг 13.17 (продолжение) /// HKEYUSERS /// </summary> public static UlntPtr HKU = new UIntPtr(0x80000003); /// <summary> /// Возвращаемое значение в случае успеха /// </summary> public const int ERROR_SUCCESS = 0; /// <summary> /// Функция для создания заданного раздела реестра. Если раздел /// уже существует, то функция открывает его. /// </summary> /// <param name="hkey">[in] Дескриптор к открываемому разделу /// или одна из ветвей реестра: /// HKCR, HKCU, HKLM.</param> /// <param name="lpSubKey">[in] Имя для нового раздела. Данный /// раздел должен быть подразделом раздела, определенного в /// параметре hKey. /// </param> /// <рагаш name="Reserved">[in] Зарезервированный параметр. /// Установлен равным 0</param> /// <param name="lpClass">[in] Имя класса или типа объекта /// Данный параметр игнорируется, если раздел уже существует ///</рагаш> /// <param name="dwOptions">[in] Игнорируется: установите /// равным 0 /// </param> /// <param name="samDesired,,>[in] Игнорируется; установите /// равным 0 /// </рагаш> /// <param name="lpSecurityAttributes">[in] Установите в NULL. /// </param> /// <param name="phkResult">[out] Переменная, получаемая от /// дескриптора нового или открытого раздела /// Если вы больше не нуждаетесь в дескрипторе, то вызовите /// функцию RegCloseKey для его закрытия. </param> /// <param name="lpdwDisposition”>[out] Переменная, которая /// получает значение 1 (REG_CREATED_NEW_KEY), /// если раздел был создан /// и значение 2 (REG OPENED EXISTING KEY), если был открыт уже ///существующий раздел
Реестр 305 /// </param> /// <returns>ERROR_SUCCESS сообщает об успешном вызове функции. ///В случае ошибки возвращается ненулевое значение /// </returns> [Dl1Import("coredl1.dll", SetLastError = true)] public static extern int RegCreateKeyEx ( UlntPtr hkey, String IpSubKey, uint Reserved. StringBuilder IpClass, uint dwOptions, KeyAccess samDesired, IntPtr IpSecurityAttributes. ref UlntPtr phkResult. ref uint IpdwDisposition ): /// <summary> /// Функция для удаления раздела реестра /// </summary> /// <param name="hkey">[in] Дескриптор к удаляемому разделу или /// одна из ветвей реестра: HKCR, HKCU, HKLM. /// </рагаш> /// <param name=,,subkeyName">[in] Имя удаляемого раздела. /// Нельзя использовать NULL /// </param> /// <returns>ERROR_SUCCESS сообщает об успешном вызове функции. /// В случае ошибки возвращается ненулевое значение /// </returns> [Dl1 Import("coredl1.dll", SetLastError - true)] public static extern int RegDeleteKey ( UlntPtr hkey, string subkeyName ); /// <summary> /// Функция для открытия заданного раздела реестра. /// </summary> /// <param name="hkey">[in] Дескриптор к открываемому разделу /// или одна из ветвей реестра HKCR, HKCU, HKLM.</param> /// <param name="IpSubKey">[in] Имя открываемого раздела продолжение &
306 Глава 13. Использование неуправляемого кода Листинг 13.17 (продолжение) III </param> /// <param name="ulOptions">[in] Зарезервированный параметр. ///Установлен равным 0</рагат> /// <param name=”samDesired">[in] Не поддерживается. Установите /// в 0.</рагат> /// <param name="phkResult">[out] Переменная, получаемая от /// дескриптора открытого раздела. Если вы больше не нуждаетесь /// в дескрипторе, то вызовите функцию RegCloseKey для его /// закрытия</рагаш> /// <returns>ERROR_SUCCESS сообщает об успешном вызове функции. /// В случае ошибки возвращается ненулевое значение /// </returns> [Dl1Import("coredl1.dll", SetLastError = true)] public static extern int RegOpenKeyEx ( UlntPtr hkey, String IpSubKey, uint ulOptions, KeyAccess samDesired, ref UlntPtr phkResult ); /// <summary> /// Функция получает тип и данные из заданного раздела реестра /// </summary> /// <param name=”hkey">[in] Дескриптор к открываемому разделу /// или одна из ветвей реестра: HKCR, HKCU, HKLM.</param> /// <param name“"lpValueName">[in] Значение параметра. /// </param> /// <param name="lpReserved">[in] Зарезервированный параметр. /// Установите в NULL.</param> /// <param name="lpType">[out] Тип данных /// </param> /// <param name="lpData">[out] Буфер, получающий данные. /// Данный параметр может быть NULL, если данные не требуются. /// </param> /// <param name="lpcbData">[in/out] Размер буфера в байтах ///</param> /// <returns>ERROR_SUCCESS сообщает об успешном вызове функции. /// В случае ошибки озвращается ненулевое значение /// </returns> [Dl1Import!"coredll.dll", SetLastError • true)]
Реестр 307 public static extern int RegQueryValueEx ( UlntPtr hkey, String IpValueName, IntPtr IpReserved, ref KeyType IpType, byte[] IpData, ref uint IpcbData ); /// <summary> /// Функция создает параметр в разделе реестра. /// </summary> [Dl1 Import!"coredl1.dll", SetLastError • true)] public static extern int RegSetValueEx ( UlntPtr hkey, String IpValueName, uint Reserved, KeyType dwType, byte[] IpData, uint cbData ): [Dl1Import!"coredl1.dl1", SetLastError = true)] public static extern int RegDeleteValue ( UlntPtr hkey, string valueName ); [DllImport("coredll.dll", SetLastError = true)] public static extern int RegCloseKey ( UlntPtr hkey ): } } Наличие внешней клавиатуры С помощью класса Registry разработчик может получать или уста- навливать значения параметров в реестре. Предположим, что нуж-
308 Глава 13. Использование неуправляемого кода но узнать, подключена ли к устройству внешняя клавиатура. За данную функцию отвечает параметр HasKeyboard в разделе HKEY_CUR- RENT_USER\Software\Microsoft\Shell. Если данный параметр имеет единичное значение, то система работает с подключенной внешней клавиатурой. Если значение равно нулю, то клавиатуры нет. В лис- тинге 13.18 приведен код, показывающий, как можно извлечь зна- чение интересующего параметра. Листинг 13.18 private void butCheckKeyboard_Click(object sender, EventArgs e) { uint check - 0; Regi stry.GetDWORDValue(Regi stry.HKCU, "SOFTWAREWMicrosoftWShell". "HasKeyboard", ref check): 1Ы Info. Text = Convert.ToBoolean(check) .ToStringO; } В этом примере используется функция-оболочка GetDWORDVal ue из класса Regi stry. Если же вы предпочитаете обходиться без функ- ций-оболочек, а обращаться напрямую к функциям API, то пример можно переписать так, как показано в листинге 13.19. Листинг 13.19 private static bool IsKeyboardO { uint dwordResult: UlntPtr hkey - UlntPtr.Zero; try { int result = Registry.RegOpenKeyEx(Registry.HKCU, "SOFTWAREWMicrosoftWShel 1", 0, Regi stry.KeyAccess.None, ref hkey); if (Registry.ERROR-SUCCESS != result) return false; byte[] bytes = null; uint length 0; Registry.KeyType keyType e Registry.KeyType.None; result - Registry.RegQueryValueEx(hkey, "HasKeyboard", IntPtr.Zero.
Реестр 309 ref keyType, null, ref length); if (Registry.ERROR-SUCCESS != result) return false; bytes = new byte[Marshal.SizeOf(typeof(uint))]; length - (uint)bytes.Length; keyType = Registry.KeyType.None: result = Registry.RegQueryValueEx(hkey, "HasKeyboarcr, IntPtr.Zero, ref keyType, bytes, ref length); 1f (Registry.ERROR-SUCCESS != result) return false; dwordResult = BitConverter.ToUInt32(bytes, 0); return (dwordResult — 1); } finally { if (UlntPtr.Zero !» hkey) { * Regi stry.RegCloseKey(hkey); } } } Теперь эту функцию можно вызвать в любом месте программы, как показано в листинге 13.20. Листинг 13.20 // Определяем наличие внешней клавиатуры 1Ы Info. Text = IsKeyboardO.ToStringO; ВНИМАНИЕ--------------------------------------------------------- Следует отметить, что при проверке примера на эмуляторе функ- ция обнаруживает присутствие клавиатуры Что, в общем то, справедливо, так как с помощью обычной клавиатуры пользователь может вводить данные в программах, запущенных на эмуляторе. Информация о пользователе Также с помощью реестра можно узнать информацию о пользова- теле устройства. За эту информацию отвечает параметр Owner в раз-
310 Глава 13. Использование неуправляемого кода деле HKEY_CURRENT_USER\Control Panel \0wner. В листинге 13.21 приве- ден код, который получает эту информацию. Листинг 13.21 private void butOwner_Click(object sender. EventArgs e) { string strOwner = Regi stry.GetStri ngValue(Regi stry.HKCU. “WControlPanelWOwner". "Owner", ref strOwner): 1Ы Info.Text = strOwner; } Наличие дополнительной клавиатуры Узнать о наличии в системе подключаемой клавиатуры можно с по- мощью функции API или просмотрев значение соответствующего ключа в реестре. Использование реестра рассматривалось несколько раньше. В листинге 13.22 приведен код, который показывает, как Кюжно узнать о присутствии подключенной клавиатуры с помощью функции API GetKeyboardStatus. Листинг 13.22 III <summary> /// Функция возвращает статус подключаемой клавиатуры и ее /// возможности. /// </summary> /// <ге^гп5>Функция возвращает битовую маску, /// показывающую присутствие клавиатуры и ее возможности /// </returns> [Dl1Import("coredl1.dl1")] public static extern uint GetKeyboardStatus!): /// <summary> » /// Показывает присутствие клавиатуры в системе /// </summary> public const uint KBDI KEYBOARD_PRESENT = 0x0001; /// <summary> /// Показывает доступность клавиатуры. »- * Г4 /// Данный бит может быть изменен функцией /// EnableHardwareKeyboard /// </summary>
Виброзвонок 311 public const uint KBDI_KEYBOARDENABLED = 0x0002; /// <summary> /// Показывает наличие на клавиатуре клавиш ENTER и ESC /// </summary> public const uint KBDIKEYBOARDENTERESC = 0x0004; /// <summary> /// Показывает наличие клавиш с буквами и цифрами /// </summary> public const uint KBDI_KEYBOARD_ALPHA_NUM = 0x0008; private void FormlJ_oad(object sender, EventArgs e) { MessageBox.Show("Наличие и доступность клавиатуры: " + I sKeyboardO. ToStringO); } private static bool IsKeyboardO { uint flags = KBDI_KEYBOARD_ENABLED | KBDI_KEYBOARD_PRESENT: return ((GetKeyboardStatusO & flags) == flags): } Виброзвонок Как правило, практически все современные модели мобильных те- лефонов и смартфонов поддерживают функцию виброзвонка. Сле- довательно, должны существовать функции для его включения и выключения. Для использования виброзвонка на смартфоне при- меняются функции Vibrate, VibrateStop и VibrateGetDeviceCaps. При помощи этих функций разработчик может использовать воз- можности виброзвонка в своих приложениях. Соответствующий код приведен в листинге 13.23. Листинг 13.23 /// <summary> /// Включает виброзвонок /// </summary> /// <returns>S_OK сообщает об успешном вызове функции. В случае /// ошибки возвращается E_FAIL ///</returns* [Dl1Import("aygshel Г’)] продолжение &
312 Глава 13. Использование неуправляемого кода Листинг 13.23 (продолжение) private static extern int Vibrate( int cvn, IntPtr rgvn, uint fRepeat, uint dwTimeout): /// <summary> /// Останавливает виброзвонок /// </summary> /// <returns>S_OK сообщает об остановке виброзвонка. В случае /// ошибки возвращается EFAIL /// </returns> [Dll Import("aygshell”)] private static extern int VibrateStopO: /// <summary> /// Получает сведения о возможности использования виброзвонка /// </summary> /// <param name="caps"Перечисление VIBRATEDEVICECAPS, /// определяющее возможности воспроизведения виброзвонка /// устройством. ///</param> [Dl1Import("aygshel1")] private static extern int VibrateGetDeviceCaps(VibrationCapabilities caps); /// <summary> /// Используется функцией VibrateGetDeviceCaps для определения /// возможности воспроизведения виброзвонка. /// </summary> public enum VibrationCapabilities : int { VDC_Amplitude, VDC_Frequency, } private void mnuStopVibrateCl ick (object sender, EventArgs e) { StopVibrateO; } private void mnuVibrate_Click(object sender, EventArgs e) {
Виброзвонок 313 StartVibrateO; } /// <summary> /// Включаен виброзвонок /// </summary> . /// <returns>B успешной случае возвращается TRUE, в случае /// ошибки - FALSE.</returns> public static bool StartVibrateO { int result - Vibrate(0, IntPtr.Zero, Oxffffffff. Oxffffffff); if (result != 0) { return false; } return true; } /// <summary> /// Останавливаем виброзвонок /// </summary> /// <returns>B успешном случае возвращается TRUE, в случае /// ошибки - FALSE.</returns> public static bool StopVibrateO { int result = VibrateStopO; if (result != 0) { return false; } return true; } ВНИМАНИЕ--------------------------------------------------------- Приведенный код будет работать только на смартфонах. На фо- румах можно найти сообщения, что на устройствах под управлени- ем PocketPC Phone Edition этот пример не работает, даже если ука- занное устройство поддерживает виброзвонок.
Глава 14 Кирпичики .NET Compact Framework Итак, изучение .NET Compact Framework подходит к концу. Мы с вами рассмотрели различные аспекты программирования для карманных компьютеров и смартфонов. Напоследок я хочу предложить вам не- сколько маленьких советов-кирпичиков, с помощью которых вы смо- жете построить свое новое приложение. Часть этих советов уже встре- чалась вам на страницах этой книги. Но, может быть, вы не обратили на них внимания или не помните, где искать нужный вам кусок кода. Поэтому я отобрал часть этих советов и поместил их в отдельную гла- ву. Эту главу можно рассматривать как справочный материал. Узнать версию .NET Compact Framework В папке Windows есть утилита CGACUTIL.EXE, которая выводит но- мер версии установленной .NET Compact Framework. Если нужно программно узнать номер версии, то следует воспользоваться ко- дом, приведенным в листинге 14.1. Листинг 14.1 // Узнаем версию установленной .NET Compact Framework txtAppDir.Text = Environment. Vers ion. ToStringO: Узнать версию операционной системы Для получения версии операционной системы нужно вызвать уже свойство OSVersion, как показано в листинге 14.2. Листинг 14.2 // Узнаем версию операционной системы txtinfo.Text Environment.OSVersion.ToStringO: Получаемые значения приведены в следующем списке. □ 3.0 — соответствует Pocket PC 2000/2002. □ 4.20 — соответствует Pocket PC 2003.
Узнать имя устройства 315 □ 4.21 — соответствует Pocket PC 2003 SE. □ 5.01 — соответствует Windows Mobile 5.0. Путь к запущенному приложению Иногда требуется узнать путь к файлу запущенного приложения. Для этого можно воспользоваться кодом из листинга 14.3. Листинг 14.3 using System.10; using System.Reflection; txtAppDir.Text = Path.GetDi rectoryName(Assemblу.GetExecuti ngAssemblу().GetModul e ОЕО]. Ful1yQual5fi edName).ToStri ng(); В этом примере после выбора соответствующего пункта в тексто- вом поле будет отображен полный путь к файлу запущенного при- ложения. Специальные папки В главе, посвященной функциям Windows API, путь к специаль- ным папкам отыскивался с помощью функции SHGetSpeci al Fol- derPath. Сторонники управляемого кода могут воспользоваться ме- тодом GetFolderPath, который появился в .NET Compact Framework 2.0. С помощью перечисления Environment. Special Folder можно по- лучить пути к некоторым специальным папкам системы. Напри- мер, чтобы получить путь к папке Start Up, можно воспользоваться кодом, приведенным в листинге 14.4. Листинг 14.4 txtinfo.Text = Envi ronment.GetFolderPath(Envi ronment.Speci alFolder.Startup) .To tringO; Узнать имя устройства Чтобы узнать имя устройства, на котором запущено приложение» достаточно вызвать метод GetHostName, как показано в листинге 14.5. .Г
316 Глава 14. Кирпичики .NET Compact Framework Листинг 14.5 txtinfo.Text = System. Net. Dns.GetHostNameO.ToStringO; Узнать ориентацию экрана Чтобы узнать, какой режим экрана установлен на данный момент, достаточно получить свойство Bounds, как показано в листинге 14.6. Листинг 14.6 txtinfo.Text = Screen.PrimaryScreen.Bounds.Width + + Screen.Pri maryScreen.Bounds.Hei ght; Зная ширину и высоту экрана, уже не составит труда понять, какой режим отображения используется в данный момент. Открытие файлов по умолчанию Стоит обратить особое внимание на класс Process. С помощью дан- ного класса очень удобно запускать любой файл, который будет открываться программой, сопоставленной с данным типом фай- ла. Предположим, что необходимо воспроизвести музыкальный файл MP3, но при этом неизвестно, какая именно программа у пользователя отвечает за воспроизведение этих музыкальных файлов. В этом случае можно просто указать имя файла, и систе- ма сама запустит соответствующую программу. Соответствующий код приведен в листинге 14.7. Листинг 14.7 System. Di agnosti cs. Process. St art (" WMy Musi cWmyf i 1e.mp3"): ВНИМАНИЕ------------------------------------------------- Класс System.Diagnostics.Process появился в .NET Compact Framework 2.0. Для версии .NET Compact Framework 1.0 нужно использовать функцию API ShellExecuteEx. Создание и отправка письма Существует очень легкий и быстрый способ создания и отправки письма с использованием технологии, применяемой на веб-страни- цах. С помощью ключевого слова mai 1 to создается заготовка пись-
Кнопки навигации 317 ма, в которой указываются автор сообщения, тема и текст письма. После этого запускается процесс, который в автоматическом режи- ме запускает нужную почтовую программу и отсылает письмо, как показано в листинге 14.8. Листинг 14.8 private void butSendMail_Click(object sender. EventArgs e) { System.Di agnosti cs.Process.Start ("mai1to:alexander.klimoff@gmai1.com?subject=About Book". null): } Кнопки навигации У карманных компьютеров есть кнопки навигации, позволяющие управлять объектами на экране. Это кнопки со стрелками и кнопка ввода. Чтобы узнать, на какую кнопку нажал пользователь, нужно пере- определить событие OnKeyDown. Для создания тестового приложения нужно разместить на форме строку состояния, в которой будет ото- бражаться название нажатой кнопки. Соответствующий код при- веден в листинге 14.9. Листинг 14.9 protected override void OnKeyDown(KeyEventArgs keyg) { switch (keyg.KeyData) { case Keys.Left: sbaKeys.Text = "Left"; break; case Keys.Right: sbaKeys.Text = "Right": break; case Keys.Down: sbaKeys.Text = "Down"; break; case Keys.Up: sbaKeys.Text = "Up"; break; case Keys.Return: sbaKeys.Text = "Return"; break; default: break; } }
Послесловие Что дальше? Вот и подошла к концу книга о программировании для мобиль- ных устройств с помощью .NET Compact Framework. Надеюсь, я смог рассказать об основных особенностях программирования в этой среде, и вам будет легко продолжить изучение этой техно- логии. Жизнь не стоит на месте, и постоянно выпускаются новые релизы эмуляторов, обновлений SDK и новых утилит. Компания Microsoft уже работает над новой мобильной версией Windows, которая должна прийти на смену Windows Mobile 5.0, и обещает выпустить ее в конце 2006 года. Новая операционная система на- зывается Crossbow. По заявлениям разработчиков, в ней будут представлены расширенные средства синхронизации с програм- мами Office 2007 и Exchange 12. Также в состав операционной системы войдет новая программа Office Communicator, обладающая широкими возможностями об- мена информацией через мгновенные сообщения, голосовую связь и видео. Также появилась информация, что после Crossbow будет выпущена еще одна новая платформа под кодовым названием Photon. Главная особенность этой системы заключается в том, что ее можно будет использовать как на смартфонах, так и на карман- ных компьютерах. На сегодняшний день, по оценкам экспертов, компания Microsoft удерживает примерно 16% рынка мобильных операционных систем. Лидером в этом сегменте является операци- онная система Symbian, на долю которой приходится 63%. Но есть все предпосылки, что в ближайшем будущем эти цифры могут из- мениться в сторону увеличения доли Windows Mobile. Полезные ресурсы Напоследок хотелось бы привести несколько полезных ссылок на различные ресурсы в Сеги, которые могут оказаться полезными для разработчиков.
Полезные ресурсы 319 .NET Compact Framework 2.0 Redistributable Если вы пишете программы с использованием .NET Compact Framework 2.0, то при распространении программы надо либо вклю- чать в состав вашего установочного файла все необходимые биб- лиотеки, либо предложить пользователю самостоятельно уста- новить .NET Compact Framework 2.0. В этом случае достаточно бу- дет выкладывать на сайте только сам исполняемый файл програм- мы. Загрузить последнюю версию .NET Compact Framework 2.0 можно по адресу www.microsoft.com/downloads/details.aspx?familyid= =9655156b-356b-4a2c-857c-e62f50ae9a55&displaylang-en. Microsoft ActiveSync 4.1 Программа синхронизации ActiveSync используется для передачи файлов между настольным и карманным компьютерами. Последнюю версию программы можно скачать по адресу www.microsoft.com/ downloads/details.aspx?FamilyId=3926AlE0-CABD-4A45-8E5B- F938D9A69D8D&displaylang=ru. Русская версия эмулятора для Windows Mobile 5.0 Smartphone Помимо стандартного эмулятора для смартфона под управлением системы Windows Mobile 5.0 на английском языке, вы можете ска- чать и локализованную версию, которая располагается по адресу www.microsoft.com/downloads/details. aspx?familyid=52FED581-8F8D- -4C46-9966-4832098191B7&displaylang=ru. Русская версия эмулятора для КПК под управлением Windows Mobile 5.0 Pocket PC Также можно использовать русскую версию эмулятора для КПК под управлением операционной системы Windows Mobile 5.0, ко- торую можно найти по адресу www.microsoft.com/downloads/ details. aspx?FamilyID=eec33ae3-cl29-4c25-abaa-18e8e842178f&dis- playlang=en&Hash=S3HN6BD. Сайт Роба Майлза На страницах книги я использовал примеры разработчика Роба Майлза (Rob Miles). Он является автором многих статей, которые
320 Послесловие можно найти в документации MSDN. Также у него есть свой сайт www.robmiles.com, который стоит посетить. Сайт Кристиана Форсберга Кристиан Форсберг (Christian Forsberg), чьи примеры я использо- вал в этой книге, тоже ведет свой сайт www.businessanyplace.net, на котором можно найти много полезной информации. OpenNETCF.org Один из самых популярных сайтов, посвященных программиро- ванию при помощи .NET Compact Framework, расположился по адресу www.opennetcf.org. Особый интерес вызывают представлен- ные на сайте статьи и исходные коды приложений. Также на сай- те находятся очень интересные блоги опытных программистов, ко- торые делятся своими мыслями, разработками и примерами. Например, я частенько заглядываю на блог Алекса Яхнина по ад- ресу blog.opennetcf.org/ayakhnin, на котором не раз находил инте- ресные примеры. Часть этих примеров я также использовал в кни- ге с разрешения автора. Кстати, Алексу можно задать вопрос на русском языке! Google К сожалению, я не могу упомянуть все сайты, которые посвящены программированию с помощью .NET Compact Framework. Для по- иска новой информации можно воспользоваться услугами любой поисковой системы. Я рекомендую вам для этих целей использо- вать поисковую службу Google (www.google.com), которая осуще- ствляет поиск не только по сайтам, но и по группам новостей.
АНТИВИРУС ИГОРЯ ДАНИЛОВА № www.drweb.ru in шиш min С помощью этой книги вы научитесь создавать самые разнообразные приложения для карманных компьютеров и смартфонов. В издании рассмотрены принципы использования технологии программирования .NET Compact Framework, описан процесс создания приложений различного типа. Кроме того, здесь вы найдете подробные примеры и множество советов по использованию .NET Compact Framework. Тема: Общие вопросы программирования ^ППТ№ Заказ книг: 197198, Санкт-Петербург; а/я 619 тел.: (812) 703-73-74 postbook@pler.com 61093, Харьков-93, а7я 9130 тел.- (057) 712-27-05, piter@kharkov.piter.oom Уровень читателя: опытный ISBN 978-5-91180-270-7 9 78591 1 802707 www.piter.com — вся информация о книгах и веб-магазин