Текст
                    (bhv
л
•	Visual Studio .NET
•	Абстракция классов/данных
•	Компоновочные блоки/Пространства имен
•	Обработка исключений
•	GUI/формы Windows
•	Базы данных/ADO.NET/SQL
•	Файлы и потоки/XML
•	ASP.NET и Web-службы
*	Организация сетей
Разработка мобильных средств доступа в Web
руководство


Санкт-Петербург «БХВ-Петербург» 2006
УДК 681.3.068+800.92C# ББК 32.973.26-018.1 Д27 Дейтел, X. Д27 С#: Пер. с англ. / Дейтел X., Дейтел П., Листфилд Дж., Нието Т., Йегер ILL, Златкина М. — СПб.: БХВ-Петербург, 2006. —1056 с.: ил. ISBN 5-94157-817-2 Рассматриваются архитектура .NET, интегрированная среда разработки Visual Studio .NET и про- граммирование на С#, а также объектно-ориентированное программирование, концепции графического пользовательского интерфейса, язык XML, базы данных, SQL, ADO.NET, ASP.NET, Web-формы, Web- службы и элементы управления Web, организация сетей, структуры данных, обеспечение доступности программных приложений и пакет программ Mobile Internet Toolkit. Представлены действующие программы и примеры, протестированные в системах Windows 2000 и Windows ХР. Особое внимание уделено принципам корректного проектирования программных продук- тов и их удобочитаемости. Для программистов, имеющих опыт работы с языками высокого уровня УДК 681.3.068+800.92C# ББК 32.973.26-018.1 Группа подготовки издания: Главный редактор Зам. главного редактора Зав. редакцией Перевод с английского Научный редактор Редактор Компьютерная верстка Корректор Оформление обложки Зав. производством Екатерина Кондукова Татьяна Лапина Григорий Добин Дмитрия Ежова Андрей Никифоров Анна Кузьмина Ольги Сергиенко Зинаида Дмитриева Елены Беляевой Николай Тверских Authorized translation from the English language edition, entitled C# for Experienced Programmers, Iм Edition, ISBN 0-13-046133-4, by DEITEL, HARVEY M., DEITEL, PAUL J., published by Pearson Education, Inc., publishing as Prentice Hall PTR, Copyright C 2003 by Pearson Education, Inc. All rights reserved. No part of this book may be reproduced or transmitted in any form or by any means, electronic or mechanical, including photocopying, recording or by any information storage retrieval system, without permission from Pearson Education, Inc. RUSSIAN language edition published by BHV— St Petersburg, Copyright C 2006. Авторизованный перевод английской редакции, выпущенной Prentice Hall, Pearson Education, Inc., C 2003. Все права защищены. Никакая часть настоящей книги не может быть воспроизведена или перед ана в какой бы то ни было форме и какими бы то ни было средствами, будь то электрон- ные или механические, включая фотокопирование и запись на магнитный носитель, если на то нет разрешения Pearson Education, Inc Перевод на русский язык "БХВ-Петербург", С 2006. Лицензия ИД Ni 02429 от 24.07.00. Подписано в печать 20.05.06. Формат ВОхОО1/,. Почать офсетная. Усл. поч. л. 132. Тираж 2500 акз. Заказ Ni 3333 “БХВ-Петербург*. 194354, Санкт-Петербург, ул. Есенина, 56. Санитарно-эпидемиологическое заключение на продукцию Ni 77.99.02.953Д.006421.11.04 от 11.11.2004 г. выдано Федеральной службой по надзору в сфере защиты прав потребителей и благополучия человека. Отпечатано с готовых диапозитивов в ГУП «Типография «Наука» 199034, Санкт-Петербург, 9 линия, 12 ISBN 0-13-046133-4 (англ) ISBN 5-94157-817-2 (рус.) С 2003, Реапоп Education, Inc. С Перевод на русский язык “БХВ-Петербург", 2006
Оглавление s Об авторах..................................................................................................................................................... 15 Предисловие.................................................................................................................................................. 17 Потенциальная читательская аудитория.........................................................................................17 Особенности книги............................................................................................................18 Педагогический подход........................................................................................................20 Обучающие принципы LIVE-CODE™...........................................................................................20 Доступ к World Wide Web.................................................................................................20 Цитаты..................................................................................................................21 Темы 21 Вспомогательные элементы................................................................................................21 Резюме..................................................................................................................22 Благодарности................................................................................................................22 О корпорации Deitel & Associates, Inc........................................................................................23 Консорциум W3C...............................................................................................................24 Глава 1. Введение в архитектуру .NET и язык С#........—.................................................................. 25 1.1. Введение...............................................................................................................25 1.2. История Интернета и WWW................................................................................................25 1.3. Консорциум Всемирной паутины...........................................................................................26 1.4. Язык XML...............................................................................................................27 1.5. Ключевое направление развития программных средств: объектно-ориентированная технология.................................27 1.6. Введение в Microsoft .NET..............................................................................................29 1.7. Язык C#................................................................................................................30 1.8. .NET Framework и среда Common Language Runtime.........................................................................30 1.9. Обзор книги............................................................................................................31 1.10. Резюме...............................................................................................................38 1.11. Ресурсы Интернета и WWW..............................................................................................40 Глава 2. Введение в интегрированную среду разработки Visual Studio .NET и программирование на С#........................................................................................«............................»« 41 2.1. Введение..............................................................................................................41 2.2. Обзор интегрированной среды разработки Visual Studio .NET.............................................................42 2.3. Меню и панели инструментов............................................................................................44 2.4. Окна Visual Studio .NET...............................................................................................45 2.4.1. Окно Solution Explorer..........................................................................................46 2.4.2. Панель элементов................................................................................................46 2.4.3. Окно Properties.................................................................................................48 2.5. Использование справки.................................................................................................49 2.6. Простая программа: отображение текста и графики.......................................................................50 2.7. Простая программа: печать строки текста...............................................................................55 2.8. Арифметика............................................................................................................62 2.9. Принятие решений: операции отношения и равенства......................................................................63 2.10. Резюме............................................................................................................. 67
6 Оглавление Глава 3. Управляющие структуры.....................................................................68 3.1. Введение......................................................................................68 3.2. Управляющие структуры.........................................................................68 3.3. Условный оператор if..........................................................................69 3.4. Условный оператор if/else.....................................................................70 3.5. Оператор цикла while..........................................................................70 3.6. Операции присваивания.........................................................................73 3.7. Операции инкремента и декремента..............................................................73 3.8. Оператор цикла for............................................................................74 3.9. Пример: использование оператора for для расчета депозитного процента..........................76 3.10. Оператор выбора switch.......................................................................80 3.11. Оператор цикла do/while......................................................................83 3.12. Операторы break и continue...................................................................84 3 13. Логические и условные операции.............................................................86 3.14. Введение в программирование Windows-приложений...............................................89 3.15. Резюме.......................................................................................94 Глава 4. Методы и массивы..........................................................................95 4.1. Введение......................................................................................95 4.2. Методы в C#...................................................................................95 4.3. Определения методов...........................................................................96 4.4. Приведение типов аргументов..................................................................101 4.5. Пространства имен C#.........................................................................103 4.6. Типы значений и ссылочные типы...............................................................103 4.7. Передача аргументов по значению и по ссылке..................................................104 4.8. Правила области действия.....................................................................107 4.9. Рекурсия.....................................................................................109 4.10. Перегрузка методов..........................................................................111 4.11. Массивы.....................................................................................114 4.12. Объявление и распределение массивов.........................................................114 4.13. Передача массивов в методы..................................................................116 4.14. Передача массивов по значению и по ссылке...................................... ...........117 4.15. Многомерные массивы.........................................................................120 4.16. Оператор цикла foreach............................ ..........................................123 4.17. Резюме......................................................................................124 Глава 5. Объектно-ориентированное программирование................................................................ 125 5.1. Введение.....................................................................................125 5.2. Реализация абстрактного типа данных времени с помощью класса.................................126 5.3. Область действия классов.....................................................................132 5.4. Управление доступом к членам класса..........................................................132 5.5. Инициализация объектов классов: конструкторы.................................................133 5.6. Использование перегруженных конструкторов....................................................134 5.7. Свойства.....................................................................................137 5.8. Композиция: ссылки на объекты как переменные экземпляра других классов.......................143 5.9. Использование ссылки this....................................................................146 5.10. "Сбор мусора"...............................................................................148 5.11. Staffc-члены класса.........................................................................149 5.12. Члены класса const и readonly...............................................................152 5.13. Индексаторы.................................................................................154 5.14. Абстракция данных и сокрытие информации.....................................................159 5.15. Многократное использование программных средств..............................................160 5.16. Пространства имен и компоновочные блоки.....................................................161 5.17. Окна Class View и Object Browser............................................................165 5.18. Резюме......................................................................................167 Глава 6. Объектно-ориентированное программирование: наследование..................................... 168 6.1. Введение.....................................................................................168 6.2. Базовые и производные классы.................................................................169 6.3. Члены protected и internal...................................................................171
Оглавление 7 6.4. Взаимоотношения между базовыми и производными классами..................................172 6.5. Учебный пример: трехуровневая иерархия наследования....................................187 6.6. Конструкторы и деструкторы в производных классах.......................................190 6.7. Проектирование программ с наследованием................................................195 6.8. Резюме.................................................................................196 Глава 7. Объектно-ориентированное программирование: полиморфизм.............................197 7.1. Введение...............................................................................197 7.2. Преобразование объекта производного класса в объект базового класса....................197 7.3. Типы объектов и оператор switch........................................................203 7.4. Примеры полиморфизма...................................................................203 7.5. Абстрактные классы и методы............................................................204 7.6. Пример: наследование интерфейса и реализации...........................................206 7.7. Классы и методы типа sealed............................................................212 7.8. Пример: система начисления заработной платы с применением полиморфизма.................213 7.9. Пример: создание и использование интерфейсов...........................................221 7.10. Делегаты..............................................................................230 7.11. Перегрузка операторов.................................................................233 7.12. Резюме................................................................................238 Глава 8. Обработка исключительных ситуаций..................................................239 8.1. Введение...............................................................................239 8.2. Обзор процесса обработки исключительных ситуаций.......................................240 8.3. Пример: исключение DivideByZeroException...............................................243 8.4. Иерархия .NET-исключений...............................................................246 8.5. Блок finally...........................................................................247 8.6. Свойства класса Exception..............................................................254 8.7. Классы исключений, определенные пользователем..........................................257 8.8. Обработка переполнений с помощью операторов checked и unchecked.,......................261 8.9. Резюме.................................................................................262 Глава 9. Концепции графического пользовательского интерфейса: часть 1.......................264 9.1. Введение...............................................................................264 9.2. Формы Windows..........................................................................265 9.3. Модель обработки событий...............................................................267 9.3.1. Основы обработки событий.........................................................268 9.4. Свойства элементов управления и их размещение..........................................271 9.5. Метки, текстовые поля и кнопки.........................................................274 9.6. Группы и панели........................................................................279 9.7. Флажки и переключатели.................................................................282 9.8. Рамки изображений......................................................................288 9.9. Обработка событий мыши............................................................... 290 9.10. Обработка событий клавиатуры..........................................................292 9.11. Резюме................................................................................295 Глава 10. Концепции графического пользовательского интерфейса: часть 2......................297 10 1. Введение.............................................................................297 10.2. Меню..................................................................................298 10 3. Элемент управления LinkLabel.........................................................304 10.4. Элементы управления ListBox и CheckedListBox..........................................307 10.4.1. Элемент управления ListBox......................................................309 10.4.2. Элемент управления CheckedListBox...............................................311 10.5. Элемент управления ComboBox...........................................................314 10.6. Элемент управления TreeView...........................................................317 10.7. Элемент управления ListView...........................................................322 10.8. Элемент управления TabControl...................................................... 327 10.9. Многодокументный интерфейс Windows....................................................331 10.10. Визуальное наследование..............................................................338 10.11. Элементы управления, определенные пользователем......................................341 10.12. Резюме...............................................................................344
8 Оглавление Глава 11. Организация многозадачной обработки.......................________.............._........................... 346 11.1. Введение...........................................................................................346 11.2. Состояния потоков: жизненный цикл потока...........................................................347 11.3. Приоритеты потоков и их планирование...............................................................349 11.4. Синхронизация потоков и класс Monitor..............................................................352 11.5. Отношение "производитель/потребитель" без синхронизации потоков....................................354 11.6. Отношение "производитель/потребитель" с синхронизацией потоков.....................................359 11.7. Отношение "производитель/потребитель": кольцевой буфер.............................................366 11.8. Резюме.............................................................................................374 Глава 12. Строки, символы и регулярные выражения................................_____________________________.............................. 375 12.1. Введение...........................................................................................375 12.2. Основы понятий символов и строк....................................................................375 12.3. Конструкторы класса String.........................................................................376 12.4. Индексатор класса String, свойство Length и метод СоруТо..........................................377 12.5. Сравнительный анализ строк.........................................................................379 12.6. Метод GetHashCode класса String....................................................................381 12.7. Расположение символов и подстрок в классе String...................................................382 12.8. Извлечение подстрок из строк.......................................................................384 12.9. Сцепление строк....................................................................................385 12.10. Прочие методы класса String......................................................................386 12.11. Класс StringBuilder..............................................................................387 12.12. Индексатор класса StringBuilder, свойства Length и Capacity и метод EnsureCapacity...............388 12.13. Методы Append и AppendFormat класса StringBuilder................................................390 12.14. Методы Insert, Remove и Replace класса StringBuilder.............................................392 12.15. Методы Char......................................................................................395 12.16. Моделирование процессов тасования и раздачи карт.................................................397 12.17. Регулярные выражения и класс Regex...............................................................400 12.18. Резюме...........................................................................................408 Глава 13. Графика и мультимедиа_____________................................................_......................._...._410 13.1. Введение...........................................................................................410 13.2. Графические контексты и графические объекты.......................................................412 13.3. Управление цветом..................................................................................413 13.4. Управление гарнитурами шрифтов.....................................................................418 13.5. Рисование линий, прямоугольников и эллипсов.......................................................423 13.6. Рисование дуг......................................................................................425 13.7. Рисование многоугольников и полилиний..............................................................427 13.8. Дополнительные графические возможности.............................................................431 13.9. Общие сведения о мультимедиа.......................................................................435 13.10. Загрузка, отображение и масштабирование графических объектов.....................................436 13.11. Анимация серии изображений.......................................................................438 13.12. Windows Media Player.............................................................................448 13.13. Microsoft Agent..................................................................................450 13.14. Резюме...........................................................................................461 Глава 14. Файлы и потоки_______________....................................................................._............................... 462 14.1. Введение......................................................................................... 462 14.2. Иерархия данных....................................................................................462 14.3. Файлы и потоки.....................................................................................464 14.4. Классы File и Directory...........................................................................465 14.5. Создание файла с последовательным доступом.........................................................473 14.6. Считывание данных из файла с последовательным доступом.............................................482 14.7. Файлы с произвольном доступом.....................................................................491 14.8. Создание файла с произвольном доступом.............................................................494 14.9. Произвольная запись данных в файл с произвольным доступом..........................................497 14.10. Последовательное считывание данных из файла с произвольным доступом..............................500 14.11. Учебный пример: программа обработки транзакций...................................................504 14.11.1. Поведение обработки транзакций.............................................................504 14.11.2. GUI обработчика транзакций.................................................................507 14.12. Резюме...........................................................................................521
Оглавление 9 Глава 15. Язык XML...---------------......................................................................................................—...... 522 15.1. Введение.................................................................................................522 15.2. Документы XML............................................................................................522 15.3. Пространства имен XML................................................................................... 526 15.4. Объектная модель документа (DOM)........................................................................ 528 15.5. Document Type Definition, схемы и проверка...............................................................543 15.5.1. Определения типа документа.........................................................................543 15.5.2. Microsoft XML Schema...............................................................................547 15.5.3. W3C XML Schema.....................................................................................548 15.5.4. Проверка схемы в C#................................................................................550 15.6. Язык XSL и класс XslTransform............................................................................553 15.7. Microsoft BizTalk........................................................................................558 15.8. Резюме...................................................................................................560 15.9. Ресурсы Интернета и WWW..................................................................................561 Глава 16. Базы данных, SQL и ADO.NET................................................................----............ 563 16.1. Введение.................................................................................................563 16.2. Модель реляционной базы данных...........................................................................563 16.3. Обзор реляционной базы данных: база данных Books.........................................................564 16.4. Structured Query Language (SQL)..........................................................................569 16.4.1. Базовый запрос SELECT..............................................................................569 16.4.2. Выражение WHERE....................................................................................570 16.4.3. Выражение ORDER BY.................................................................................572 16.4.4. Слияние данных из нескольких таблиц: INNER JOIN....................................................575 16.4.5. Объединение данных из таблиц Authors, AuthorlSBN, Titles и Publishers..............................576 16.4.6. Оператор INSERT....................................................................................578 16.4.7. Оператор UPDATE....................................................................................579 16.4.8. Оператор DELETE....................................................................................580 16.5. Объектная модель ADO.NET.................................................................................580 16.6. Программирование с помощью ADO.NET: извлечение информации из базы данных.................................581 16.6.1. Подключение к источнику доступа к данным и запросы............................................... 581 16.6.2. Запросы базы данных Books..........................................................................587 16.7. Программирование с помощью ADO.NET: модификация базы данных..............................................589 16.8. Считывание файлов XML и запись в них.....................................................................595 16.9. Резюме...................................................................................................597 Глава 17. ASP.NET, Web-формы и элементы управления Web........................................—......... 599 17.1. Введение.................................................................................................599 17.2. Простая транзакция HTTP..................................................................................600 17.3. Системная архитектура....................................................................................602 17.4. Создание и запуск простого примера Web-формы.............................................................602 17.5. Элементы управления Web..................................................................................612 17.5.1. Элементы управления текстом и графикой.............................................................612 17.5.2. Элемент управления AdRotator.......................................................................616 17.5.3. Контролирующие элементы управления.................................................................620 17.6. Контроль сеансов.........................................................................................628 17.6.1. Cookies............................................................................................629 17.6.2. Контроль сеансов с помощью класса HttpSessionState.................................................637 17.7. Учебный пример: гостевая книга в режиме on-line..........................................................644 17.8. Учебный пример: подключение к базе данных в ASP.NET......................................................650 17.9. Трассировка..............................................................................................661 17.10. Резюме..................................................................................................663 17.11. Ресурсы Интернета и WWW.................................................................................664 Глава 18. ASP.NET и Web-службы......................................................................................................... 666 18.1. Введение.................................................................................................666 18.2. Web-службы...............................................................................................667 18.3. Протокол SOAP и Web-службы...............................................................................670 18.4. Публикация и использование Web-служб.....................................................................671
10 Оглавление 18.5. Контроль сеансов в Web-службах...............................................................682 18.6. Использование Web-форм и Web-служб...........................................................692 18.7. Учебный пример: программа определения температуры............................................697 18.8. Пользовательские типы в Web-службах..........................................................706 18.9. Резюме.......................................................................................714 Глава 19. Организация сетей: сокеты на основе потоков и дейтаграммы................................715 19.1. Введение.....................................................................................715 19.2. Создание простого сервера с помощью потоковых сокетов........................................716 19.3. Создание простого клиента с помощью потоковых сокетов........................................717 19.4. Взаимодействие "клиент-сервер" посредством потоковых сокетов.................................718 19.5. Взаимодействие системы "клиент-сервер’.' с дейтаграммами независимо от наличия соединения....725 19.6. Игра "Крестики-нолики" клиента с сервером через многопоточный сервер.........................730 19.7. Резюме.......................................................................................741 Глава 20. Структуры данных и коллекции______________..................._____..........._______......... 742 20.1. Введение.....................................................................................742 20.2. Самоотносимые классы.........................................................................743 20.3. Связанные списки.............................................................................744 20.4. Стеки........................................................................................753 20.5. Очереди......................................................................................757 20.6. Деревья......................................................................................760 20.6.1. Дерево двоичного поиска целочисленных значений.........................................761 20.6.2. Дерево двоичного поиска объектов IComparable...........................................767 20.7. Коллекции классов............................................................................773 20.7.1. Класс Array............................................................................773 20.7.2. Класс ArrayList........................................................................775 20.7.3. Класс Stack............................................................................780 20.7.4. Класс Hashtable........................................................................784 20.8. Резюме.......................................................................................789 Глава 21. Обеспечение доступности программных приложений..................................................... 791 21.1. Введение...........................................................................;.........791 21.2. Законодательство и компьютерные ресурсы.................................................... 792 21.3. Инициатива доступности Web...................................................................793 21.4. Альтернативные способы просмотра изображений в сети......................................... 794 21.5. Повышение удобочитаемости совершенствованием структуры.......................................795 21.6. Доступность в Visual Studio .NET.............................................................795 21.6.1. Увеличение размеров значков на панелях инструментов....................................796 21.6.2. Увеличение размера шрифта текста..................................................... 796 21.6.3. Модификация панелей инструментов.......................................................797 21.6.4. Модификация клавиатуры.................................................................798 21.6.5. Перегруппировка окон............................................ ;.................799 21.7. Доступность в C#.............................................................................799 21.8. Доступность в таблицах XHTML.................................................................804 21.9. Доступность во фреймах XHTML.................................................................807 21.10. Доступность в XML......................................................................... 808 21.11. Использование речевого синтеза и распознавания с помощью пакета VoiceXML....................808 21.12. CallXML.....................................................................................814 21.13. JAWS для Windows............................................................................819 21.14. Другие инструменты доступа..................................................................819 21.15. Доступность в операционной системе Microsoft Windows 2000...................................820 21.15.1. Инструментальные средства доступности для слабовидящих пользователей..................821 21.15.2. Инструментальные средства доступности для слабослышащих пользователей.................822 21.15.3. Инструментальные средства доступности для пользователей без возможности работы с клавиатурой..................................................................................822 21.15.4. Microsoft Narrator....................................................................825 21.15.5. Экранная клавиатура Microsoft.........................................................826 21.15.6. Функции доступности в Internet Explorer 6.0...........................................827 21.16. Резюме......................................................................................828
Оглавление 11 21.17. Ресурсы Интернета и WWW.............................................................................829 21.17.1. Общая информация, руководства и определения...................................................829 21.17.2. Разработка приложений доступности по существующим технологиям.................................829 21.17.3. Информация о физических недостатках...........................................................831 Глава 22. Mobile Internet Toolkit............---------------.............................................. 832 22.1. Введение............................................................................................ 832 22.2. Клиентские устройства MIT............................................................................832 22.3. Введение в понятия М1Т и мобильной Web-формы.........................................................833 22.4. Усовершенствованные элементы управления мобильными Web-формами.......................................844 22.5. Пример: беспроводной портал Deitel...................................................................851 22.6. Аппаратно-независимый Web-дизайн с помощью таблиц стилей и шаблонов..................................855 22.7. Использование Web-служб в мобильном приложении.......................................................866 22.8. Резюме...............................................................................................870 22.9. Ресурсы Интернета и WWW..............................................................................870 Приложение 1. Приоритет операций............................................................................871 Приложение 2. Системы счисления....................................................................................................... 873 П2.1. Введение..............................................................................................873 П2.2. Перевод двоичных чисел в восьмеричные и шестнадцатеричные числа.......................................875 П2.3. Преобразование восьмеричных и шестнадцатеричных чисел в двоичные......................................877 П2.4. Преобразование двоичных, восьмеричных или шестнадцатеричных чисел в десятичные........................877 П2.5. Преобразование десятичных чисел в двоичные, восьмеричные или шестнадцатеричные........................878 П2.6. Отрицательные двоичные числа: запись дополнения двойки................................................879 П2.7. Резюме...........................................................................-...................880 Приложение 3. Возможности карьерного роста------------------.......................................................................... 881 П3.1. Введение..............................................................................................881 П3.2. Интернет-ресурсы для соискателей......................................................................882 ПЗ.З. Интерактивные возможности для работодателей...........................................................884 П3.3.1. Размещение в сети объявлений о вакансиях........................................................884 П3.3.2. Недостатки трудоустройства через Интернет..................................-....................885 ПЗ.З.З. Разнородность рабочих мест......................................................................885 П3.4. Службы трудоустройства.............................................................................. 886 П3.5. Web-сайты трудоустройства и вакансий..................................................................887 ПЗ.5.1. Универсальные сайты трудоустройства.............................................................887 ПЗ.5.2. Технические вакансии.......................................................................... 887 П3.5.3. Вакансии в области беспроводных технологий......................................................888 ПЗ.5.4. Заключение контрактов через Интернет............................................................888 ПЗ.5.5. Управленческие должности.................................................................... 889 ПЗ.5.6. Студенты и молодые специалисты..................................................................890 ПЗ.5.7. Другие интерактивные службы трудоустройства.....................................................890 П3.6. Резюме................................................................................................891 П3.7. Ресурсы Интернета и WWW...............................................................................891 Сайты трудоустройства специалистов по информационным технологиям........................................891 Сайты трудоустройства.........................................л.........................................892 Управленческие должности................................................................................893 Разнородность...........................................................................................893 Сайты для людей с ограниченными физическими возможностями...............................................893 Общие ресурсы...........................................................................................893 Сайты по интересам......................................................................................894 Интерактивное заключение контрактов.....................................................................894 Службы трудоустройства..................................................................................894 Ресурсы трудоустройства в области беспроводных технологий...............................................895 Приложение 4. Отладчик Visual Studio .NET....................................................................................... 896 П4.1. Введение..............................................................................................896 П4.2. Точки прерывания......................................................................................897
12 Оглавление П4.3. Просмотр данных.................................................................................................899 П4.4. Управление программой...........................................................................................901 П4.5. Дополнительные возможности отладки методов......................................................................903 П4.6. Дополнительные возможности отладки классов......................................................................905 П4.7. Резюме..........................................................................................................907 Приложение 5. Создание документации в Visual Studio .NET............................________________........................ 909 П5.1. Введение........................................................................................................909 П5.2. Комментарии к документации......................................................................................909 П5.3. Документирование исходного кода C#..............................................................................910 П5.4. Создание Web-страниц документации...............................................................................916 П5.5. Создание файлов XML-документации.............................................................................. 917 П5.6. Резюме..........................................................................................................922 Приложение 6. Набор символов ASCII.................................................................................................. 923 Приложение 7. Unicode............................................................................................................................. 924 П7.1. Введение........................................................................................................924 П7.2. Форматы преобразования Unicode..................................................................................925 П7.3. Символы и глифы.................................................................................................926 П7.4. Преимущества и недостатки Unicode...............................................................................926 П7.5. Web-сайт Консорциума Unicode....................................................................................927 П7.6. Использование кодировки Unicode.................................................................................927 П7.7. Диапазоны символов..............................................................................................930 П7.8. Резюме..........................................................................................................931 Приложение 8. Интеграция СОМ........................................................................................................... 932 П8.1. Введение........................................................................................................932 П8.2. Интеграция ActiveX..............................................................................................933 П8.3. Интеграция DLL..................................................................................................936 П8.4. Резюме..........................................................................................................939 П8.5. Ресурсы Интернета и WWW.........................................................................................940 Приложение 9. Введение в HTML 4: часть 1.........._________________.................___________..............._........ 941 П9.1. Введение........................................................................................................941 П9.2. Языки разметки..................................................................................................941 П9.3. Редактирование кода HTML........................................................................................942 П9.4. Общие элементы..................................................................................................942 П9.5. Заголовки.......................................................................................................944 П9.6. Ссылки..........................................................................................................945 П9.7. Изображения.....................................................................................................947 П9.8. Специальные символы и разделительные линии......................................................................950 П9.9. Маркированные списки............................................................................................951 П9.10. Вложенные и нумерованные списки................................................................................953 П9.11. Резюме.........................................................................................................955 П9.12. Ресурсы Интернета и WWW........................................................................................956 Приложение 10. Введение в HTML 4: часть 2.........................................................м........................... 957 П10.1. Введение.......................................................................................................957 П10.2. Базовые таблицы HTML...........................................................................................957 П10.3. Сложные таблицы HTML и их форматирование.......................................................................959 П10.4. Базовые формы HTML.............................................................................................961 П10.5. Более сложные формы HTML.......................................................................................964 П10.6. Внутренние ссылки..............................................................................................969 П10.7. Создание и использование карт изображений......................................................................972 П10.8. Тег <meta>.....................................................................................................974 П10.9. Тег <frameset>.................................................................................................975 П10.10. Вложенные теги <framesef>.....................................................................................977 П10.11. Резюме........................................................................................................979 П10.12. Ресурсы Интернета и WWW.......................................................................................980
Оглавление 13 Приложение 11. Введение в XHTML: часть 1..............................................--------------.........................._981 П11.1. Введение.............................................................................................................981 П11.2. Редактирование XHTML.................................................................................................981 П11.3. Первый пример XHTML..................................................................................................982 П11.4. Служба контроля XHTML W3C............................................................................................984 П11.5. Заголовки............................................................................................................985 П11.6. Ссылки...............................................................................................................986 П11.7. Изображения..........................................................................................................989 П11.8. Специальные символы и разрывы строк..................................................................................992 П11.9. Маркированные списки.................................................................................................993 П11.10. Вложенные и нумерованные списки.....................................................................................995 П11.11. Резюме.................................................................................................................997 П11.12. Ресурсы Интернета и WWW................................................................................................998 Приложение 12. Введение в XHTML: часть 2......—......................................................................... 999 П12.1. Введение.............................................................................................................999 П12.2. Базовые таблицы XHTML................................................................................................999 П12.3. Сложные таблицы XHTML и их форматирование..............................................................1001 П12.4. Базовые формы XHTML.................................................................................................1003 П12.5. Более сложные формы XHTML...........................................................................................1006 П12.6. Внутренние ссылки...................................................................................................1012 П12.7. Создание и использование карт изображений...........................................................................1014 П12.8. Тег <meta>..........................................................................................................1016 П12.9. Тег <frameset>......................................................................................................1017 П12.10. Вложенные теги <framesef>..............................................................................................1020 П12.11. Резюме.............................................................................................................1022 П12.12. Ресурсы Интернета и WWW............................................................................................1023 Приложение 13. Специальные символы HTML/XHTML............................................................... 1024 Приложение 14. Цвета HTML/XHTML____________________________________________________________________________________________1025 Приложение 15. Поразрядные операции............................................................................................. 1028 П15.1. Введение............................................................................................................1028 П15.2. Побитовые операции..................................................................................................1028 П15.3. Класс BitArray......................................................................................................1037 П15.4. Резюме..............................................................................................................1039 Приложение 16. Crystal Reports для Visual Studio .NET...............................-------------------------------------------1041 П16.1. Введение............................................................................................................1041 П16.2. Ресурсы Web-сайта Crystal Reports...................................................................................1041 П16.3. Crystal Reports и Visual Studio .NET................................................................................1041 П 16.3.1. Crystal Reports в Web-приложениях...........................................................................1043 П16.3.2. Crystal Reports и Web-службы.................................................................................1044 Библиография........................................................................................................................................... 1045 Предметный указатель........................................................................................................................... 1048
Карен МакЛин, редактору серии "Developer" компании Deitel: "Мы польщены возможностью работать над такой специфической программой публика- ций с тобой — виртуозным профессионалом и нашим другом". Харви и Пол Дейтел Уиллу О'Брайену, Петру Доллару, Эндрю Флемингу и Эдаму Россу: "Во имя того прекрасного времени, которое мы провели вместе за последние четыре года". Джефф Светлой памяти дедушки Фостера. Тэм Р. Нието Моему брату Лауэллу: "Ты — самый веселый и заботливый человек из тех, кого я знаю". Шерил Особому человеку в моей жизни — Игорю Левиту, "...который всегда приходил на помощь и поддерживал меня в самые сложные моменты. Спасибо за то, что ты такой". С любовью, Марина
Об авторах Профессор Харви М. Дейтел, председатель и стратегический руководитель корпорации Deitel & Associates, Inc., в течение 41 года занимается вычислительной техникой, обладает огромным опытом работы в данной инду- стрии, а также в области преподавания. Проф. Дейтел защитил степени бакалавра и магистра в Массачусет- ском технологическом институте и степень доктора наук в Бостонском университете. Работал над проектами первых виртуальных операционных систем в корпорации IBM и MIT, разрабатывавших технологии, в на- стоящее время широко применяющиеся в таких системах, как Unix, Linux™ и Windows ХР. В течение 20 лет преподавал в колледже, одновременно являясь заведующим кафедрой вычислительной техники Бостонского колледжа, до основания корпорации Deitel & Associates, Inc. вместе со своим сыном, Полом. Автор и соавтор большого количества книг и мультимедийных программных пакетов. Работы проф. Дейтела приобрели все- мирную известность, благодаря переводам на японский, русский, испанский, классический китайский, упро- щенный китайский, корейский, французский, польский, итальянский, португальский и греческий языки. Проф. Дейтел проводит специализированные семинары в ведущих промышленных корпорациях, а также в государственных институтах и различных областях оборонной промышленности. Пол Дж. Дейтел, главный технический управляющий корпорации Deitel & Associates, Inc., является выпускни- ком Школы менеджмента Слоуна при Массачусетском технологическом институте по специальности "Ин- формационные технологии". В Deitel & Associates, Inc. Пол занимался организацией учебных курсов по про- граммированию на Java, С, C++, в Интернете и WWW для таких промышленных корпораций, как Compaq, Sun Microsystems, White Sands Missile Range, Rogue Wave Software, Boeing, Dell, Stratus, Fidelity, Cambridge technology Partners, Open Environment Corporation, One Wave, Hyperion Software, Lucent Technologies, Adra Systems, Entergy, CableData Systems, NASA в Космическом центре им. Кеннеди, Национальной лаборатории по изучению ураганов, IBM и многих других. Читал лекции по C++ и Java в бостонском подразделении Ас- социации вычислительной техники, а также проводил курсы по спутниковым системам на базе Java для со- трудников совместной компании Deitel & Associates, Inc., Prentice Hall и Technology Education Network. Пол и его отец— проф. Харви М. Дейтел — на сегодняшний день являются авторами наиболее часто продавае- мых книг по вопросам программирования. Тэм Р. Нието, директор подразделения разработок корпорации Deitel & Associates, Inc., является выпускником Массачусетского технологического института по специальности инженера вычислительной техники. Во время работы в Deitel & Associates, Inc. проводил учебные курсы для таких клиентов, как Sun Microsystems, Compaq, EMC, Stratus, Fidelity, NASDAQ, Art Technology, Progress Software, Toys "R" Us, Отдел технической поддержки Национальной океанографической и метеорологической службы, Jet Propulsion Laboratoiy, Nynex, Motorola, Федеральный резервный банк Чикаго, Banyan, Schlumberger, университет Нотр-Дам, NASA, Hewlett-Packard, различные оборонные инициативы и многое другое. Является соавтором многих книг и мультимедийных пакетов Deitel, а также внес неоценимый вклад практически в каждое издание корпорации Deitel & Associates, Inc. Шерил X. Йегер, директор Microsoft Software Publications совместно с Deitel & Associates, Inc., закончила Бос- тонский университет экстерном за три года со степенью бакалавра вычислительной техники. Является соав- тором многих публикаций Deitel & Associates, Inc., включая "C# How to Program", "C# A Programmer’s Introduction", "C# for Experienced Programmers" и "Visual Basic .NET for Experienced Programmers", а также внесла значительный вклад в такие издания, как "Perl How to Program", "Wireless Internet & Mobile Business How to Program", "Internet and World Wide Web How to Program, Second Edition" и "Visual Basic .NET How to Program, Second Edition".
16 Об авторах Марина Златкина закончила Университет Брандайс экстерном за три года с ученой степенью в области вычис- лительной техники и математики; за четвертый год обучения защитила степень магистра вычислительных наук. Во время учебы в университете проводила исследования в области баз данных и являлась ассистентом преподавателя. Соавторствовала при написании таких книг, как "C# How to Program", "C# A Programmer's In- troduction", "C# for Experienced Programmers", а также внесла вклад с другое издание Deitel — "e-Business & e-Commerce for Managers". Джефф А. Листфилд — выпускник Гарвардского колледжа по специальности "Вычислительная техника". В его дипломную работу входили курсы по компьютерной графике, организации сетей, теории вычислений; имеет опыт программирования на С, C++, Java, Perl и Lisp. Джефф является соавтором книг "C# How to Program", "C# A Programmer's Introduction”, "C# for Experienced Programmers", а также внес вклад в написание книги "Perl How to Program".
Предисловие Довольно жить отрывками Объединяйте их. Эдвард Морган Форет ер Детьми плели мы сети Из солнышка плели... Шарлота Бронте Добро пожаловать в C# и мир программирования в Windows, Интернете и WWW на Visual Studio .NET и плат- форме .NET! Язык программирования C# создан корпорацией Microsoft специально для платформы .NET. В C# представлены важнейшие аспекты разработок в области вычислительных систем: объектно-ориентированное программирова- ние, обработка графических объектов, компоненты графического пользовательского интерфейса (GUI), обра- ботка исключений, многопоточная обработка, мультимедиа (аудио, изображения, анимация и видео), обработка файлов, подготовленные структуры данных, работа с базами данных, разработка многозвенных программных приложений для Интернета и WWW, организация сетей, Web-службы и распределенные вычисления. Данный язык подходит для создания приложений, работающих в Интернете и WWW, напрямую интегрируемых с про- граммами на базе Windows. Платформа .NET обеспечивает колоссальные возможности для разработки и развертывания программных средств, включая независимость от языков и платформ. Например, программисты, пишущие код на одном (или нескольких) языках .NET (С#, Visual Basic .NET и Visual C++ .NET), могут вводить в один программный про- дукт разные компоненты. Помимо обеспечения независимости от языков, .NET заметно повышает мобильность программ их размещением на разных платформах и обеспечением взаимодействия. При этом облегчается про- цесс создания и использования Web-служб, представляющих собой программные приложения, передающие клиентам различные функции по Интернету. Платформа .NET позволяет использовать приложения на базе Web в бытовых электронных приборах, например, в беспроводных телефонах и карманных компьютерах так же, как и в обычных настольных рабочих станциях. Возможности, включенные корпорацией Microsoft в платформу .NET, значительно повышают производитель- ность программистов и сокращают время разработки продуктов. Потенциальная читательская аудитория Корпорация Deitel & Associates, Inc. выпустила несколько публикаций по теме С#, предназначенных для разных групп читателей. Для облегчения задачи выбора подходящих для них изданий авторы предоставили соответст- вующую информацию на корпоративном сайте www.deitel.com. Данная книга предназначена для опытных разработчиков, испытывающих потребность в более глубоком пони- мании новых технологий с минимумом вводных материалов. В книге весьма досконально рассматриваются сложные аспекты. В книге по C# представлено множество готовых действующих программ с описанием их входных и выходных данных в виде изображений окон работающих программ. Таков сигнатурный подход Live-Code™: все концепции представлены в контексте действующих программных приложений. Исходный код, описываемый в книге, дос- тупен бесплатно на сайте www.deitel.com. Подробный список продуктов и услуг от Deitef представлен на сайте www.deitel.com. Читателей также может заинтересовать подписка на новый интерактивный информационный бюллетень Buzz Online корпорации Deitel™ (www.deitel/newsletter/subscribe.html), в котором представлены новые публикации, корпоративные анонсы, ссылки на технические статьи, советы по программированию, методики обучения, способы решения возникающих проблем и курьезные случаи. 2 Зак. 3333
18 Предисловие Если перед ознакомлением с данной книгой читатели захотят проконсультироваться с нами по тем или иным вопросам, то это можно сделать по электронной почте (deitel@deitel.com); ответ последует незамедлительно. Частые обновления, выявленные ошибки, вопросы и ответы представлены на сайтах www.deitel.com, www.prenhall.com/deitel и www.lnformlt.com/deitel. При отправке электронных сообщений рекомендуется ука- зывать название книги и номер издания. Авторы искренне надеются, что представленные книги помогут читате- лям в изучении С#. Особенности книги Данное издание содержит много функций. □ Web-формы, элементы Web-управления и ASP.NET. Платформа NET позволяет разработчикам созда- вать надежные, расширяемые программные приложения на базе Web. Серверная технология .NET от Micro- soft— Active Server Pages .NET (ASP.NET)— обеспечивает возможность создания Web-документов, отвечающих на запросы клиентов. Для активизации интерактивных Web-страниц серверные программы об- рабатывают информацию, вводимую пользователями в формы HTML. Технология ASP.NET представляет значительное отступление от ASP 3.0 и обеспечивает разработчиков возможностью создания программных приложений на базе Web с помощью мощных объектно-ориентированных языков платформы .NET, таких как C# и Visual Basic .NET, вместо использования только языков подготовки сценариев. ASP.NET также предоставляет расширенные возможности визуального программирования, похожие на методы, применяе- мые при создании форм Windows для программ настольных рабочих станций. Программисты могут созда- вать Web-страницы визуально путем перетягивания элементов Web-управления на Web-формы. Эти техно- логии описываются в главе 17. О Web-службы и ASP.NET. Стратегия .NET от Microsoft рассматривает Интернет и WWW как неотъемлемые части разработки и развертывания программных средств. Технология Web-служб обеспечивает совместное использование информации несколькими клиентами, ведение электронного бизнеса и другие виды взаимо- действия с помощью стандартных протоколов Интернета и технологий, например, HTTP, XML, SOAP. Web- службы обеспечивают программистов возможностью создания пакетов функций для приложений так, что сеть превращается в своего рода библиотеку программных компонентов многократного пользования. В гла- ве 18 представлена Web-служба, позволяющая выполнять операции с "большими числами" —- настолько большими, что они не умещались в типах данных, встроенных в С#. В данном примере пользователь вводит два больших числа, нажимает кнопки для активизации Web-служб, выполняющих операции сложения и вы- читания, и сравнивает два числа. Информация, имеющая отношение к Web-службам, также представлена в приложении 16, где рассматривается популярная программа составления отчетов в приложениях, зависящих от баз данных. Программа Ciystai Reports, интегрированная в Visual Studio .NET, обеспечивает возможность выведения отчета в виде Web-службы. В приложении представлена вводная информация и ссылка на анализ на сайте Crystal Decisions — www.crystaldecisions.com/netzone. □ Объектно-ориентированное программирование. Объектно-ориентированное программирование— наи- более широко применяемая методика разработки надежного программного обеспечения с возможностью многократного его использования. В тексте книги расширенно представлены функции объектно- ориентированного программирования. В главе 5 описывается создание классов и объектов. Рассмотрение этих концепций продолжено в главе 6, где описываются процессы оперативного создания новых классов пу- тем "поглощения" функций существующих классов. Глава 7 знакомит читателя с основополагающими кон- цепциями полиморфизма, с абстрактными и конкретными классами и интерфейсами, упрощающими задачу выполнения операций над объектами, принадлежащими иерархии наследования. □ XML. Расширяемый язык разметки (XML) чрезвычайно широко используется в индустрии разработки про- граммного обеспечения, в сообществах электронного бизнеса и коммерции и стремительно распространяет- ся в приложениях на платформе .NET. Благодаря независимости от платформы технология описания данных и создания языков разметки способствует переносу данных XML и хорошему интегрированию с мобильны- ми приложениями и службами на базе С#. Язык XML представлен в главе 15. В главе описывается базовая разметка XML и обсуждаются такие технологии, как описание шаблонов документов (Document Type Defini- tion, DTD) и Schema, используемые для контроля корректности содержимого документов XML. Также опи- сываются способы программных манипуляций документами XML с помощью модели объекта документа (Document Object Model, DOM “) и аспекты преобразования документов XML в документы других типов с использованием языка XSLT. □ Многопоточная обработка. Большинство операций выполняется компьютерами параллельно (т. е. одно- временно), например, печать документов, загрузка файлов из Интернета и просмотр сайтов. Многопоточ- ность — это технология, с помощью которой разрабатываются программные приложения, осуществляющие
Предисловие.............................................................................. 19 параллельное выполнение нескольких операций. На заре развития вычислительной техники в компьютер входил один дорогостоящий процессор, производительность которого "распределялась" операционной сис- темой между приложениями. Современные процессоры стремительно дешевеют, что обеспечило возмож- ность сборки вполне доступных по стоимости компьютеров с несколькими процессорами, работающими па- раллельно: они называются мультипроцессорными. Режим многопоточной обработки данных одинаково эффективен как на однопроцессорных, так и на мультипроцессорных системах. Возможности многопоточ- ности и связанных с ней технологий делают платформу .NET лучше подготовленной для работы с современ- ными сложнейшими программными распределенными мультипроцессорными приложениями, тесно связан- ными с мультимедийными средствами, базами данных и сетями. Данная функция подробно описывается в главе 11. □ ADO.NET. Огромные объемы информации сохраняются в базах данных, доступом к которым должны иметь как отдельные пользователи, так и организации, для успешного ведения бизнеса. Развившись из технологии ADO корпорации Microsoft (ActiveX Data Objects)1, ADO.NET представляет новый подход к разработке про- граммных приложений взаимодействия с базами данных. В ADO.NET используется XML и расширенная объектная модель для обеспечения программистов инструментальными средствами, необходимыми для дос- тупа к базам данных и манипуляций ими в крупных, расширяемых, ответственных многозвенных приложе- ний. В главе 16 представлены возможности операций с базами данных ADO.NET и языка структурирован- ных запросов (Structured Query Language, SQL). □ Разработка беспроводных программных приложений. По некоторым оценкам порядка миллиарда чело- век во всем мире пользуются портативными устройствами— телефонами, карманными компьютерами (Personal Digital Assistant, PDA) и т. д., и число таких пользователей постоянно растет. Для облегчения зада- чи создания Web-содержимого для портативных устройств корпорация Microsoft представляет мобильный инструментальный набор выхода в Интернет— Mobile Internet Toolkit (MIT). MIT, созданный на базе - ADO.NET, обеспечивает разработку беспроводных приложений с помощью объектно-ориентированных языков Visual Studio .NET. Можно разработать одну программу, которая будет совместимой с множеством различных устройств и способной отображать разное содержимое в зависимости от типа устройства (напри- мер, по беспроводному телефону или PDA). В главе 22 описывается разработка беспроводных Web- приложений. □ Отладчик Visual Studio .NET. Программы-отладчики помогают разработчикам в поиске и исправлении логических ошибок в кодах программ. В приложении 4 авторы описывают использование основных функ- ций отладчика, таких как задание "точек прерывания" и "часов", обращение к методам и выход из них, а также изучение стека обращений к методам. □ Интеграция модели компонентных объектов (Component Object Model, СОМ). До появления техноло- гии .NET многие организации тратили время и огромные средства на создание программных компонентов, называемых СОМ, которые включали в себя элементы управления ActiveX® и динамически подключаемые библиотеки (Dynamic Link Libraries, DLL) ActiveX для приложений на базе Windows. В приложении 8 рас- сматриваются некоторые инструментальные средства, имеющиеся в Visual Studio .NET, для интеграции па- тентованных компонентов в приложения .NET. Подобная интеграция позволяет программистам использо- вать существующие наборы элементов управления на базе СОМ вместе с компонентами платформы .NET. □ Документация XML. Документирование кода программы является одним из важнейших аспектов разра- ботки, потому что в течение жизненного цикла продукта (например, при разработке новых версий, которая может длиться годами) над ним работают разные программисты. При составлении документации для мето- дов и кодов разработчикам разных аспектов и функций приложения проще понять логику кода; при этом экономиз ся время и снижается вероятность разночтений. С целью автоматизации процесса документирова- ния программ в Visual Studio .NET для программистов, работающих с С#, имеется инструмент XML. В при- ложении 5 описывается процесс вставки комментариев в код с созданием отдельного файла документации кода. □ Возможности карьерного роста. В приложении 3 представлены службы трудоустройства, имеющиеся в Интернете. Они рассматриваются как с точки зрения работодателя, так и служащего. В приложении приво- дится много адресов Web-сайтов, на которых читатели могут подавать заявления об устройстве на работу» осуществлять поиск вакансий и просматривать резюме соискателей. Также рассматриваются службы созда- ния страниц трудоустройства непосредственно на сайтах электронного бизнеса. Авторы получили отзыв од- ного из читателей о том, что он использовал Интернет в качестве основного средства поиска работы, и при- ложение 3 книги оказало ему весьма значительную помощь. 1 Технология доступа к данным, включающая набор высокоуровневых интерфейсов, позволяющих разработчикам обращаться к данным на любом языке программирования.
20 Предисловие □ Unicode®. По мере повсеместного развития компьютерных систем их производители разработали числовые обозначения наборов знаков алфавитов и специальных символов для языков общения во многих странах. В некоторых случаях для одного языка создавались различные обозначения. Разрозненные наборы символов значительно сдерживали взаимодействие между компьютерными системами. Язык C# поддерживает стан- дартный Unicode (обслуживаемый некоммерческой организацией под названием Консорциум Unicode), пре- доставляющий единый набор символов, который присваивает уникальные числовые значения знакам алфа- витов и специальным символам большинства мировых языков. В приложении 7 обсуждается сам стандарт, рассматривается Web-сайт Консорциума Unicode — www.unicode.org — и представлено приложение С#, в котором фраза "Добро пожаловать в Unicode!" отображается на нескольких языках. □ XHTML. Консорциум Всемирной паутины (World Wide Web Consortium, W3C) объявил HTML действую- щей технологией, не подлежащей дальнейшим усовершенствованиям. В настоящее время HTML замещается расширяемым языком гипертекстовой разметки (Extensible Hypertext Markup Language, XHTML) — техноло- гией на базе XHTML, которая быстро превращается в стандарт описания Web-содержимого. Язык XHTML используется в главе 15, где представлено введение в технологию, а также в приложениях 11 и 12. Здесь рас- сматриваются заголовки, изображения, списки, карты изображений и другие особенности совершенствую- щегося языка разметки (HTML также рассматривается в приложениях 9 и 10, потому что ASP.NET (см. гла- вы 17 и 18) генерирует содержимое HTML.) □ Доступность программных приложений. Несмотря на то, что WWW давно превратилась в неотъемлемую часть жизни многих людей, данная информационная среда представляет значительные сложности для людей с ограниченными физическими возможностями. В частности, люди с нарушениями слуха и зрения едва ли имеют возможность полноценного доступа к сайтам, напичканным мультимедийными "штучками". В по- пытке исправить подобное положение дел W3C запустил Инициативу обеспечения доступности WWW (Web Accessibility Initiative, WAI), представляющую руководства по повышению доступности сайтов людям с ограниченными физическими возможностями. В главе 21 рассматриваются эти рекомендации, а также раз- личные продукты и службы, предназначенные для совершенствования возможностей просмотра сайтов сети людьми с физическими недостатками. Например, в главе представлены VoiceXML и CallXML — две тех- нологии на базе XML для повышения доступности Web-содержимого для людей с нарушениями зрения. □ Поразрядные операции. Компьютеры обрабатывают данные в форме двоичных знаков или битов, которые могут принимать значения 1 или 0. Компьютерная схематика выполняет различные операции с битами, на- пример, определение значения бита, задание значения бита и обращение бита (с 1 на 0 и с 0 на 1). Операци- онные системы, оборудование для тестирования, сетевые программные средства и многие другие типы про- граммного обеспечения требуют, чтобы программы взаимодействовали "непосредственно с аппаратными средствами" с помощью операций с разрядами. В приложении 15 рассматриваются возможности манипуля- ций битами, предоставляемые .NET Framework. Педагогический подход В данной книге содержится множество примеров, протестированных на системах Windows 2000 и Windows ХР. Особое внимание уделено принципам корректного проектирования программных продуктов и их удобочитае- мости. Авторы могут считать себя наставниками в области сугубо практических аспектов, изучаемых на техни- ческих курсах во всем мире. Мы избегаем использования "туманных" терминов и синтаксических специфика- ций в пользу обучения на примерах, и текст демонстрирует качественный педагогический подход. Обучающие принципы LIVE-CODE™ В книге представлено множество примеров LIVE-CODE™. Данный стиль иллюстрирует способ обучения, изло- жения материалов о программировании и является основным аспектом мультимедийных "кибер-классов” и учебных курсов на базе Web. Каждая новая концепция представлена в контексте законченного, действующего примера, за которым следует одно или несколько изображений диалоговых окон с входными или выходными данными программы. Авторы называют данный метод изложения и обучения "обучающими принципами LIVE- CODE' При обучении языкам программирования авторы используют эти языки программирования. Ознаком- ление с примерами в тексте очень похоже на их прогон на компьютере. Читатели имеют возможность загрузки всех примеров кодов книги с сайта www.deitel.com в ссылке Downloads/Resources. В других ссылках представ- лены списки опечаток и ответы на наиболее часто задаваемые вопросы. Доступ к World Wide Web Все исходные коды примеров книги доступны для загрузки со следующих Web-сайтов: www.deitel.com и www.prenhall.com/deitel.
Предисловие 21 Регистрация на сайтах не занимает много времени, и загрузка предоставляется бесплатно. Рекомендуется загру- зить все примеры с последующим запуском каждой программы по мере ознакомления с материалами книги. В примеры можно вносить изменения и сразу же видеть их результаты: это прекрасный способ совершенство- вания навыков программирования. Все рекомендации по запуску примеров предполагают, что пользователь работает с системой Windows 2000 или Windows ХР, а также Internet Information Services (IIS). На сайте также представлены дополнительные инструкции по настройке IIS и других программных средств вместе с примерами. Примечание_____________________________________________________________________________ Данные материалы защищены авторским правом. Ими можно пользоваться бесплатно в целях обучения, но для воспроизведения или переиздания любой части необходимы явные разрешения издательства Prentice Hall и авторов. Visual Studio .NET принадлежит к семейству программных продуктов, доступных для покупки и загрузки с Web-сайтов корпорации Microsoft. Пакет Visual Studio .NET, включающий С#, поставляется в четырех разных версиях: академической (учебной), для профессионалов, для разработчиков предприятий, а также для создания архитектуры предприятий. Версия Visual Studio .NET Academic, помимо функций версии для профессионалов, включает функции и материалы, предназначенные для студентов и преподавателей (например, приложение Assignment Manager (мастер назначений), с помощью которого составляется документация по подаче назначе- ний; Application Publishing Tool — средство уведомления о назначениях, примеры кодов и т. д.). Корпорация Microsoft также предлагает автономные продукты (Visual C# .NET Standard, Visual C++ .NET Standard и Visual Basic .NET Standard) для различных языков .NET. Каждый продукт представляет интегриро- ванную среду разработки (подобную Visual Studio .NET), а также компилятор. Описания и информация по зака- зам размещены на сайте msdn.microsoft.com/vstudio/howtobuy. Цитаты В начале глав представлены разные цитаты (эпиграфы). Некоторые из них — юмористические, другие — фи- лософские, а некоторые предлагают оригинальные варианты понимания обсуждаемых аспектов. Авторы книги надеются, что читателям понравится идея сопоставления смысла цитат с содержанием глав. Возможно, многие из высказываний знаменитых людей потребуют "второй попытки” осмысления после ознакомления с главами. Темы Каждая глава открывается кратким изложением ее тем, информирующим читателей о рассматриваемых вопро- сах и дающим возможность определить факт их достижения после ознакомления с материалами главы. Вспомогательные элементы □ Приблизительно 26 000 строк кода в 230 примерах программ (с выходными данными). Все рассматри- ваемые в книги функции C# представлены в контексте полностью подготовленных рабочих программ. Их размеры варьируются от нескольких до сотен строк кода. Примеры программных приложений имеются на сайте www.deitel.com. □ 665 иллюстраций/рисунков. В книге представлено большое количество диаграмм, таблиц, графических рисунков и выходных данных приложений. □ 402 совета по технологии программирования. Авторы включили в книгу многочисленные советы и реко- мендации, помогающие читателям обратить особое внимание на важные аспекты разработки программ. Многие из них выделены заголовками "О хорошем стиле программирования", "Распространенные ошибки программирования", "Совет по тестированию и отладке", "Совет по повышению производительности", "Со- вет по повышению переносимости", "Замечание по технологии программирования" и "Замечание о «впечат- лениях и ощущениях»". Данные советы и инструкции являются лучшими из подобранных авторами с много- летним опытом программирования и преподавания. Одна из наших клиенток— специалист-математик — сказала, что подобный подход напомнил ей выделение аксиом, теорем и следствий в учебнике по математи- ке; этим закладывается основа создания надежного программного обеспечения. □ 52 совета о хорошем стиле программирования. Советы о хорошем стиле программирования — это реко- мендации, обращающие внимание разработчиков на методики создания более понятных, удобочитаемых программ, и упрощающие их сопровождение. □ 131 распространенная ошибка программирования. Разработчики, изучающие тот или иной язык про- граммирования, безусловно, часто делают разные ошибки. Выделение и описание распространенных оши- бок программирования снижает вероятность того, что читатели также будут их допускать.
22 Предисловие □ 33 совета по тестированию и отладке. При составлении первого совета такого "типа" авторы предполага- ли, что в них будут содержаться комментарии исключительно по выявлению и исправлению ошибок в про- граммах. В действительности, многие из рекомендаций описывают аспекты С#, прежде всего, предотвра- щающие появление ошибок; при этом процессы тестирования и отладки значительно упрощаются. □ 46 советов по повышению производительности. Большинство разработчиков предпочитают, чтобы их программы ’'летали". В этой связи авторы включили в книгу 46 советов по повышению производительно- сти, подчеркивающих возможности повышения эффективности программ: их более оперативное исполне- ние или минимизация объемов занимаемой памяти. □ 15 советов по повышению переносимости. Советы данного типа помогают разработчикам компилировать переносимые коды, а также обеспечивают более глубокое понимание принципов повышения портативности при написании программ на С#. □ 96 замечаний по технологии программирования. Парадигма объектно-ориентированного программиро- вания обуславливает необходимость полного пересмотра способов построения систем программного обес- печения. C# — это эффективный язык достижения качественной разработки программных средств. В заме- чаниях по технологии программирования особое внимание уделяется вопросам архитектуры и проектирова- ния, оказывающих влияние на построение крупномасштабных вычислительных систем. □ 25 замечаний о ’’впечатлениях и ощущениях". Замечания о "впечатлениях и ощущениях" представлены для особого выделения обозначений графического пользовательского интерфейса. Они помогают разработ- чикам в проектировании привлекательных, дружественных пользователям интерфейсов, соответствующих промышленным стандартам. О Предметный указатель. В конце книги представлен расширенный указатель. Данный ресурс помогает чи- тателям оперативно найти в книге нужный термин или концепцию по ключевому слову. Указатель особенно полезен практикующим программистам, пользующимся книгой качестве справочника. Резюме Каждая глава завершается резюме с описанием и закреплением основных изложенных концепций. Благодарности Одним из самых приятных моментов при написании книги является признание вклада многих соавторов, имена которых могут не появиться на обложке, но чьи напряженная работа, взаимодействие, дружба и понимание бы- ли решающими в процессе производства книги. Над проектом напряженно работали многие сотрудники Deitel & Associates, Inc. Ниже приведен список штат- ных служащих корпорации, внесших неоценимый вклад в подготовку книги к выходу в свет: Шон Е. Сантри (Sean Е. Santry), Мэтью Р. Ковалевски (Matthew R. Kowalewski), Джонатан Гэдзик (Johnathan Gadzik), Кайл Ло- мели (Kyle Lomeli), Лорен Триз (Lauren Trees), Рашми Джейапракаш (Rashmi Jayaprakash), Лора Трейбик (Laura Treibik), Бетси ДюВальдт (Betsy DuWaldt), Барбара Дейтел (Barbara Deitel), Эбби Дейтел (Abbey Deitel). Авторы также выражают благодарность участникам Программы для интернов корпорации Deitel & Associates, Inc., за вклад в книгу1: Джеффри Хэмму (Jeffrey Hamm) — Северо-Восточный университет, Калиду Азаду (Kalid Azad) — Принстонский университет, Кристоферу Касса (Christopher Cassa) — Массачусетский технологический институт, Дэвиду Таттлу (David Tuttle) — Гарвардский университет, Тиаго Лукасу да Сильва (Thiago Lucas da Silva)— Северо-Восточный университет, Ори Шварцу (Ori Schwarz)— Бостонский университет, Элизабет Ро- кетт (Elizabeth Rockett) — Принстонский университет, Барбаре Штраус (Barbara Strauss) — Брандайс, Кристине Карни (Christina Carney) — Фрэмингэм Стейт, Брайану Фостеру (Brian Foster) — Северо-Восточный универси- тет, Майку Прешману (Mike Preshman) — Северо-Восточный университет. Авторам посчастливилось работать с талантливой и преданной своему делу командой’профессионалов изда- тельства Prentice Hall. Особо хочется отметить усилия редакторов— Петры Ректер (Petra Recter) и Карен 1 Программа Для интернов корпорации Deitel & Associates, Inc. предлагает ограниченное количество оплачиваемых должностей студентам колледжей в районе Бостона, специализирующихся в вычислительной технике, информационных технологиях, марке- тинге, менеджменте и английском языке. Студенты работают в штаб-квартире корпорации в Мейнарде, штат Массачусетс, полный рабочий день летом и неполный рабочий день во время учебного года. Также предлагаются штатные должности интернов для сту- дентов, заинтересованных в замещении учебного семестра производственной практикой. Штатные должности регулярно предла- гаются для выпускников колледжей. За подробностями обращаться к Эбби Дейтел по адресу deitel@deitel.com и на сайте www.deitel.com.
Предисловие 23 McLean (Karen McLean) издательства Prentice Hall и PH/PTR соответственно, а также Майкла Руэля (Michael Ruel) — руководителя процессов рецензирования изданий серии "Developer" Deitel™. Авторы выражают благо- дарность Марку Л. Таубу (Mark L. Taub), главному редактору изданий по вычислительной технике издательств PH/PTR, за формирование концепции этой серии. Им обеспечена необходимая среда и ресурсы для создания многих книг серии. Отдельное спасибо Марсии Хортон (Marcia Horton), главному редактору подразделения вы- числительной техники издательства Prentice Hall, которая в течение 18 лет являлась нашим наставником и дру- гом. Она отвечала за все аспекты изданий корпорации Deitel во всех подразделениях Pearson, включая Prentice Hall, PH/PTR и Pearson International. Дизайн обложки разработан Лорой Трейбик, директором подразделения мультимедиа корпорации Deitel & Associates, Inc. Тамара Ньюман (Tamara Newman) (smart_art@earthlink.net) завершила разработку обложки и графических пиктограмм "советов и замечаний"1. Хочется отметить участие наших рецензентов первой и второй "очередей". Придерживаясь жестких сроков, они тщательно проштудировали текст и программы, предложив множество поправок в плане точности и насыщен- ности излагаемых материалов. Авторы искренне благодарны им за внеурочное время, выделенное для обеспе- чения качества, безукоризненности и своевременного выхода книги в свет. Авторы благодарны всем за комментарии, критические замечания, исправления и предложения по совершенст- вованию книги. Просьба направлять всю корреспонденцию по адресу deitel@deitel.com. Ответ последует незамедлительно. Ну, вот и все. Добро пожаловать в мир программирования на С#. Надеемся, что читателям понравится пред- ставленный взгляд на основной язык .NET. Удачи! Профессор Харви М. Дейтел Пол Дж. Дейтел Джефф Листфилд Тэм Р. Нието Шерил X. Йегер Марина Златкина О корпорации Deitel & Associates, Inc. Deitel & Associates, Inc. — всемирно признанная педагогическая и технологическая организация, специализи- рующаяся в областях программных технологий Интернета и Всемирной паутины, электронного бизнеса и ком- мерции, объектных технологий, а также в области обучения работе с различными языками программирования. Компания занимается организацией учебных курсов по программированию в Интернете и Всемирной паутины, беспроводному программированию, разработке Web-служб (как на языке Java, так и на языках .NET), объект- ным технологиям, а также в областях различных языков программирования и платформ: Visual Basic .NET, С#, Visual C++ .NET, Java, Advanced Java, C.-C++, XML, Perl, Python. ASP.NET, ADO.NET и т. д. Корпорация Deitel & Associates, Inc. основана проф. Харви М. Дейтелом и Полом Дж. Дейтелом — всемирно известными авторами книг по вычислительным технологиям. В число клиентов корпорации входят многие известные компании- разработчики вычислительной техники и программных продуктов, государственные агентства, филиалы обо- ронных и коммерческих организаций. За 25-летнюю историю сотрудничества с издательским домом Prentice Hall корпорация Deitel & Associates, Inc. выпускала ведущие учебники по программированию, пособия для про- фессионалов, "кибер-классы" на базе мультимедийных CD-ROM, полные курсы обучения, электронные книги, дискуссии, учебные курсы на базе Web, а также системы управления электронным содержимым курсов. Свя- заться с корпорацией Deitel & Associates, Inc. и с ее авторами можно по адресу: deitel@deitel.com. Более подробная информация в корпорации, ее изданиях и интерактивных учебных планах представлена на сайте www.deitel.com. Физические лица, заинтересованные в приобретении книг корпорации Deitel, "кибер-классов", полных учебных курсов и курсов на базе Web, могут обратиться в местные книжные магазины, в интернет-магазины, а также на сайты □ www.deitel.com □ www.prenhall.com/deitel □ www.InformIT.com/deitel □ www.lnformlt.com/cyberclassrooms 1 Это, разумеется, касается оригинального издания книги. — Ред.
24 Предисловие Оптовые заказы со стороны компаний и учебных заведений размещаются непосредственно в издательстве Prentice Hall. Для ознакомления с программой изданий Deitel рекомендуется зарегистрироваться на сайте www.deitel.com/newsletter/subscribe.html. Консорциум W3C Корпорация Deitel & Associates. Inc. является членом Консорциума W3C, основанного в 1994 году "для разра- ботки общих протоколов развития Всемирной паутины". Будучи членом W3C, корпорация Deitel & Associates, Inc. имеет место в Консультативном комитете (представитель— главный администратор и технический руково- дитель Лол Дейтел). Члены Консультативного комитета оказывают помощь в разработке "стратегического на- правления" W3C посредством совещаний, проводимых в разных точках мира. Организации-члены помогают в разработке стандартных рекомендаций по Web-технологиям (таким как XHTML, XML и многим другим) путем участия в деятельности W3C и подчиненных ему групп. Членство в W3C доступно для крупных компаний и организаций. Информация по вступлению в члены Консорциума размещена на сайте www.w3.org/Consortium /Prospectus/Joining.
ГЛАВА 1 Введение в архитектуру .NET и язык C# Поначалу все вещи выглядят наилучшими. Блез Паскаль Высокие мысли должны выражаться высоким языком. Аристофан Мы размениваемся на мелочи... Не стоит ничего усложнять, не стоит. Генри Дэйвид Торо < Перед началом дела спланируй его... Марк Туллии Цицерон Взирай благосклонно на смелое начало... Виргинии Мне кажется, я начинаю в этом что-то понимать. Огюст Ренуар Темы данной главы: □ изучение истории Интернета и Всемирной паутины; П ознакомление с Консорциумом Всемирной паутины (Word Wide Web Consortium, W3C); □ язык XML и важность данной технологии; □ влияние объектно-ориентированной технологии на разработку программного обеспечения; □ изучение инициативы Microsoft .NET; □ обзор других глав книги. 1.1. Введение Добро пожаловать в С#! Авторы провели огромную работу по обеспечению программистов наиболее точным и исчерпывающим пособием по языку C# и платформе .NET. Надеемся, что данная книга поможет читателям приобрести опыт обучения в информативной, занимательной и стимулирующей форме. В этой главе предпри- нят краткий экскурс в историю развития Интернета и Всемирной паутины и представлено введение в концеп- цию Microsoft .NET. Завершается глава общим обзором всего изложенного в книге материала. 1.2. История Интернета и WWW В конце 60-х годов прошлого века на конференции ARPA (Advanced Research Projects Agency of the Department of Defense, управление перспективного планирования научно-исследовательских работ при Министерстве обо- роны) в университете Иллинойса Урбана-Щампань (Urbana-Champaign) впервые был представлен план объеди- нения в общую сеть основных компьютерных систем около десятка университетов и исследовательских органи- заций, финансируемых ARPA. Планировалось объединить компьютеры линиями связи, работающие с невероят- ной по тем временам скоростью в 56 Кбит/с (1 Кбит/с = 1024 бит/с), в то время как большинство людей (из тех, немногих, кто имел доступ к сетевым технологиям) подключались к компьютерам по телефонным линиям со скоростью ПО бит/с. Для управления расчетами, имевшими отношение к исследованиям компьютерной графи- ки, ученые Гарвардского университета говорили о подключении к "суперкомпьютеру" Univac 1108, который располагался на другом конце страны в университете Юты. Рассматривалось множество других весьма привле- кательных возможностей. Академические исследования уже были готовы сделать большой шаг вперед. Вскоре после этой конференции ARPA начало разработки того, что стало называться ARPAnet, — "дедушки" современ- ного Интернета.
26 Глава 1 Все пошло несколько не так, как предполагалось по первоначальному плану. Несмотря на то, что проект ARPA- net позволил-таки исследователям объединить их компьютеры в сеть, основным преимуществом стала возмож- х ность легкой и оперативной связи через то, что впоследствии получило название электронной почты (e-mail). Оно же, — это преимущество, — полностью себя оправдывает в современном Интернете, где сотни миллионов людей во всем мире имеют доступ к электронной почте, мгновенному обмену сообщениями и средствам связи, максимально упрощающим процесс обмена файлами. Сеть была спроектирована для функционирования без централизованного контроля. Это означало следующее: если в какой-либо части сети происходил сбой, то планировалось, что оставшаяся часть по-прежнему будет спо- собна передавать пакеты данных от отправителей адресатам по альтернативным путям. Протокол (другими словами, набор правил) для передачи информации по ARPAnet стал называться протоколом управления передачей (Transmission Control Protocol, TCP). Протокол TCP обеспечивал передачу сообщений непосредственно от отправителя адресату, а также доставку этих сообщений неповрежденными. Параллельно с ранним развитием Интернета компании по всему миру стали организовывать собственные кор- поративные сети для упрощения взаимодействия внутри и между разными компаниями. Постепенно появилось множество сетевых аппаратных и программных средств. Одной из сложностей была необходимость заставить "разношерстные" продукты взаимодействовать друг с другом. В ARPA эта проблема была решена после разра- ботки протокола Интернета (Internet Protocol, IP), создавшего подлинную "сеть сетей", ныне существующую архитектуру Интернета. Комбинированный набор протоколов в настоящее время обозначается широко извест- ной аббревиатурой TCP/IP. Изначально использование Интернета ограничивалось университетами и исследовательскими институтами; позже технологию признали вооруженные силы. С течением времени правительство приняло решение обеспе- чить доступ в Интернет коммерческим организациям. Это решение вызвало недовольство в исследовательских и военных кругах, потому что считалось, что с появлением в "сети" слишком большого количества пользовате- лей время ее реакции может значительно сократиться. На самом же деле произошло обратное. Руководители предприятий поняли, что при эффективном использова- нии Интернета они смогут упорядочить свои действия и предлагать клиентам новые услуги повышенного каче- ства. Компании начали тратить большие средства на разработку и совершенствование своего присутствия в Ин- тернете. Это породило жесткую конкуренцию в среде поставщиков услуг связи и производителей аппаратных и программных средств, которые старались максимально удовлетворить возросшие инфраструктурные требова- ния. В результате этого полоса пропускания (т. е. возможности передачи информации линий связи) радикально увеличилась, а затраты на аппаратные средства резко упали. Технология Интернета сыграла ведущую роль в экономическом росте последнего десятилетия, с которым столкнулись индустриализованные государства. World Wide Web (Всемирная паутина) дает пользователям компьютеров возможность размещения и просмотра документов, основанных на мультимедиа (т. е. документов с текстом, графикой, анимацией, аудио- и видеоин- формацией), практически по любой теме. Несмотря на то, что Интернет был создан более тридцати лет назад, служба WWW возникла сравнительно недавно. В 1989 году Тим Бернерс-Ли (Tim Berners-Lee) из CERN (Conseil Europeen pour la Recherche Nucleaire, Европейская организация по ядерным исследованиям) приступил к разработке технологии совместного использования информации посредством текстовых документов с гипер- ссылками. Базируя новый язык на хорошо себя зарекомендовавшем стандартном обобщенном языке разметки (Standard Generalized Markup Language, SGML) — стандарте обмена коммерческой информацией, Бернерс-Ли назвал свое изобретение гипертекстовым языком разметки (HyperText Markup Language, HTML). Он также написал протоколы связи для создания "скелета" новой системы гипертекстовой информации, названной World Wide Web. Несомненно, историки внесут Интернет и World Wide Web в список наиболее важных и значительных изобре- тений человечества. В прошлом большинство компьютерных приложений работало на "автономных" компью- терах (не соединенных между собой). Современные программные приложения можно написать для взаимодей- ствия с сотнями миллионов компьютеров во всем мире. Интернет и World Wide Web объединяют вычислитель- ную и коммуникационную технологии, что значительно ускоряет и упрощает работу. Они обеспечивают мгновенный и удобный доступ к информации очень большого количества людей, дают возможность отдельным людям и небольшим предприятиям заявить о себе всему миру, изменяют методы ведения бизнеса и личную жизнь. 1.3. Консорциум Всемирной паутины В октябре 1994 года Тим Бернерс-Ли основал организацию под названием Консорциум Всемирной паутины (World Wide Web Consortium, W3C), которая занималась разработкой непатентованных межоперационных тех- нологий для World Wide Web. Одной из основных целей W3C было обеспечение универсальной доступности сети, независимо от средств, языков или культур разных народов.
Введение в архитектуру .NETи язык C#27 W3C также представляет собой организацию стандартизации и состоит из трех ведущих узлов — Массачусет- ского технологического института (MIT), французского Национального института исследований в области ком- пьютерной обработки данных и автоматики и университета Кейо в Японии — и порядка 400 членов, в число которых входит компания Deitel & Associates, Inc. Члены обеспечивают основное финансирование W3C и помо- гают в разработке стратегических направлений Консорциума. Более подробная информация о W3C имеется на Web-сайте www.w3.org. Технологии Web, стандартизированные W3C, называются Рекомендациями. В число текущих Рекомендаций W3C входят расширяемый гипертекстовый язык разметки (extensible HyperText Markup Language, XHTML), предназначенный для разметки содержимого Web, каскадные таблицы стилей (Cascading Style Sheets, CSS) для описания форматирования содержимого и язык XML (extensible Markup Language) для создания языков размет- ки. Сами по себе Рекомендации являются не программными продуктами, а документами, определяющими роль, синтаксис и правила технологии. До того как превратиться в Рекомендацию W3C, документ проходит три ос- новные фазы: рабочего проекта (по своему названию обозначающей разрабатываемый проект); конкурсной рекомендации (устойчивой версии документа, который можно начать реализовывать), предложенной рекомен- дации (конкурсной рекомендации, считающейся готовой, т. е. той, которая реализована и протестирована за определенный период времени, и уже рассматривается как готовая по состоянию для Рекомендации W3C). Под- робную информацию о Рекомендациях W3C см. в "6.2 The W3C Recommendation track" ("6.2 Рекомендации W3C") на Web-сайте www.w3.org/Consortium/Process/Process-19991111/process.html#RecsCR. 1.4. Язык XML По мере роста популярности Web стали очевидными ограничения HTML. Факт недостатка расширяемости HTML (возможности изменения или добавления новых функций) расстраивал разработчиков, а довольно амби- циозное название языка быстро развивало ошибочность HTML. Как реакцию на эти проблемы W3C добавил HTML ограниченную расширяемость. Впрочем, такое решение было временным: очевидной стала необходи- мость стандартизированного, полностью расширяемого и строгого по структуре языка. Как результат W3C был разработан язык XML. Последний объединяет в себе мощь и расширяемость "праязыка" — стандартного обоб- щенного языка разметки (SGML) с его простотой, так необходимой Web-сообществу. Независимость данных, отделение содержимого от его представления — основные характеристики языка XML. Поскольку документ XML описывает данные, вероятно, что такой документ может быть обработан любым про- граммным приложением. Признав это, разработчики программных средств интегрируют XML в свои приложе- ния для повышения функциональности и межфункциональности Web. Гибкость и мощность XML делают его идеальным для систем среднего яруса и систем "клиент/сервер", предназначенных для взаимодействия с широ- ким спектром клиентов. Теперь компьютеры-клиенты могут осуществлять большую часть вычислительной ра- боты, ранее возлагаемой на компьютеры-серверы, потому что теперь любое приложение, обрабатывающее текст, может манипулировать семантической и структурной информацией языка XML. При этом снижается на- грузка на серверы, в результате чего Web работает быстрее и эффективнее XML не ограничивается только Web-приложениями. Этот язык теперь все больше применяется в базах данных: структура документа XML позволяет легко интегрировать его в приложения баз данных. Поскольку программ- ные приложения становятся более Web-активизированными, то, похоже, в очень скором времени XML превра- тится в универсальную технологию представления данных. Все приложения, в которых используется XML, смо- гут взаимодействовать с другими приложениями, при условии, что они смогут понимать разметку или словарь другого XML. Простой протокол доступа к объектам (Simple Object Access Protocol, SOAP) — технология распределения дан- ных (отмеченных как XML) в Интернете. Изначально разработанный Microsoft и Develop-Mentor, SOAP — это рабочий проект W3C, предоставляющий структуру выражения семантики программного приложения, кодиро- вания и упаковки данных. Microsoft .NET (см. разд. 1.6 и 1.7) использует XML и SOAP для разметки и передачи данных по Интернету. XML и SOAP лежат в основе .NET: они обеспечивают межфункциональное взаимодейст- вие (т. е быстрое взаимодействие друг с другом) компонентов программного обеспечения. SOAP поддержива- ется многими платформами благодаря его основе — XML и HTTP. XML рассматривается в главе 15, a SOAP — в главе 18. 1.5. Ключевое направление развития программных средств: объектно-ориентированная технология Что такое объекты и в чем их особенность? Объектно-ориентированная технология — это схема упаковки, уп- рощающая создание значимых блоков программного обеспечения. Это крупные блоки, направленные на кон- кретные прикладные области. Это объекты данных, времени, оплаты чеков, счетов-фактур, аудиообъекты, видеообъекты, объекты файлов, записи и т. д. На самом деле, практически любое существительное можно пред-
28 Глава 1 ставить в виде программного объекта. Объекты обладают свойствами (или атрибутами, такими как цвет, раз- мер и вес) и выполняют действия (т. е. поведения, такие как передвижение, сон, рисование). Классы представ- ляют группы родственных объектов. Например, все автомобили принадлежат к классу ’’автомобиль", даже не- смотря на то, что все автомобили могут различаться маркой, моделью, цветом и комплектацией Класс опреде- ляет общий формат входящих в него объектов; свойства и действия, доступные объекту, зависят от его класса. Мы живем в мире объектов. Оглянитесь: нас окружают автомобили, самолеты, люди, животные, здания, свето- форы, лифты и т. д. До появления объектно-ориентированных языков так называемые языки процедурного про- граммирования (Fortran, Pascal, BASIC и С) были направлены на действия (глаголы), а не на вещи или объекты (существительные). Мы живем в мире объектов, однако более ранние языки программирования заставляли раз- работчиков программировать в основном "с глаголами". Такое смещение парадигмы усложнило процесс напи- сания программ. Впрочем, с возникновением объектно-ориентированных языков, например, C++, Java и C# появилась возможность программирования объектно-ориентированным способом, отражающим путь, по кото- рому программисты постигают мир. Результатом этого процесса, кажущегося более естественным, нежели про- цедурное программирование, стал значительный выигрыш в производительности. Одной из ключевых проблем процедурного программирования является то, что создаваемые программные еди- ницы не отражают эффективно объектов реального мира и поэтому их сложно использовать многократно. Часто для разных проектов программисты пишут и переписывают сходные программы. При этом тратится масса вре- мени и денег, поскольку в очередной раз программистам приходится "изобретать колесо". В объектно- ориентированной технологии правильно спроектированные программные единицы (называемые объектами) можно использовать в последующих проектах. Применение библиотек многократной компоновки может сокра- тить объем усилий, необходимых на реализацию систем определенных типов (по сравнению с усилиями, кото- рые требуются на повторное воссоздание функций в новых проектах). Программисты, работающие с С#, поль- зуются библиотекой классов .NET Framework (обычно называется FCL — Framework Classes Library), описы- ваемой в разд. 1.8. Некоторые организации признают/что возможность многократного использования программных средств, на самом деле, не является основным преимуществом объектно-ориентированного программирования. Дело в том, что объектно-ориентированное программирование создает более понятные программные средства, потому что оно лучше организовано и требует меньше технической поддержки. Порядка 80% затрат на программное обес- печение не связано с изначальными усилиями на его разработку, а относятся к непрерывному его совершенст- вованию и технической поддержке на протяжении всего жизненного цикла. Объектное ориентирование позво- ляет программистам абстрагироваться от деталей и полностью сосредоточиться на "общем плане". Вместо того чтобы озаботиться второстепенными подробностями, программист делает основной упор на поведении и взаи- модействии объектов. В дорожной карте, на которой указано каждое дерево, дом или проезд, разобраться до- вольно трудно (если вообще возможно). Если убрать все подробности и оставить только основную информацию (расположение дорог), то карта сразу станет "прозрачной" для понимания. Точно так же программу, разделен- ную по объектам, проще понять, модифицировать и обновить, потому что множество несущественных деталей скрыто. Понятно, что, как минимум, на ближайшее десятилетие объектно-ориентированное программирование останется ключевой методологией программирования. Замечание по технологии программирования__________________________________________________ При создании программ пользуйтесь принципом компоновки из стандартных блоков. Использование в новых проектах существующих блоков программ облегчает работу программиста. Данный процесс называется повтор- ным использованием программ и является ключевым в объектно-ориентированном программировании. Примечание________________________________________________________________________________ На протяжении книги авторы предполагают не раз вводить замечания по технологии программирования для объяснения концепций и идей, совершенствующих общую архитектуру и влияющих на качество программных систем, в особенности крупных. Также будут выделяться советы о хорошем стиле программирования (методы, с помощью которых программисты могут создавать более понятные, простые для тестирования и отладки про- граммы), распространенные ошибки программирования (описание проблем, во избежание наиболее распро- страненных ошибок), советы по повышению производительности (методы написания быстродействующих про- грамм, использующих меньший объем памяти), советы по повышению переносимости (методы написания про- грамм, которые могут выполняться с минимальной модификацией на разных компьютерах), советы по тестированию и отладке (методы, помогающие программистам исправлять ошибки и — что более важно, — изначально писать приложения без ошибок), а также замечания о "впечатлениях и ощущениях" (методики, по- могающие программистам проектировать внешний вид графических пользовательских интерфейсов с учетом простоты их использования) Многие из излагаемых методик представляют собой обычные руководства; общий стиль разработки будет, несомненно, зависеть от предпочтений самого программиста. Преимущество написания собственного кода заключается в том, что программист точно знает, как он будет ра- ботать, сможет просматривать его, модифицировать и совершенствовать. Преимуществом является экономия времени и усилий, затрачиваемых на проектирование, разработку и тестирование нового кода.
Введение в архитектуру .NET и язык C#29 Совет по повышению производительности________________________________________________ Повторное использование компонентов кода вместо написания новых версий может повысить производитель- ность программы, потому что, как правило, данные компоненты пишутся для эффективного выполнения. Замечание по технологии программирования___________________________________ Расширенный набор библиотек классов программных компонентов многократного использования доступны в Ин- тернете и World Wide Web; многие из них бесплатны. 1.6. Введение в Microsoft .NET В июне 2000 года корпорация Microsoft объявила о своей новой инициативе — .NET. Платформа NET обеспе- чивает значительные усовершенствования более ранних платформ разработки. .NET предлагает новую модель разработки программного обеспечения, обеспечивающую взаимодействие между собой программных приложе- ний, созданных на разных языках программирования. Данная платформа также позволяет разработчикам созда- вать программные приложения на базе Web, которые можно размещать на многих устройствах, включая бес- проводные телефоны и настольные рабочие станции. Инициатива .NET от Microsoft представляет собой новое видение Интернета и Web в том, что касается разра- ботки, инженерного проектирования и использования программных средств. Одним из ключевых аспектов стратегии .NET является его независимость от конкретного языка или платформы. Вместо требования от про- граммистов использования одного языка программирования, они могут создавать приложения .NET с помощью любой комбинации языков, совместимых с .NET1: □ APL; □ Fortran; □ Oz; □ Smalltalk; □ С#; □ Haskell; □ Pascal; □ Standard ML; □ COBOL; □ J#; □ Perl; □ Visual Basic .NET; □ Component Pascal; □ JScript .NET; □ Python; □ Visual C++ .NET. □ Curriculum; □ Mercury; □ RPG; □ Eiffel; □ Oberon; □ Scheme; Программисты могут внести свой вклад в один и тот же проект путем написания кодов на языках .NET (таких как С#, Visual C++ .NET, Visual Basic .NET и многих других), с которыми они знакомы лучше всего. Частью инициативы является технология активных серверных страниц ASP.NET (Active Server Pages) от Microsoft, по- зволяющая создавать программные приложения для Web. С помощью технологии ASP.NET можно оперативно создавать приложения на базе Web и с базами данных путем активного использования мощность объектно- ориентированных языков .NET. Разработчики могут использовать ASP.NET для создания устойчивых и надеж- ных Web-приложений, учитывая преимущества оптимизаций ASP.NET для повышения производительности проведения испытаний и обеспечения защиты. Ключевым компонентом архитектуры .NET являются Web-службы (или Web-сервисы) — программные прило- жения, обеспечивающие (т. е. делающие доступными) функции для клиентов через Интернет. Клиентские и дру- гие приложения применяют Web-службы в качестве стандартных блоков повторного использования. Одним из примеров Web-службы может быть система бронирования автомобилей от компании Dollar Rent а Саг, извест- ная под названием Quick Keys1 2. Целью компании Dollar было обеспечение функциональностью своей системы, построенной на базе универсальных компьютеров, другие компании, которые могли бы предоставлять своим заказчикам возможно аренды автомобилей. Для своих деловых партнеров Dollar могла создавать индивидуаль- ные, патентованные решения. Для многократного предоставления функциональности Dollar приняла решение о реализации решения с помощью Web-служб. Результаты оказались феноменальными. С помощью вновь соз- - данной Web-службы авиакомпании и отели могут арендовать и бронировать автомобили для своих клиентов. Деловым партнерам компании Dollar не обязательно работать на той же платформе, а также у них нет необхо- димости вникать в механизм реализации данной системы бронирования. Повторная реализация программного приложения в виде Web-службы принесла компании Dollar миллионы долларов прибыли, а также тысячи новых клиентов. 1 Данные с Web-сайта Microsoft — www.microsoft.com. 2 По материалам корпорации Microsoft. "Dollar Rent а Саг вдыхает новую жизнь в традиционные системы с помощью программных средств, подключенных к .NET". 15 марта 2002 года. www.microsoft.com/business/casestudies/b2c/doHarrentacar.asp.
30 Глава 1 Web-службы раздвигают рамки концепции повторного использования программных средств, давая программи- стам возможность сосредоточиться на своей специализации без необходимости реализации каждого компонента каждого приложения. Компании могут просто приобретать Web-службы и посвящать время и силы разработке собственных продуктов. Получило популярность визуальное программирование (см. главу 2), потому что оно дает возможность создавать приложения без особых усилий с помощью таких заложенных компонентов, как кнопки, текстовые поля и надписи. Аналогичным же образом программисты могут создавать приложения с по- мощью Web-служб для баз данных, защиты, аутентификации, сохранения данных и языковых переводов без необходимости разбираться во внутреннем устройстве этих компонентов. При связывании (объединении) своих продуктов через Web-службы пользователь приобретает новый опыт. На- пример, одно программное приложение может управлять оплатой счетов, взиманием налогов и инвестициями с помощью Web-служб, предоставляемых разными компаниями. Любой коммерсант может приобрести через сеть Web-службы для осуществления платежей по кредитной карте, для аутентификации пользователей, сетевой за- щиты и базам данных инвентаризации с целью создания собственного Web-сайта электронной торговли. Ключевыми концепциями в этом смысле являются XML и SOAP, обеспечивающие взаимосвязь и взаимодейст- вие Web-служб. Язык XML позволяет осмыслить данные, а протокол SOAP обеспечивает взаимодействие Web- служб. Язык XML и протокол SOAP выполняют роль "клея”, связующего различные Web-службы для образова- ния программных приложений. Универсальный доступ к данным — еще одна существенная концепция .NET. При наличии двух копий одного файла (например, на личном компьютере и на компьютере компании) более старая версия должна постоянно обновляться; этот процесс называется синхронизацией файлов. Если файлы разные, то это означает, что они не синхронизированы, и такая ситуация может привести к возникновению ошибок. С помощью .NET данные могут быть размещены в одном центральном местоположении, а не на раздельных системах. Любое устройство, под- ключенное к Интернету, может осуществить доступ к данным (разумеется, при строгом контроле), которые за- тем будут соответствующим образом отформатированы для использования или отображения на устройстве. Та- ким образом, один и тот же документ можно просмотреть и отредактировать на настольном компьютере, на PDA ("карманный" компьютер, КПК), на сотовом телефоне или на любом другом устройстве. Пользователям не придется синхронизировать информацию, потому что она будет полностью обновлена на центральном местопо- ложении. .NET — сложное и комплексное предприятие. В данной книге рассматриваются различные аспекты .NET. До- полнительная информация доступна на сайте www.microsoft.com/net. 1.7. ЯзыкС# Язык программирования С#, разработанный в корпорации Microsoft рабочей группой под руководством Андер- са Хейльсберга (Anders Hejlsberg) и Скотта Вилтамата (Scott Wiltamuth), предназначен специально для плат- формы .NET в качестве языка, позволяющего программистам перейти на технологию .NET. Этот переход (ми- грация) прост для большинства программистов, потому что корнями своими СЦ уходит в С, C++ и Java с адап- тацией лучших функций каждого языка и добавлением собственных. Спецификация языка C# доступна на Web- сайте msdn.microsoft.com/vstudio/techinfo/articles/upgrade/Csharpdownload.asp. C# — это событийно-управляемый, полностью объектно-ориентированный язык визуального программирова- ния, в котором программы создаются с помощью интегрированной среды разработки (Integrated Development Environment, IDE). В этой среде программист может спокойно писать, запускать, тестировать и отлаживать про- граммы, написанные на С#, экономив массу времени на создании рабочей программы без помощи IDE. Процесс оперативного создания программных приложений с помощью IDE называется быстрой разработкой приложе- ний (Rapid Application Development, RAD). 1.8. .NET Framework и среда Common Language Runtime .NET Framework— ядро концепции .NET. Данная структура управляет программными приложениями и запус- кает их, содержит библиотеку классов .NET Framework (FCL), обеспечивает защиту и множество других воз- можностей программирования. Подробности .NET Framework имеются в спецификации универсального языка (Common Language Specification, CLS), содержащей информацию о сохранении объектов и проч. Спецификация CLS была представлена на стандартизацию в ЕСМА (Европейская Ассоциация производителей вычислительной техники). При этом независимые производители программного обеспечения могут создавать .NET Framework для других платформ. .NET Framework существует только для платформы Windows, но находится в стадии раз- работки для других платформ, например, для совместно используемого источника CLI (Common Language Infrastructure, инфраструктура универсального языка) от Microsoft. Совместно используемый источник CLI —
Введение в архитектуру .NET и язык C#31 это архив исходного кода, предоставляющий подмножество Microsoft .NET Framework как для операционной системы’ Windows ХР, так и для FreeBSD1 2. Более подробная информация о совместно используемом источнике CLI имеется по адресу msdn.microsof.com/library/default.asp?url=/Iibrary/en-us/dndotnet/html/mssharsource- cli.asp. Среда Common Language Runtime (CLR, общеязыковая среда выполнения) — еще одна центральная часть .NET Framework; она выполняет программы, написанные на С#. Программы компилируются в машинные команды за два этапа. Сначала программа компилируется в промежуточный язык Microsoft (Microsoft Intermediate Language, MS1L), определяющий команды для CLR. Код преобразуется в MSIL из других языков, и источники переплетаются с помощью CLR. Затем другой компилятор в CLR компилирует MSIL в машинный код (для кон- кретной платформы) с созданием единого программного приложения. Зачем нужен второй этап преобразования из C# в MSIL вместо того, чтобы сразу осуществить компиляцию в машинный язык? Основными причинами является переносимость кода, переносимого из одной операционной системы на другую, взаимная функциональность между языками и такие особенности управления исполнением, как управление памятью и защита. Если для определенной платформы существует .NET Framework (и она установлена), тогда на этой платформе можно запустить любую программу .NET. Способность программы к исполнению (без модификации) на не- скольких платформах называется независимостью от платформы. Один раз написанный код можно использо- вать на другой машине без модификаций; при этом экономится и время, и деньги Помимо этого, программное обеспечение может охватить более широкую аудиторию; ранее компаниям приходилось решать, стоит ли затрат преобразование (иногда называемое переносом) программ под другие платформы. При наличии .NET перенос упрощается. .NET Framework также обеспечивает высокую степень возможности взаимодействия языков. Все программы, написанные на разных языках, компилируются в MSIL; разные части могут быть объединены для создания еди- ной унифицированной программы. MSIL позволяет .NET Framework быть независимой от языка, потому что MSIL не связан ни с каким языком программирования. Возможность взаимодействия языков предлагает много преимуществ компаниям-производителям программно- го обеспечения К примеру, разработчики на С#, Visual Basic .NET, и Visual C++ .NET могут работать совместно над одним и тем же проектом без необходимости изучения другого языка программирования: части кода легко кодируются в MSIL и связываются в единую программу. Кроме этого, .NET Framework может объединять в па- кеты уже существующие компоненты (т. е. компоненты, созданные с помощью инструментов, предшествовав- ших .NET) с компонентами .NET для совместной работы. Это позволяет компаниям многократно использовать код, на создание которого были потрачены годы, и интегрировать его с кодом .NET, который они пишут. Инте- грация критична, потому что компании не смогут с легкостью перейти к .NET с помощью существующих раз- работчиков и программных средств, если производительность снижается. Другим преимуществом .NET Framework являются функции исполнения и управления CLR. CLR управляет па- мятью, защитой и другими функциями, избавляя программиста от ответственности за выполнение этих задач. При работе с такими языками, как C++, программисты должны самостоятельно управлять памятью. Это приво- дит к проблемам, если память запрашивается, но никогда не освобождается: программы могут занять весь дос- тупный объем памяти, и приложения не смогут выполняться. Управлением памятью программы .NET Framework дает программистам возможность сосредоточиться на логике программы. .NET Framework также предоставляет программистам обширную библиотеку повторно используемых классов. Этой библиотекой, называемой библиотекой классов среды .NET (FCL), может пользоваться любой язык .NET В книге описывается разработка программных средств .NET с помощью C# и FCL. Стив Боллмер (Steve Ballmer), председатель правления Microsoft заявил в мае 2001 года, что Microsoft "делает ставку" на .NET. По- добное драматическое признание указывает на светлое будущее C# и сообщество его приверженцев. 1.9. Обзор книги В данном разделе описывается содержание глав и приложений книги. Помимо конкретных сведений главы со- держат информацию о ресурсах Интернета и Web, где перечислены дополнительные источники, ознакомившись с которыми читатели смогут расширить свои знания в области программирования на С#. 1 По материалам корпорации Microsoft. "Реализация совместно используемого источника CLI", март 2002 года, msdn.microsoft.com /I i bra ry/en-us/Dndotnet/h tml/mssha rsou rce-cli.asp. 2 Проект FreeBSD предоставляет полностью доступную открытую операционную систему, подобную UNIX, основанную на BSD Более подробная информация о BSD имеется на сайте www.freebsd.org.
32 Глава 1 □ Глава 1. Введение в архитектуру .NET и язык С#. В главе 1 изложена история Интернета, World Wide Web и различных технологий (например, XML и SOAP), приведших к скачку в развитии вычислительной техники. Здесь представлены инициатива Microsoft .NET и язык программирования С#, включая службы Web. Исследуется влияние .NET на разработку программных средств и возможность их многократного использования. □ Глава 2. Введение в интегрированную среду разработки Visual Studio .NET и программирование на С#. В главе 2 представлена интегрированная среда разработки Visual Studio .NET, позволяющая программи- стам создавать программы на С#. Visual Studio .NET обеспечивает визуальное программирование, в котором элементы управления (например, кнопки и текстовые поля) "перетягиваются" и "сбрасываются" в нужном месте, а не добавляются путем дописывания кода. Визуальное программирование повышает производитель- ность разработки программных средств путем удаления множества рутинных задач программирования. На- пример, свойства графического пользовательского интерфейса (Graphical User Interface, GUI), такие как раз- мер и цвет, можно модифицировать в IDE Visual Studio .NET; при этом изменения вносятся достаточно опе- ративно, и результаты сразу отражаются на экране. Вместо того чтобы гадать во время написания программы, как будет в конечном итоге выглядеть GUI, разработчик видит результат сразу после окончания выполнения программы. Visual Studio .NET содержит инструментальные средства отладки, документирова- ния и написания кодов. В главе представлены различные возможности Visual Studio .NET, включая основные окна, и показаны способы компилирования и выполнения программ. Возможности Visual Studio .NET ис- пользуются для создания простых программных приложений для Windows без единой строки кода. В данной главе читатели также познакомятся с невизуальным программированием на С#. Каждая концепция представлена в контексте полностью рабочей программы C# с последующими графическими изображениями окон, отражающими фактические данные ввода и вывода по мере исполнения программы. Это — принцип LIVECODE ("живой" код). Рассматриваются фундаментальные задачи, например, как программа вводит пользовательские данные и как писать арифметические выражения. Глава также демонстрирует отображение текста в окне MessageBox. П Глава 3. Управляющие структуры. В данной главе изложены принципы структурного программирования, набор методик, которые помогут чи- тателям в разработке четких, понятных и поддерживаемых программ. Затем представлено использование управляющих структур, влияющих на последовательность выполнения операторов. Управляющие структуры создают программы, которые легко понять, отлаживать и поддерживать. Рассматриваются три формы про- граммного управления: последовательность, выборка и повторение с особым упором на операторы if/else, while, for, do/while и switch. Объясняются операторы break и continue, а также логические операции. Ав- торы опирались на материал предыдущей главы для создания интерактивных программ (поведение которых меняется для соответствия данным, вводимым пользователем). Приводится пример, в котором объединены визуальная и невизуальная методики программирования. □ Глава 4. Методы и массивы. Метод дает программисту возможность создания блока кода, который можно вызвать из разных мест про- граммы. Крупные программы можно разбить на взаимодействующие классы, каждый из которых состоит из методов; иногда этот процесс называется стратегией "разделяй и властвуй". Программы делятся на простые компоненты, взаимодействующие прямолинейно. В этой главе обсуждается создание собственных методов, принимающих данные ввода, выполняющих вычисления и возвращающих выходные данные. Представлены рекурсивные методы (методы, вызывающие самих себя) и перегрузка методов, позволяющая нескольким методам иметь одно и то же имя. Авторы демонстрируют перегрузку созданием двух методов square, каж- дый из которых принимает целое число и число с плавающей запятой соответственно. В главе также пред- ставлены массивы — первая структура данных. Структуры данных чрезвычайно важны для сохранения, сор- тировки, поиска и манипуляций большими объемами информации. Массивами называются группы связан- ных между собой элементов данных, обеспечивающих программисту прямой доступ к любому элементу. Вместо созданйя 100 отдельных переменных, которые так или иначе связаны между собой, программист может создать массиь из 100 элементов и осуществлять доступ к ним по их местоположению в этом массиве. Рассматриваются способы объявления и распределения массивов, авторы исходят из методик передачи мас- сивов методам, описанных в предыдущей главе. В главе 3 заложен фундамент рассмотрения массивов, пото- му что для итерации через элементы массива используются операторы циклов. Комбинация этих концепций помогает читателю в создании высокоструктурированных и хорошо организованных программ. Авторы рас- сматривают многомерные массивы (как прямоугольные, так и разреженные), которые можно использовать для сохранения таблиц данных. Представлена конструкция foreach, выполняющая итерации в массиве. □ Глава 5. Объектно-ориентированное программирование. В главе 5 представлены объекты и классы. Объектно-ориентированная технология привела к значительному продвижению разработок программных средств при создании многократно используемых компонентов про-
Введение в архитектуру .NET и язык C#33 ’ граммного обеспечения. Объекты позволяют организовывать программы естественными и интуитивными способами. В данной главе представлены основы объектно-ориентированного программирования, например инкапсуляция, абстракция данных и абстрактные типы данных (Abstract Data Type, ADT). Эти методики скрывают подробности компонентов с тем, чтобы программист мог сосредоточиться на "общей картине" своей работы. Создается класс Time, отражающий время в стандартном и универсальном форматах. Авторы показывают способы создания компонентов программного обеспечения для многократного использования со скомпонованными блоками, пространствами имен и файлами динамически подключаемой библиотеки. Создаются классы и пространства имен и обсуждаются свойства и ключевые слова readonly и const. Глава закладывает основы следующих двух глав, в которых объектно-ориентированное программирование описы- вается подробно. □ Глава 6. Объектно-ориентированное программирование: наследование. В данной главе рассматривается понятие наследования — формы повторного использования программных средств, где классы (называемые производными классами) создаются путем поглощения атрибутов и мето- дов существующих классов (называемых базовыми классами). Унаследованный класс (т. е. производный) может содержать дополнительные атрибуты и методы. Рассматривается, как нахождение подобия между классами объектов может сократить объем работы, необходимый для создания крупных программных сис- тем. Подробный учебный пример демонстрирует многократное использование программных средств и хо- роший стиль программирования путем нахождения подобия в трехуровневой иерархии наследования: между классами Point, Circle и Cylinder. Обсуждаются проектные преимущества объектно-ориентированного программирования. В главе также представлены его наиболее значимые концепции, такие как создание и расширение классов. □ Глава 7. Объектно-ориентированное программирование: полиморфизм. В данной главе продолжается описание объектно-ориентированного программирования. Авторы рассматри- вают полиморфическое программирование и его преимущества. Полиморфизм позволяет рассматривать классы в общем виде, заставляя тот же способ вызова метода проявляться по-разному, в зависимости от кон- текста (например, команда "двигайся", поданная птице или рыбе, побуждает их к принципиально разным ви- дам действий: птица летит, а рыба плывет). Помимо рассмотрения существующих классов в общем виде, по- лиморфизм позволяет легко добавлять в систему новые классы. Приводятся ситуации, в которых полимор- физм весьма полезен. Контрольный пример с системой платежных ведомостей также демонстрирует полиморфизм: система определяет зарплату каждого сотрудника в отдельности, в соответствии с его катего- рией (руководители с фиксированными окладами, почасовики, сотрудники, получающие помимо основной зарплаты комиссионные, и сдельщики). Такие методы программирования, наряду с описанными в предыду- щей главе, позволяют программисту создавать расширяемые программные компоненты многократного ис- пользования. □ Глава 8. Обработка исключительных ситуаций. Обработка исключительных ситуаций — одна из ключевых тем в C# с точки зрения компоновки ответствен- ных и коммерчески-критичных программных приложений. Пользователи могут вводить некорректные дан- ные, данные могут быть запорчены, а клиенты могут пытаться получить доступ к записям, которых не суще- ствует или доступ к которым ограничен. Простая ошибка деления на ноль может вызвать сбой программы- калькулятора. Но чем может закончиться возникновение такой ошибки в системе навигации авиалайнера в воздухе? В некоторых случаях сбой программы может вызвать катастрофические последствия. Программи- стам необходимо знать, как распознавать ошибки (исключения), которые могут иметь место в программных компонентах, и как их эффективно устранять, чтобы программа сама могла их обрабатывать, вместо того, чтобы "падать". Программисты, создающие системы из компонентов, написанных своими коллегами, долж- ны уметь обрабатывать исключения, которые могут демонстрировать эти компоненты. В данной главе опи- саны подробности обработки исключительных ситуаций в С#, модель завершения обработки, прогон и от- слеживание исключительных ситуаций и класс Exception библиотеки FCL. □ Глава 9. Концепции графического пользовательского интерфейса: часть 1. В данной главе описаны способы организации в программах сложных GUI. С помощью методологий быст- рой разработки приложений (RAD) программисты могут создавать GUI из компонентов многократного ис- пользования вместо чистого программирования каждой детали. Интегрированная среда разработки Visual Studio .NET еще больше упрощает разработку GUI, давая программисту возможность располагать компо- ненты в окне с помощью так называемого визуального программирования. В главе обсуждается компоновка пользовательских интерфейсов с помощью элементов управления, размещаемых в формах Windows, таких как метки, кнопки, текстовые поля и рамки изображений. Авторы также объясняют понятие "событие", представляющее собой отправляемое программой сообщение для сигнализации объекту или группе объектов о том, что имела место какая-либо операция. Чаще всего события сигнализируют о взаимодействии пользо- 3 Зак. 3333
34 Глава 1 вателя с элементами управления GUI, но они могут сообщать и об операциях, происходящих внутри про- граммы. Также в главе обсуждается обработка событий, имеющих отношение непосредственно к устройст- вам управления — мыши и клавиатуре. □ Глава 10. Концепции графического пользовательского интерфейса: часть 2. В главе 10 обсуждаются более сложные компоненты GUI: меню, ссылки, панели, списки, комбинированные списки и вкладки. Представлено программирование многодокументного интерфейса (Multiple Document Interface, MD1), обеспечивающее одновременное открытие нескольких документов (форм) в одном GUI. За- вершается глава введением в понятие визуального наследования, позволяющее программистам комбиниро- вать представленные здесь концепции GUI с концепциями объектно-ориентированного программирования, описанного в главе б, для создания пользовательских интерфейсов, применения и расширения другими про- граммистами. В главе приводится много советов в помощь программистам при создании визуально привле- кательных, хорошо структурированных и непротиворечивых GUI. □ Глава 11. Организация многозадачной обработки. Пользователи ожидают от программных приложений довольно многого. Они хотят скачивать файлы из Ин- тернета, слушать музыку, печатать документы, просматривать Web и делать все это одновременно! Для это- го программистам нужна функция, называемая многозадачной обработкой, позволяющая приложениям па- раллельно выполнять различные операции. .NET Framework имеет встроенные возможности активизации многозадачных приложений, ограждая программиста от сложных подробностей. Языки .NET лучше подхо- дят для работы с более сложной мультимедийной средой, сетевыми и многопроцессорными приложениями, нежели языки, не имеющие функций многопоточной обработки. В данной главе представлены поточные классы FCL и описываются потоки, жизненные циклы потоков, квантование времени, планирование и при- оритеты. Авторы анализируют взаимодействие "производитель/потребитель", синхронизацию потоков и кольцевые буферы. Данная глава закладывает основу для создания многозадачных программ, столь необхо- димых пользователям. П Глава 12. Строки, символы и регулярные выражения. В данной главе рассматривается обработка слов, предложений, символов и групп символов. В C# строки (группы символов) являются объектами. Это еще одно преимущество сосредоточения C# на объектно- ориентированном программировании. Объекты типа string содержат методы, которые могут копировать строки, осуществлять их поиск, извлекать подстроки и связывать строки между собой. Авторы представляют класс stringBuilder, определяющий объекты, подобные строкам, которые можно модифицировать после инициализации. В качестве интересного примера строк смоделированы тасование и раздача карт. В главе рассматриваются регулярные выражения — мощный инструмент поиска текста и манипуляций им. П Глава 13. Графика и мультимедиа. В данной главе рассматривается GD1+ (расширение интерфейса графических устройств, Graphic Device Interface) — сервис Windows, обеспечивающий графические функции, используемые приложениями .NET. Расширенные графические возможности GDI+ могут превратить создание и использование программ в бо- лее наглядный и увлекательный процесс. В главе рассматривается обработка на C# графических объектов и управление цветом, а также изображение дуг, многоугольников и других форм. Здесь же демонстрируется применение различных перьев и кистей для создания цветовых эффектов, включая пример, в котором пока- заны градиентная заливка и текстуры. Авторы также представляют методики превращения приложений для работы только с текстом в захватывающие, эстетически-привлекательные программы, доступные для напи- сания даже программистам-новичкам Вторая часть главы посвящена аудио-, видео- и речевым технологиям. Рассматривается добавление в программы звука, видео и анимированных объектов (в основном из сущест- вующих аудио- и видеоклипов). Читатели поймут, насколько просто вставить мультимедиа в программные приложения С#. В главе представлена захватывающая технология под названием Microsoft Agent для добав- ления в программу интерактивных анимированных персонажей. Каждый персонаж дает пользователям возможность взаимодействовать с приложением более свойственным человеку способом — с помощью ре- чи. Персонажи-агенты реагируют на щелчки мыши и нажатие клавиш, говорят и слышат (т. е. они поддер- живают синтез и распознавание речи). При таких возможностях программное приложение может фактически "разговаривать" с пользователями и "отвечать" на их голосовые команды! □ Глава 14. Файлы и потоки. Представьте себе программу, которая не может сохранять данные в виде файла. Как только пользователь ее 'закрывает, вся работа безвозвратно пропадает. По этой причине данная глава — одна из наиболее важных для программистов, планирующих разработку коммерческих программных приложений. Авторы представи- ли классы FCL для ввода и вывода данных. Подробный пример демонстрирует эти концепции путем предос- тавления пользователям возможности считывания информации о банковских счетах из файлов и ее записи в них. Авторы представили классы FCL и методы, помогающие без проблем выполнять ввод и вывод; они де-
Введение в архитектуру .NET и язык C# 35 монстрируют мощь объектно-ориентированного программирования и классов многократного использова- ния. Рассматриваются преимущества файлов с последовательным доступом к данным, файлов с произволь- ным доступом и буферизации. Данная глава закладывает основу материала, представленного в главе 19. □ Глава 15. Язык XML. Язык XML (Extensible Markup Language, расширяемый язык разметки) происходит из SGML (Standard Generalized Markup Language, стандартный обобщенный язык разметки), который стал промышленным стан- дартом в 1986 году. Несмотря на то, что SGML применяется в издательских приложениях по всему миру, он не был включен в основной ряд языков программирования из-за своих размеров и сложности. XML стал по- пыткой создания технологии, подобной SGML, которая бы стала доступной более широкой аудитории. Соз- данный Word Wide Web Consortium, XML описывает данные в мобильном формате. По сути, XML отличает- ся от таких языков разметки, как, например, HTML, описывающего только визуализацию информации в браузере. XML — это технология создания языков разметки практически для любого типа информации. Ав- торы документов используют XML для создания принципиально новых языков разметки с целью описания особых типов данных, включая математические формулы, химические молекулярные структуры, музыку, рецепты и многое другое. В число языков разметки, созданных с помощью XML, входят XHTML (расши- ряемый гипертекстовый язык разметки для Web-содержимого), MathML (для математических формул), VoiceXML (для речи), SM1L (синхронизированный язык интеграции мультимедиа для мультимедиа- презентаций), CML (химический язык разметки для химических формул) и XBRL (расширяемый язык биз- нес-отчетов для обмена финансовыми данными). Расширяемость XML превратила его в одну из важнейших современных технологий, и он интегрируется практически в любую область. Компании и разработчики- одиночки постоянно находят новаторские способы применения XML. В данной главе авторами представле- ны примеры, иллюстрирующие основы разметки данных с помощью XML. Демонстрируются несколько языков разметки, производных от XML, например, такие как XML Schema (для проверки грамматики XML- документа), XSLT (расширяемые преобразования стилевого языка для преобразования данных XML- документа в другой текстовый формат, например, XHTML) и BizTalk от Microsoft (для разметки коммерче- ских транзакций). Примечание_______________________________________________________________________________ Для тех, кто не знаком с XHTML, в книге имеются приложения 1 / и 12 с подробным описанием. □ Глава 16. Базы данных, SQL и ADO.NET. Процесс сохранения и доступа к данным неотъемлем от процесса создания устойчивых программных при- ложений. В данной главе рассматривается поддержка .NET манипуляций базами данных. Самые популярные современные системы баз данных — реляционные. В этой главе представлен язык структурированных за- просов (Structure Query Language, SQL) для осуществления запросов в реляционных базах данных. Также представлена технология ADO.NET (ActiveX Data Objects) — расширение ADO, обеспечивающее приложе- ниям .NET доступ и манипулирование базами данных. ADO.NET предусматривает экспорт данных в виде XML, что позволяет приложениям, использующим ADO.NET, связываться с многими программами, распо- знающими XML. Авторы демонстрируют способы создания связей баз данных с помощью инструментов, имеющихся в Visual Studio .NET, а также использование классов ADO.NET для выполнения запросов к ба- зам данных. П Глава 17. ASP.NET, Web-формы и элементы управления Web. В предыдущих главах описывалось создание программных приложений, исполняемых локально на компью- тере пользователя. В данной главе, а также в главах 18 и 22 обсуждается создание приложений на базе Web с помощью ASP.NET (Active Server Pages .NET). Это важнейший аспект .NET и видения Microsoft того, как следует разрабатывать и развертывать программные средства в Интернете. ASP.NET — обобщающая тех- нология создания динамического Web-содержимого, размеченного как HTML. (Для тех, кто не знаком с HTML, в книге имеются приложения 9 и Юс подробным описанием языка.) Web-формы обеспечивают GUI для страниц ASP.NET и могут содержать элементы управления Web, например надписи, кнопки и текстовые поля, с которыми взаимодействуют пользователи. Подобно формам Windows, Web-формы спроектированы визуальным программированием. В данной главе представлено много интересных примеров, в том числе оперативное приложение гостевой книги и многозвенное, тесно связанное с базами данных приложение, по- зволяющее пользователям сделать запрос списка публикаций определенного автора в базе данных. Также рассматривается отладка Web-форм с помощью свойства Trace. □ Глава 18. ASP.NET и Web-службы. В данной главе продолжается рассмотрение ASP.NET. Здесь представлены Web-службы, являющиеся про- граммами, "выставляющими" службы (т. е. методы) клиентам по Интернету, внутренним и экстрасетям. Web-службы предлагают возможность многократного использования программ путем обеспечения прямого
36 Глава 1 взаимодействия служб разных платформ. В данной главе приведено несколько интересных примеров, вклю- чающих Web-службы для манипуляций большими числами (до 100 знаков), моделирование карточной игры "Блэк-джек" и реализацию системы бронирования билетов авиакомпании. Одним из наиболее интересных примеров является сервер температуры — Web-служба, собирающая информацию о погоде в десятках горо- дов США. □ Глава 19. Организация сетей: сокеты на основе потоков и дейтаграммы. В главе 19 представлены фундаментальные методологии организации сетей на основе потоков. Авторы де- монстрируют, как сокеты на основе потоков позволяют программистам скрыть многие подробности сетево- го планирования. С помощью сокетов организация сети становится настолько же простой задачей, как счи- тывание файла и запись в него. Также представлены дейтаграммы, в которых отправляются пакеты данных между программами. Каждый пакет адресован получателю и направляется в сеть, а та, в свою очередь, пере- правляет пакет адресату. Примеры данной главы делают упор на связь между приложениями. Один пример демонстрирует использование сокетов на основе потоков для взаимодействия двух программ, написанных на С#. Другой похожий пример пересылает дейтаграммы между программными приложениями. Авторы также описывают создание многопотокового серверного приложения, которое может параллельно взаимодейство- вать с несколькими клиентами. В игре "Крестики-нолики" на основе технологии "клиент-сервер" сервер поддерживает статус игры, а два клиента взаимодействуют с сервером собственно для игры. □ Глава 20. Структуры данных и коллекции. В этой главе рассматривается классификация данных по структурам: связанным спискам, стекам, очередям и деревьям. Каждая структура данных обладает свойствами, полезными для многих приложений, от сортиров- ки элементов до прослеживания обращений к методам. Авторы рассматривают способы построения каждой из этих структур данных. Это также дает ценный опыт создания полезных классов. Кроме того, в главе опи- сывается заготовленная коллекция классов FCL. В этих классах сохраняются наборы — или коллекции — данных, и они обеспечивают функциональность, позволяющую разработчикам сортировать, вставлять, уда- лять и извлекать элементы данных. Различные коллекции данных сохраняют данные по-разному. В этой гла- ве основной упор сделан на классах Array, ArrayList, Stack и Hashtable с подробным рассмотрением каж- дого. Если возможно, то программистам, работающим на С#, следует пользоваться FCL для нахождения подходящих структур данных, вместо того, чтобы самим реализовывать эти структуры данных. Глава в большой степени подкрепляет материалы, изложенные в главах 5—7, включая классы, наследование и ком- позицию. □ Глава 21. Обеспечение доступности программных приложений. World Wide Web представляет определенные сложности для пользователей, не имеющих достаточных физи- ческих возможностей. Web-сайты с большим количеством мультимедийной информации труднодоступны для тех, кто интересуется только текстом, и для интерпретации определенными программами, поэтому поль- зователи с ограниченными средствами прослушивания и просмотра могут испытывать трудности при про- смотре таких сайтов. Для решения данной проблемы W3C выступил с Инициативой доступности в Web (Web Accessibility Initiative, WAI), предоставляющей руководства по доступу к Web-сайтам людям с ограни- ченными возможностями. В данной главе представлено описание этих руководств, например, использование элемента headers для того, чтобы сделать таблицы более доступными для посетителей страниц, атрибута alt элемента img для описания изображений, а также комбинации XHTML и стандарта CSS для обеспечения доступности Web-страницы с целью просмотра практически на любом типе монитора или в обозревателе. Авторы иллюстрируют ключевые функции доступности Visual Studio .NET, Internet Explorer и Windows 2000. Здесь же представлены VoiceXML и CallXML — две технологии повышения досгупности содержимо- го на базе Web. VoiceXML помогает пользователям с ограниченными возможностями просмотра с доступом к Web-содержимому через синтез и распознавание речи, a CallXML — с помощью телефонной связи. □ Глава 22. Mobile Internet Toolkit. Потребность в беспроводных приложениях растет с каждым днем. В ближайшие два года число людей, осу- ществляющих подключение к Интернету посредством беспроводных устройств, превысит число пользовате- лей, выходящих в Интернет с настольных компьютеров. Mobile Internet Toolkit (М1Т, мобильный набор для выхода в Интернет) превосходит Visual Studio .NET по предоставлению классов FCL для создания Web- приложений для мобильных устройств. В данной главе представлены мобильные элементы управления Web и Web-формы, которые можно использовать для создания приложений ASP.NET, подходящие широкому диапазону мобильных устройств. Более того, мобильные Web-приложения, созданные с помощью MIT, можно спроектировать с определением типа устройства путем запроса и генерирования разметки, подходя- щей именно этому устройству. Например, органайзер и мобильный телефон могут запросить одну и ту же страницу, но получить разную разметку. Этот феномен известен как "визуализация под устройство" — про- цесс, описанный в данной главе. И наконец, авторы демонстрируют способы получения Web-службы из мо-
Введение в архитектуру .NET и язык C#37 бильного Web-приложения. В этом примере показывается, что получение Web-службы из мобильного при- ложения практически ничем не отличается от той же операции из приложения в Windows. □ Приложение 1. Приоритет операций. В данном приложении перечислены операции C# с учетом их приоритета. □ Приложение 2. Системы счисления. В данном приложении рассказывается о двоичных, восьмеричных, десятичных и шестнадцатеричных систе- мах счисления. Также рассматривается преобразование чисел между этими системами и для каждой приво- дятся математические операции. □ Приложение 3. Возможности карьерного роста. В данном приложении описаны карьерные ресурсы для программистов на С#. □ Приложение 4. Отладчик Visual Studio .NET. Здесь представлена программа-отладчик Visual Studio .NET для локализации логических ошибок в програм- мах. В число основных функций входит настройка точек прерывания, построчный просмотр программ и на- блюдение за значениями переменных. □ Приложение 5. Создание документации в Visual Studio NET. В данном приложении рассматривается использование комментариев к документации в рамках программ, написанных на С#. Программисты могут пользоваться инструментом генерирования документации Visual Studio .NET для создания документов в форматах XML или HTML. □ Приложение 6. Набор символов ASCII. Данное приложение содержит таблицу из 128 буквенно-числовых символов ASCII (американский стандарт- ный код обмена информацией) вместе с их целочисленными значениями. П Приложение 7. Unicode. В данном приложении представлен стандарт Unicode — схема кодирования, в которой уникальные числовые значения присваиваются символам большинства мировых языков. Авторы включили сюда Windows- приложение, в котором используется кодировка Unicode для распечатки приглашающих сообщений на не- скольких языках. □ Приложение 8. Интеграция СОМ. До появления .NET для обозначения того, как различные языки программирования взаимодействуют между собой на двоичном уровне, применялась модель COM (Component Object Model, модель компонентных объ- ектов). Например, такие компоненты СОМ, как элементы управления ActiveX и динамические библиотеки ActiveX, часто писались на Visual C++ от Microsoft, но использовались в других программах Windows. Платформа .NET не поддерживает напрямую компоненты СОМ, однако Microsoft обеспечивает инструмен- тарий для интеграции компонентов СОМ в приложения .NET. В данном приложении рассматриваются неко- торые из этих инструментальных средств путем интеграции в приложения элементов управления ActiveX и ActiveX DLL. ч □ Приложение 9 Введение в HTML 4: часть 1. Приложение 10. Введение в HTML 4: часть 2. В данных приложениях представлено введение в HTML— язык разметки описания элементов Web- страницы для того, чтобы браузер, например Microsoft Internet Explorer, мог визуализировать эту страницу. До чтения главы 17 читатель должен ознакомиться с данными приложениями; в них ни слова не сказано о программировании на С#. В некоторые ключевые темы, обсуждаемые в приложении 9, входит описание включения текста и изображений в документ HTML, связывание с другими документами HTML в Web, вне- дрение в документ HTML специальных символов (например, символов авторского права и торговой марки) и разделение документа горизонтальными линиями. В приложении 10 рассматриваются более значительные элементы и функции HTML. Авторы демонстрируют представление информации в списках и таблицах, описывают сбор информации от пользователей, просмат- ривающих Web-сайт. Здесь же объясняется использование внутренних ссылок и отображение графики для упрощения навигации по Web-страницам, а также использование кадров для отображения множественных окон в окне браузера. □ Приложение 11. Введение в XHTML: часть 1. Приложение 12. Введение в XHTML: часть 2. В этих приложениях вводится понятие расширяемого гипертекстового языка разметки (XHTML) — разви- вающейся технологии W3C, предназначенной для замены HTML в качестве первичного средства описания
38 Глава 1 Web-содержимого. Будучи языком, основанным на XML, XHTML устойчивее, нежели HTML, и обладает более усовершенствованными возможностями расширяемости. XHTML объединяет большую часть элемен- тов и атрибутов HTML, на чем и сделан упор в данных приложениях. Приложения 11 и 12 предназначены для читателей, не знакомых с XHTML или желающих заново "пройти" тему XHTML перед изучением глав 15 и 21. □ Приложение 13. Специальные символы HTML/XHTML. В данном приложении приводится много широко используемых специальных символов HTML/XHTML, ко- торые называются ссылками на запись символов. □ Приложение 14. Цвета HTML/XHTML. В приложении приводятся широко используемые названия цветов HTML/XHTML и их соответствующие шестнадцатеричные значения. □ Приложение 15. Поразрядные операции. В данном приложении рассматриваются усовершенствованные возможности поразрядных операций С#. Они помогают программам обрабатывать битовые строки, настраивать отдельные биты и более компактно со- хранять информацию. Такие возможности (унаследованные из С) характерны для ассемблерных языков низ- кого уровня и оцениваются программистами, пишущими системное программное обеспечение — операци- онные и сетевые системы. х □ Приложение 16. Crystal Reports для Visual Studio .NET. Visual Studio .NET включает в себя специальный инструмент Crystal Reports — генератор отчетов на базе Windows. В данном приложении описаны ресурсы, которые компания Crystal Decisions (разработчик Crystal Reports) представляет на собственном Web-сайте вместе с обзором уникальной функциональности Crystal Reports в Visual Studio .NET. 1.10. Резюме В конце 60-х годов прошлого века на конференции ARPA (Advanced Research Projects Agency of the Department of Defense, управление перспективного планирования научно-исследовательских работ при Министерстве обо- роны) в университете Иллинойса Урбана-Шампань (Urbana-Champaign) впервые был представлен план объеди- нения в общую сеть основных компьютерных систем около десятка университетов и исследовательских органи- заций, финансируемых ARPA, Вскоре после этой конференции ARPA начало разработки ARPAnet, — "дедуш- ки" современного Интернета. Несмотря на то, что ARPAnet позволила исследователям объединять компьютеры в сети, ее основным достоин- ством стала возможность быстрого и простого взаимодействия через то, что впоследствии стало называться электронной почтой (e-mail). Это преимущество полностью себя оправдывает в современном Интернете, где сотни миллионов людей во всем мире имеют доступ к электронной почте, мгновенному обмену сообщениями и средствам связи, максимально упрощающим процесс обмена файлами. Протокол (другими словами, набор правил) для передачи информации по ARPAnet стал называться протоколом управления передачей (TCP). TCP обеспечивал передачу сообщений непосредственно от отправителя адресату, а также доставку этих сообщений неповрежденными. ARPA разработала протокол Интернета (IP), создавший подлинную "сеть сетей", ныне существующую архитектуру Интернета. Комбинированный набор протоколов в настоящее время обозначается широко известной аббревиатурой TCP/IP. World Wide Web (Всемирная паутина) дает пользователям компьютеров возможность размещения и просмотра документов, основанных на мультимедиа (т. е. документов с текстом, графикой, анимацией, аудио- и видеоин- формацией), практически по любой теме. В 1989 году Тим Бернерс-Ли (Tim Berners-Lee) из CERN приступил к разработке технологии совместного использования информации посредством текстовых документов с гипер- ссылками. Бернерс-Ли назвал свое изобретение гипертекстовым языком разметки (HTML). Он также написал протоколы связи для создания "скелета" новой системы гипертекстовой информации, названной World Wide Web. В октябре 1994 года Тим Бернерс-Ли основал организацию под названием Консорциум Всемирной паутины (World Wide Web Consortium, W3C), которая занималась разработкой непатентованных межоперационных тех- нологий для World Wide Web. Одной из основных целей W3C было обеспечение универсальной доступности сети, независимо от средств, языков или культур разных народов. Расширяемый язык разметки (XML) объединяет мощь и расширяемость "праязыка" — стандартного обобщен- ного языка разметки (SGML) с его простотой, столь необходимой Web-сообществу. Независимость данных,
Введение в архитектуру .NET и язык C#39 отделение содержимого от его представления — основные характеристики языка XML. Поскольку документ XML описывает данные, вероятно, что такой документ может быть обработан любым программным приложе- нием. Гибкость и мощность XML делают его идеальным для систем среднего яруса и систем "клиент/сервер", предназначенных для взаимодействия с широким спектром клиентов. Простой протокол доступа к объектам (SOAP) — технология распределения данных (отмеченных как XML) в Интернете. Изначально разработанный Microsoft и Develop-Mentor, SOAP— это рабочий проект W3C, пре- доставляющий структуру выражения семантики программного приложения, кодирования и упаковки данных. Microsoft .NET использует XML и SOAP для разметки и передачи данных по Интернету. XML и SOAP лежат в основе .NET: они обеспечивают межфункциональное взаимодействие (т. е. быстрое взаимодействие друг с дру- гом) компонентов программного обеспечения. Объектно-ориентированная технология — это схема упаковки, упрощающая создание значимых блоков про- граммного обеспечения. Объекты обладают свойствами (или атрибутами, такими как цвет, размер и вес) и вы- полняют действия (т. е. поведения, такие как передвижение, сон, рисование). Классы представляют группы род- ственных объектов. С возникновением объектно-ориентированных языков, например, C++, Java и C# появилась возможность про- граммирования объектно-ориентированным способом, отражающим путь, по которому программисты постига- ют мир. Результатом этого процесса, кажущегося более естественным, нежели процедурное программирование, стал значительный выигрыш в производительности. В объектно-ориентированной технологии правильно спроектированные программные единицы (называемые объектами) можно использовать в последующих проектах Использование библиотек многократной компоновки может сократить объем усилий, необходимых на реализацию систем определенных типов (по сравнению с уси- лиями, которые требуются на повторное воссоздание функций в новых проектах). Программисты, работающие с С#, пользуются библиотекой класса среды .NET (обычно называется FCL). В июне 2000 года корпорация Microsoft объявила о своей инициативе .NET. Платформа .NET обеспечивает зна- чительные усовершенствования более ранних платформ разработки. .NET предлагает новую модель разработки программного обеспечения, предусматривающую взаимодействие между собой программных приложений, соз- данных на разных языках программирования. Данная платформа также позволяет разработчикам создавать про- граммные приложения на базе Web, которые можно распределять по многим устройствам, включая беспровод- ные телефоны и настольные рабочие станции. Одним из ключевых аспектов стратегии .NET является ее независимость от конкретного языка программирова- ния или платформы. Вместо того чтобы требовать от программистои использования одного языка программи- рования, разработчики могут создать приложение .NET с помощью любой комбинации языков, совместимых с .NET. Программисты могут вести один и тот же проект с написанием кода на языках .NET, которые они знают лучше всего. Частью инициативы является технология ASP.NET (Active Server Pages) от Microsoft, позволяющая создавать приложения для Web. Ключевым компонентом архитектуры .NET являются Web-службы— программные приложения, обеспечи- вающие функции для клиентов через Интернет. Клиентские и другие приложения используют Web-службы в качестве стандартных блоков повторного использования. Универсальный доступ к данным — еще одна важная концепция .NET. С помощью .NET данные могут распо- лагаться в одном центральном местоположении, а не на разных системах. Любое устройство, подключенное к Интернету, может осуществить доступ к данным (разумеется, при строгом контроле), которые затем будут со- ответствующим образом отформатированы для использования или отображения на устройстве. C# — это событийно-управляемый, полностью объектно-ориентированный язык визуального программирова- ния, в котором программы создаются с помощью интегрированной среды разработки (IDE). В этой среде про- граммист может спокойно писать, запускать, тестировать и отлаживать программы, написанные на С#, экономя массу времени на создание рабочей программы без помощи IDE. .NET Framework — ядро концепции .NET. Данная структура управляет программными приложениями и запус- кает их, содержит библиотеку классов FCL, обеспечивает защиту и множество других возможностей програм- мирования Подробности .NET Framework имеются в спецификации универсального языка (CLS), содержащей информацию о сохранении объектов и проч. Рабочий цикл универсального языка (CLR) — еще одна центральная часть .NET Framework; она выполняет про- граммы, написанные на С#. Программы компилируются в машинные команды за два этапа. Сначала программа компилируется в промежуточный язык Microsoft (MSIL), определяющий команды для CLR. Код преобразуется в MSIL из других языков, и источники переплетаются с помощью CLR. Затем другой компилятор в CLR компи- лирует MSIL в машинный код (для конкретной платформы) с созданием единого программного приложения.
40 Глава 1 1.11. Ресурсы Интернета и WWW □ www.deitel.com Официальный Web-сайт компании Deitel & Associates, Inc. Здесь посетитель найдет обновления, исправле- ния, материалы для загрузки и дополнительные ресурсы всех публикаций Deitel. Помимо этого, на сайте представлена информация о других материалах Deitel & Associates, Inc., сведения о международных перево- дах и многое другое! □ www.deitel.com/newsletter/subscribe.html Здесь можно зарегистрироваться для получения новостного буклета DEITEL BUZZ ONLINE. Данный бес- платный буклет-ежемесячник сообщает читателям о программе публикаций, корпоративных обучающих курсах под руководством опытных инструкторов, о самых последних тенденциях индустрии и многом дру- гом. Имеется как полноцветный HTML-буклет, так и в текстовом формате. □ www.prenhall.com/deitel Сайт издательства Prentice Hall для публикаций Deitel, содержащий информацию о продуктах и публикаци- ях, материалах для загрузки, учебные планы Deitel и информацию об авторах □ www.InformlT.com/deitel Web-страница компании Deitel & Associates, Inc, рассказывающая о сайте Пирсона InformIT. (Пирсон (Perason) — владелец издателя — Prentice Hall). InformIT — исчерпывающий ресурс д ля 1Т-профессионалов, в котором содержатся статьи, электронные издания и другие материалы о самых современных технологиях. В киоске Deitel на InformIT размещаются две или три бесплатных статьи в неделю, а также коммерческие электронные публикации. На данном сайте можно купить публикации Deitel. □ www.w3.org Консорциум World Wide Web (W3C) — организация, разрабатывающая и рекомендующая технологии для Интернета и World Wide Web. На сайте имеются ссылки на технологии W3C, новости, отчеты о миссиях и часто задаваемые вопросы. Deitel и Associates, Inc. входит в число членов W3C. □ www.netvalley.com/intval.html На данном сайте представлена история Интернета и World Wide Web. □ www.microsoft.com Web-сайт корпорации Microsoft представляет информацию и технические ресурсы для всех продуктов Microsoft, включая .NET, промышленное программное обеспечение и операционную систему Windows. □ www.microsoft.com/net Домашняя страница .NET представляет материалы для загрузки, описание новостей и событий, информацию по сертификации и подписке.
ГЛАВА 2 @1 Введение в интегрированную среду разработки Visual Studio .NET и программирование на C# Лучше один раз увидеть, чем сто раз услышать Поговорка За формой всегда следует действие. Луис Генри Салливан Интеллект... это способность к созданию искусственных объектов, особенно инструментов для изготовления инструментов. Анри Луи Бергсон Темы данной главы: □ ознакомление с интегрированной средой разработки (Integrated Development Environment, IDE) Visual Studio .NET; □ работа с командами меню и панелью инструментов IDE; . П описание окон Visual Studio .NET; □ работа с разделами панели элементов; □ справочная система Visual Studio .NET; □ создание, компилирование и выполнение простой программы, написанной на С#; □ использование операторов ввода/вывода; □ знакомство с примитивными типами данных; □ использование арифметических операторов; □ применение директивных операторов; П использование реляционных операторов и операторов равенства. 2.1. Введение Visual Studio .NET — это интегрированная среда разработки (IDE) корпорации Microsoft для создания, докумен- тирования, запуска и отладки программ, написанных На различных языках программирования .NET. Visual Stu- dio .NET также предлагает инструментальные средства редактирования для манипулирования файлами несколь- ких типов. Visual Studio .NET — это мощный и сложный инструмент для создания целевых коммерческих про- грамм. В данной главе представлен общий обзор функций Visual Studio .NET, необходимых для создания простейшей программы на языке С#. Здесь также описаны дополнительные особенности IDE. В главе объясняются принципы программирования на C# и представлены примеры, иллюстрирующие несколь- ко важнейших функций языка. Примеры анализируются построчно. В данной главе делается попытка создания консольных приложений— программных средств, содержащих выходные данные, состоящие в основном из текста. Существует несколько типов проектов, которые можно создавать на С#, и консольное приложение — один из основных типов. Текстовые выходные данные в консольном приложении отображены в консольном окне (также называется командным окном). В Microsoft Windows 95/98 консольное окно называется подсказкой MS-DOS (MS-DOS prompt). В Microsoft Windows NT/2000/XP консольное окно называется приглашением на ввод команды (command prompt).
42 Гпава 2 С помощью C# можно создать программу, имеющую множественные типы вывода (в виде окон, диалоговых окон и т. д.). Эти программы называются Windows-приложениями. Они обеспечивают графические пользова- тельские интерфейсы. В главе представлен краткий обзор создания на C# как Windows-, так и консольных при- ложений. А в следующей главе подробно рассмотрен процесс разработки программ и программного управления на С#. 2.2. Обзор интегрированной среды разработки Visual Studio .NET При первом запуске Visual Studio .NET отображается страница запуска (Start Page) (рис. 2.1). Эта страница со- держит полезные ссылки, показанные в левой ее части Пользователи могут щелкнуть кнопкой мыши на назва- нии раздела (например, Get Started) для просмотра содержимого. Будем называть однократное нажатие на ле- вую кнопку мыши выборкой или щелчком, а быстрое двойное нажатие — двойным щелчком. Примечание___________________________________________________________________ Пользователь должен знать о небольших различиях внешнего представления Visual Studio NET. в зависимости от версии При щелчке на Get Started загружается страница, содержащая ссылки на проекты, открывавшиеся последними (например, windowsApplicationl), а также даты последних изменений этих проектов. Пользователь также может выбрать в меню File команду Recent Projects. При первой загрузке Visual Studio .NET этот раздел будет пус- тым. Обратите внимание на две кнопки на странице: Open Project и New Project. Кнопки навигации Адресная строка L Скрытое окно _ Ссылки стартовой страницы Кнопки Последние просматриваемые свойства Рис. 2.1. Страница запуска Visual Studio .NET Теперь авторы намерены представить краткий обзор других ссылок Start Page1, показанных на рис. 2.1. Раздел What's New отображает новые функции и обновления Visual Studio .NET, включая материалы загрузки приме- ров кодов и новые инструментальные средства программирования. Раздел Online Community включает спосо- 1 Многие ссылки в Start Page требуют подключения компьютера к Интернету.
Введение в интегрированную среду разработки Visual Studio .NET и программирование на C#* 43 бы контактов с другими разработчиками программного обеспечения с помощью групп новостей, новостей, ста- тей и практических руководств. Раздел Headlines предоставляет возможность просмотра новостей, статей и ру- ководств. Раздел Search Online дает пользователю возможность просматривать оперативную библиотеку MSDN (Microsoft Developer Network— собрание документов компании Microsoft, содержащее сведения обо всех ее разработках). На сайте MSDN имеется множество статей, материалов для загрузки и обучающих мате- риалов по различным технологиям. Раздел Downloads предоставляет возможность получения обновлений и примерных кодов. Раздел XML Web Services снабжает пользователей информацией о Web-службах, являю- щихся частями программного обеспечения многократного использования, доступ к которым осуществляется по Интернету. Авторы рассматривают эту технологию в главе 18. Web Hosting обеспечивает информацию для раз- работчиков, желающих разместить свои программные средства (например, Web-службы) в оперативном режиме для общественного использования. Страница Му Profile дает пользователям возможность специальной на- стройки Visual Studio .NET, например клавиатуры и предпочтений расположения окон. Пользователи также мо- гут настроить Visual Studio .NET выбором строк Options или Customize в меню Tools. Примечание________________________________________________________________________________ С этого места будем пользоваться символом | для указания на выбор команды меню. Например, используется обозначение Tools | Options и Tools | Customize для указания на выбор команд Options и Customize из меню Tools. В Visual Studio .NET можно даже просматривать Web — браузер Internet Explorer является частью IDE. Для доступа к Web-странице введите ее адрес в адресной строке (см. рис. 2.1) и нажмите клавишу <Enter>. Помимо окна Start Page в IDE появляются еще несколько окон, которые описываются в последующих разделах. Для создания новой программы на C# нажмите кнопку New Project в разделе Get Started. После этого действия отобразится диалоговое окно, показанное на рис. 2.2. В Visual Studio .NET программы размещаются по проек- там и решениям. Проектом называется группа родственных файлов, например код С#, графика и документация. Решение — это группа проектов, представляющая собой полное приложение или набор связанных приложений. Каждый проект в решении может выполнять разные задачи. В данной главе создается решение, состоящее из одного проекта. [- Папка Visual C# Projects г Выбранный значок Windows Application L Имя проекта L Размещение проекта L Описание выбранного проекта Рис. 2.2. Диалоговое окно New Project Visual Studio .NET дает программистам возможность создавать проекты на различных языках программирова- ния. В данной книге упор сделан на С#, поэтому выберите папку Visual C# Projects в диалоговом окне New Project. Выбор можно делать из большого числа типов проектов, и некоторые из них рассматриваются в книге. В данном случае создается Windows-приложение. Windows-приложения — это программы, работающие в рам- ках операционной системы Windows, такие, как, например, Microsoft Word, Internet Explorer и Visual Studio .NET. Как правило, такие программы содержат элементы управления', графические элементы— кнопки и надписи, с помощью которых пользователи взаимодействуют с программой.
44 Гпава 2 Visual Studio .NET по умолчанию присваивает проекту и решению имя WindowsApplicationl (см. рис. 2.2). Ме- стоположение по умолчанию для сохранения связанных между собой файлов — папка, в которой был создан последний проект. Первая папка, которая просматривается Visual Studio .NET,— папка по умолчанию Visual Studio Projects в папке Му Documents. Пользователи могут изменять как имя, так и местоположение папки, в которой будет сохранен проект. После выбора имени и местоположения проекта нажмите кнопку ОК в диало- говом окне New Project. Внешний вид IDE изменится, как показано на рис. 2.3. Пункт меню -1 г- Панель Окно “ инструментов Solution Explorer Вкладки -э г- Активная Л I вкладка Г Заголовок окна г- Строка меню *- Окно Properties L Форма (Windows-приложение) Рис. 2.3. Среда Visual Studio NET после создания проекта На приведенном рисунке большая серая рамка обозначает окно для разрабатываемого приложения. Эта рамка называется формой. Далее в главе рассматривается процесс добавления в форму элементов управления. Форма и элементы управления представляют графический пользовательский интерфейс (GUI) программы. То есть, они являются графическими компонентами, посредством которых пользователи взаимодействуют с програм- мой. Пользователи вводят данные (входные данные) в программу с клавиатуры и щелчками левой кнопки мыши. Программа отображает инструкции и прочую информацию (выходные данные) для пользователей, которую она считывает из GUI. В заголовке окна IDE (см. рис. 2.3) показан текст WindowsApplicationl - Microsoft Visual C# .NET [design] - Forml.cs {Design], Этот заголовок представляет название проекта (windowsAppiication), язык программирова- ния (Microsoft Visual C# .NET), режим IDE (режим проектирования), просматриваемый файл (Forml.cs) и ре- жим просматриваемого файла (режим проектирования— Design). Имя файла Forml.cs— имя по умолчанию для Windows-приложений С#. Различные режимы рассматриваются в разд. 2.6. Обратите внимание на появлении вкладки для каждого открытого документа. В данном случае документы — Start Page и Forml.cs [Design]. Для просмотра документа с вкладки щелкните кнопкой мыши на вкладке с на- званием документа, который нужно просмотреть. Вкладки экономят место и обеспечивают упрощенный доступ к нескольким документам. 2.3. Меню и панели инструментов Команды для управления IDE, а также для разработки, поддержки и выполнения программ содержатся в меню. На рис. 2.4 показаны пункты меню, отображенные в панели меню Visual Studio .NET. Меню содержат группы связанных команд, при выборе которых IDE выполняет различные операции (например, открытие окна). На- пример, новые проекты можно создавать выбором в панели меню команд File I New | Project. Содержимое
Введение в интегрированную среду разработки Visual Studio .NET и программирование на C# 45 пунктов меню, изображенных на рис. 2.4, в общем виде представлено в табл. 2.1. Visual Studio .NET обеспечи- вает различные режимы, в которых может работать пользователь. Один из этих режимов — режим проектиро- вания, рассматриваемый в разд. 2.6. Определенные меню появляются только в особых режимах IDE. — г тт г —? —------------------------------------------------------------------------ FHe View Project Q&s Fgrn^ T^ois , Рис. 2.4. Меню Visual Studio .NET Таблица 2.1. Пункты меню Visual Studio .NET Меню Описание File Содержит команды для открытия, закрытия, печати проектов и т. д. Edit Содержит команды вырезания, вставки, поиска, отмены последней операции и т. д. View Содержит команды отображения окон IDE и панелей инструментов Project Содержит команды добавления в проект функций, например, форм Build Содержит команды для компилирования программы Debug Содержит команды отладки и исполнения программы Data Содержит команды взаимодействия с базами данных Format Содержит команды организации элементов управления формы Tools Содержит команды дополнительных инструментов IDE и опций специальной настройки среды Window Содержит команды расположения и отображения окон Help Содержит команды получения справки в Visual Studio .NET Вместо выбора наиболее часто используемых команд из меню программисты обращаются к командам через панель инструментов. Панель инструментов содержит кнопки, обозначающие команды. Для выполнения команды щелкните мышью на соответствующей кнопке. Для обращения к связанным с командами опциям на- жмите значок с направленной вниз стрелкой рядом с пиктограммой кнопки. На рис. 2.5 показана стандартная панель инструментов (заданная по умолчанию) и кнопка с направленной вниз стрелкой. - Кнопка на панели инструментов (соответствует команде созданя нового проекта или решения) инструментов показывает дополнительные команды Рис. 2.5. Панель инструментов Visual Studio .NET Всплывающая подсказка Рис. 2.6. Всплывающая подсказка команды При размещении курсора мыши на кнопке она подсвечивается, и через несколько секунд появляется всплываю- щая подсказка (рис. 2.6). Всплывающие подсказки помогают программистам-новичкам быстрее ознакомиться с функциями IDE. 2.4. Окна Visual Studio .NET Среда Visual Studio .NET предоставляет программистам окна для просмотра файлов и специализированной на- стройки элементов управления. В следующих разделах рассматриваются окна, представляющие важность при разработке программных приложений на С#. Вход в эти окна осуществляется посредством кнопок панели инст- рументов, расположенных под строкой меню справа от панели элементов (рис. 2.7), либо выбором названия нужного окна из меню View.
46 Глава 2 Для окна Solution Explorer Для окна Properties Для панели инструментов Рис. 2.7. Кнопки панели инструментов для открытия разных окон Visual Studio .NET 2.4.1. Окно Solution Explorer В окне Solution Explorer (рис. 2.8) перечислены все файлы решения. При первой загрузке Visual Studio .NET (рис. 2.8, б) окно Solution Explorer пустое: файлов для отображения нет. После создания нового проекта или загрузки существующего Solution Explorer отображает содержание этого проекта (рис. 2.8, а). Стартовый проект решения — это проект, выполняющийся при запуске решения. Его название появляется полужирным шрифтом в окне Solution Explorer. Для рассматриваемого решения, состоящего из одного проек- та, единственным проектом является WindowsApplicationl. Файл C#— Forml.cs, в нем содержится код про- граммы. Далее в книге рассматриваются другие файлы и папки. Обновить “ Отобразить все файлы г- Окно Properties [solution Eki lor et - W< tdow* №гмвв|(|,- -и-1,г-1М|- Hfc |r 3 Solution ’WindowiAppfcsbonl ‘ (1 project) I 3 Sri WtodomAppkatlonl----------- Ж (jig References i*l AssemHylnfo.cs ИЕЯВ OassVtew ! L Развернуть дерево - Свернуть дерево Стартовый проект— а Рис. 2.8. Окно Solution Explorer: а — с открытым решением; б — без открытого решения Значки с изображениями плюса и минуса слева от элементов в окне Solution Explorer разворачивают и свора- чивают элементы (или деревья), соответственно (подобно тому, как это делает Проводник). Щелкните на значке с плюсом для отображения большего количества опций; щелкните на значке с минусом для свертывания раз- вернутого дерева. Пользователи также могут развертывать и свертывать дерево двойным щелчком кнопки мы- ши на названии папки с файлами. Во многих других окнах Visual Studio .NET для развертывания и свертывания также используются значки с изображениями плюса и минуса. Окно Solution Explorer содержит панель инструментов. Одна пиктограмма в панели инструментов перезагру- жает файлы решения (обновляет), а другая пиктограмма показывает все файлы решения (включая скрытые фай- лы или файлы, расположенные в каталоге проекта, но не рассматриваемые как часть проекта). Количество пик- тограмм в панели инструментов меняется в зависимости от типа выбранного файла. Эти пиктограммы рассмат- риваются далее в книге. 2.4.2. Панель элементов Панель элементов (Toolbox), показанная на рис. 2.9, содержит программные компоненты (или элементы управ- ления) многократного использования, которые можно применять для настройки приложений. С помощью визу- ального программирования разработчики могут "перетаскивать и бросать" (drag and drop) элементы управления на форму вместо того, чтобы самостоятельно писать соответствующие коды. Точно так же, как людям для управления автомобилем не обязательно знать устройство двигателя, программистам не обязательно знать уст- ройство элемента управления для его использования. Это позволяет им сосредоточиться на "общей картине", а не на сложных подробностях каждого элемента управления. Широкое разнообразие доступных программистам
Введение в интегрированную среду разработки Visual Studio .NET и программирование на C# 47 инструментов является мощной особенностью С#. Далее в главе авторы демонстрируют силу элементов управ- ления при визуальном создании собственной программы на С#. Группа в панели Toolbox - Указатель, помогает снимать выделение текущего элемента управления Элементы “* Стрелка управления прокрутки Components . . y-j Pointer JJ. FteS^temWatdMr (^Eventlog TH DtractoryEntry 1§деас№у5е*4мг f gjTMessageQiieue Q3₽erformmceCounter 73lrO BSS ? f . ServKoCortrpler . . 0 Reporttacunent Рис. 2.9. Панель элементов: a — группа Windows Forms (начало); б — группа Windows Forms (окончание); в — группа Components В панели элементов, показанной на рис. 2.9, содержатся группы связанных компонентов (например, данные — Data, компоненты — Components и формы окон — Windows Forms). Легко увеличить количество участников группы щелчком кнопки мыши на названии группы. Пользователи могут прокручивать отдельные элементы с помощью черных стрелок прокрутки в правой части окна Toolbox. Добавить элемент управления в приложение можно выбором имени этого элемента, двойным щелчком по эле- менту управления или перетаскиванием с помощью курсора мыши в форму. Первый элемент управления в группе не является добавляемым в приложение: это указатель мыши (выделенный элемент на рис. 2.9). Щелчок на его имени позволяет отменить выбор текущего элемента управления в панели элементов и при этом восста- новить нормальное использование указателя мыши. Обратите внимание на отсутствие всплывающих подсказок, потому что пиктограммы панели элементов уже помечены именами элементов управления. В последующих гла- вах рассматриваются многие из этих элементов управления. Панель элементов изначально может быть скрыта под вкладкой Toolbox, расположенной в боковой части IDE (рис. 2.10, а). Перемещение указателя мыши на имя скрытого окна открывает его. Перемещение указателя мы- ши за пределы окна закрывает его. Эта функция называется автоматическим скрытием. Для "закрепления" панели элементов (т. е. отключения функции автоматического скрытия) щелкните на пиктограмме с изображе- нием кнопки в правом верхнем углу окна (рис. 2.10, б). Если окно не скрыто (автоматическое скрытие не акти- визировано) пиктограмма кнопки отобразится вертикально. Для активизирования автоматического скрытия (если оно было деактивизировано) еще раз щелкните на пиктограмме кнопки. Обратите внимание, что при активизированном автоматическом скрытии пиктограмма кнопки изображается повернутой набок, как показано на рис. 2.10.
48 Гпава 2 - Скрытое окно Пиктограмма отключения-1 г- Кнопка автоматического скрытия окна закрытия окна Указатель мыши над именем окна Рис. 2.10. Демонстрация автоматического скрытия окон: а — панель Toolbox скрыта; б — панель Toolbox появилась после наведения указателя мыши на вкладку 2.4.3. Окно Properties Окно свойств Properties, показанное на рис. 2.11, обеспечивает манипуляцию свойствами формы или элемента управления. В свойствах приведена информация об элементе управления: его размер, цвет и положение и т. д. Каждый элемент управления имеет собственный набор свойств. В нижней части окна свойств содержится опи- сание выбранного свойства. По категориям Выбор компонента По алфавиту Свойство Описание События Полоса прокрутки Текущее значение Рис. 2.11. Окно Properties В левой колонке окна Properties перечислены свойства элемента управления. В колонке справа отображены их текущие значения. Две крайние слева пиктограммы панели инструментов сортируют свойства либо в алфавит- ном порядке (при нажатии на пиктограмму Alphabetic), либо по категориям (при нажатии на пиктограмму Categorized). Это означает, что свойства организованы по группам, в зависимости от их целей. Пользователи могут просматривать список свойств путем перемещения вертикального ползунка вверх или вниз (удержанием левой кнопки мыши с курсором, установленным на полосу прокрутки). Пиктограмма Event обеспечивает управление формы в ответ на определенные действия пользователей. События описываются в главе 9. Далее в главе и на протяжении всей книги авторы демонстрируют настройку отдельных свойств. Окно Properties также представляет важность для визуального программирования. Элементы управления обыч- но настраиваются после того, как они создаются в панели инструментов. Окно Properties позволяет программи-
Введение в интегрированную среду разработки Visual Studio NET и программирование на C#49 стам визуально модифицировать элементы управления (т. е. без написания кода). Такая настройка обладает оп- ределенными преимуществами. Во-первых, программист видит, какие свойства доступны для модификации, а также возможные значения; ему не нужно искать или запоминать настройки того или иного свойства. Во-вторых, в окне приведено краткое описание каждого свойства, позволяющее программисту ознакомиться с целью каждого свойства. В-третьих, с помощью этого окна значение свойства можно установить очень быстро одним щелчком, и код писать не нужно. Все эти функции разработаны в помощь программистам при создании приложений без необходимости выполнения многих повторяющихся операций В верхней части окна Properties имеется раскрывающийся список или элемент управления, содержащий изо- бражение стрелки, указывающей вниз, при нажатии на которую раскрывается список опций. Раскрывающийся список обеспечивает выбор компонентов. В первой строке списка показан изменяемый в данный момент ком- понент. Программист может пользоваться списком для выбора компонентов с целью их редактирования. На- пример, если GUI содержит несколько кнопок, то программист может выбрать название той или иной кнопки для ее редактирования. 2.5. Использование справки В среде Visual Studio .NET имеется расширенный механизм получения справки. В меню Help содержится мно- жество разнообразных опций. Команда меню Contents отображает содержание доступных тем справки по кате- гориям. Команда Index выводит алфавитный указатель для работы пользователя. Команда Search дает пользо- вателям возможность нахождения нужных статей справки на основе нескольких ключевых слов. В каждом слу- чае подмножество (или фильтр) доступных статей может ограничить поиск статьями, относящимися только к С#. Динамическая справка (рис. 2.12) предоставляет список статей на основе текущего содержимого (т. е. для эле- ментов, расположенных вокруг указателя мыши). Для открытия динамической справки (если она не открыта) выберите команду Help | Dynamic Help. После щелчка на Visual Studio .NET в окне Dynamic Help появятся со- ответствующие статьи. В окне, помимо панели инструментов обычных функций справки, приведены нужные записи справки, примеры и информация Getting Started (перед началом работы). Динамическая справка — пре- красный способ получения информации о функциях Visual Studio .NET. Однако обратите внимание, что коман- да Dynamic Help может замедлить работу Visual Studio .NET. Окно iic Help -I - Выбранный элемент Актуальные справочные статьи для выбранного элемента Рис. 2.12. Окно Dynamic Help Совет по повышению производительности Если время реакции Visual Studio .NET снижается, то динамическую справку можно отключить нажатием кнопки закрытия (в виде значка х) в правом верхнем углу окна 4 Зак. зззз
50 Гпава 2 Помимо динамической справки, Visual Studio NET предоставляет контекстно-зависимую справку. Контекстно- зависимая справка сходна с динамической, за исключением того, что она незамедлительно выводит нужную статью справки, вместо целого их списка. Для использования контекстно-зависимой справки выберите элемент и нажмите клавишу <F1>. Справка будет представлена внутренним или внешним образом. При внешней справке нужная статья сразу появляется в отдельном окне за пределами IDE. При внутренней справке статья выводится в окне с вкладкой в Visual Studio .NET. Опции справки настраиваются в разделе Му Profile страницы запуска Start Page. 2.6. Простая программа: отображение текста и графики В данном разделе будет рассмотрено создание программы, отображающей текст "Welcome to С#!" и картинку. Данная программа состоит из одной формы, использующей метку для вывода текста и рамку для отображения графики. На рис. 2.13 показан внешний вид программы при ее выполнении. Приведенный пример (а также ис- пользованный графический файл) доступен на Web-сайте (www.deitel.com) в ссылке Downloads/Resources. Тип проекта Имя проекта Щелкните кнопку, чтобы изменить размещение проекта Рис. 2.13. Простая программа в процессе выполнения L Размещение проекта Рис. 2.14. Создание нового Windows-приложения При создании программы не пишется ни одной строки программного кода. Вместо этого используются методи- ки визуального программирования. Различные действия программиста (например, использование мыши для выделения, щелчков, перетягивания и размещения элементов) обеспечивают Visual Studio .NET достаточным количеством информации для создания целого программного кода или его большей части. В данной главе авто- ры обсуждают простой программный код, а в книге в целом описывается создание более значительных и мощ- ных программ. Визуальное программирование на С#, как правило, включает в себя комбинацию написания час- ти программного кода и применение Visual Studio .NET для генерирования оставшейся части кода. Для создания, запуска и остановки первой программы выполните следующие шаги: 1. Создание нового проекта. Если проект уже открыт, закройте его выбором команд File | Close Solution. Мо- жет появиться диалоговое окно с запросом о сохранении текущего решения; сохраните все внесенные изме- нения. Затем создайте новое Windows-приложение для программы открытием Visual Studio .NET и выбором команд File | New | Project... | Visual C# Projects | Windows Application (рис. 2.14). Присвойте проекту имя ASimpleProject и выберите каталог для сохранения проекта. Для выполнения последнего шага нажмите кнопку Browse..., которая откроет диалоговое окно Project Location, показанное на рис. 2.15. Просмотром каталогов выберите один для размещения проекта и нажмите кнопку ОК. Эта выборка возвращает пользова- теля в диалоговое окно New Project; выбранная папка появится в текстовом поле Location. Когда проект бу- дет размещен в нужном месте, нажмите кнопку OK. Visual Studio NET загрузит новое решение, и появится форма с именем Forml. Пример этой формы показан на рис. 2.3. 2. Изменение текста в заголовке формы. Во-первых, задайте текст, который появляется в строке заголовка. Этот текст определяется свойством Text формы (рис. 2.16). Если окно Properties формы не открыто, щелк- ните на кнопке Properties в панели инструментов или выберите команду Properties Window в меню View. Для выбора формы воспользуйтесь мышью; в окне Properties показана информация о текущем выбранном элементе. В этом окне щелкните в поле справа от поля свойства Text. Для указания значения свойства Text введите нужное значение в поле. В нашем случае введите a simple Program, как показано на рис. 2.16. По окончании нажмите клавишу <Enter> для обновления строки заголовка формы в области проектирования
51 Введение в интегрированную среду разработки Visual Studio .NET и программирование на C# / Выбранная папка для размещения проекта Щелкните кнопку, чтобы задать - размещение проекта Рис. 2.15. Указание местоположения проекта Выбранное свойство Описание свойства Имя и тип объекта Значение свойства Рис. 2.16. Настройка свойства Text формы 3. Изменение размера формы. Щелкните на одном из активизированных маркеров размеров формы (маленькие квадратики вокруг формы, показанные на рис. 2.17) и перетяните ее на другое место для изменения размера формы. Активизированные маркеры размеров имеют белый цвет, а курсор мыши меняет свой вид, когда на- ходится на активизированном маркере. Деактивизированные маркеры размеров имеют серый цвет. Сетка на форме используется для выравнивания элементов управления и не отображается при выполнении про- граммы. Неактивный маркер (серый) изменения размера формы Заголовок Сетка Указатель мыши над маркером (белым) изменения размера Рис. 2.17. Форма с маркерами размеров 4. Изменение фонового цвета формы. Свойство BackColor задает фоновый цвет формы или элемента управле- ния. При щелчке по строке BackColor в окне Properties рядом со значением свойства появляется кнопка со стрелкой (рис. 2.18). При нажатии на нее открываются другие опции (они варьируются, в зависимости от свойства). В данном случае появляются вкладки System (по умолчанию), Web и Custom. При щелчке по вкладке Custom отображается палитра для выбора цветов. Выберите желтый цвет. Палитра исчезнет, а цвет фона формы изменится на желтый.
52 Гпава 2 Палитра Custom 0,0 д.0 !ЙИ! {form! byjtem.Wrdows.Forms.Form True False । AutoScale I AutoScrol so Autobcn_tagin Ы AutoScroMlJm 255,128 untextMenu [BadcCoipt’ Текущий цвет rWTTTTsr ГШГХП~ГПл .__ ..ГИОгМга । ''а^ииг'ий'Жгй'и.и я"" Кнопка co стрелкой CauserVafctabor а Рис. 2.18. Изменение свойства BackColor 5. Добавление метки в форму. Дважды щелкните кнопкой мыши на элементе управления Label в панели эле- ментов. Эта операция создает метку с маркерами размеров в верхнем левом углу формы (рис. 2.19). Двойной щелчок мышью на любом элементе управления в панели Toolbox размещает этот элемент в форме, либо программист может попросту перетягивать элементы управления из панели элементов в форму. Метки ото- бражают текст; в данном случае по умолчанию отображается слово labeli. Обратите внимание, что метка имеет тот же цвет, что и фон формы, потому что цвет фона формы является цветом фона по умолчанию для элементов управления, добавляемых в форму. Метка Новый цвет фона формы Рис. 2.19. Добавление в форму новой метки Метка с обновленным свойством Text размещена по центру формы Рис. 2.20. Метка в нужной позиции с заданным свойством Text 6. Ввод текста метки. Выберите метку так, чтобы ее свойства появились в окне Properties. Свойство Text мет- ки определяет текст (если он есть), который отображает эта метка. Форма и метка имеют свои отдельные свойства Text. Формы и элементы управления могут иметь одни и те же типы свойств. Читатели увидят, что многие элементы управления имеют общие названия свойств. Измените свойство Text метки на Welcome 'to c#!, как показано на рис. 2.20. Измените размер метки (с помощью маркеров размеров), если текст не поме- щается. Разместите метку вверху по центру формы перетягиванием, либо с помощью клавиш со стрелками. Метку также можно переместить выбором команд Format | Center In Form | Horizontally из меню. 7. Задание размера шрифта метки и выравнивание текста. При щелчке по свойству Font справа в строке появ- ляется кнопка..., с помощью которой можно изменить значение этого свойства (рис. 2.21). Кнопка... указы- вает на появление диалогового окна, когда программист нажимает эту кнопку. После нажатия кнопки откры- вается окно Font, показанное на рис. 2.22. Пользователь может выбрать в этом окне название гарнитуры шрифта (Microsoft Sans Serif, Arial и т. д.), стиль гарнитуры (Regular— обычная, Bold — полужирная и раз- мер— 8, 10 пт и т. д.) и т. д. Текст в области Sample отображает выбранную гарнитуру. В списке Size выбе- рите 24 и нажмите кнопку ОК. Если текст не умещается на одной строке, то он перенесется на следующую. Измените размер метки, если текст в ней не помещается. После этого выберите свойство TextAiign метки, определяющее выравнивание текста в метке. Появится сет- ка "три на три" выбора выравнивания, соответствующая тому, в каком месте метки разместится текст (рис. 2.23). Выберите центральный верхний элемент сетки, чтобы текст появился в верхней части метки по центру.
Введение в интегрированную среду разработки Visual Studio .NET и программирование на C# 53 Кнопка позволяет отобразить диалоговое окно настройки шрифта Рис. 2.21. Окно Properties, отображающее свойства метки Пример шрифта Рис. 2.22. Окно Font для выбора гарнитур, стилей и размеров Опции выравнивания текста Опция выравнивания TopCenter Рис. 2.23. Центрирование текста в метке 8. Добавление элемента управления PictureBox для размещения картинки в форме. Данный шаг похож на шаг 5. Найдите элемент управления PictureBox в панели элементов и добавьте его в форму. Переместите его под метку перетягиванием или с помощью клавиш-стрелок (рис. 2.24). Добавленный в форму элемент управления PictureBox Обновленная метка Рис. 2.24. Вставка и выравнивание будущей картинки 9. Вставка изображения. Щелкните по вставленному элементу PictureBox для загрузки его свойств в окно Properties и найдите свойство image. Это свойство показывает предварительное изображение текущей гра- фики. Поскольку ни одно изображение выбрано не было, поэтому свойство image ничего не отображает (попе) — рис. 2.25. Щелкните кнопку ... для открытия диалогового окна Open (рис. 2.26). Найдите изобра-
54 Глава 2 жение для вставки, выделите его и нажмите клавишу <Enter>. Среди форматов изображений — PNG (Port- able Network Graphics, переносимая сетевая графика), GIF (Graphics Interchange Format, формат графического обмена) и JPEG (Joint Photographic Experts Group, объединенная группа экспертов в области фотографии). Каждый из этих форматов поддерживается в Интернете. Для создания нового изображения необходимо вос- пользоваться программным обеспечением редактирования графики, например Jasc Paint Shop Pro, Adobe Photoshop или Microsoft Paint. В нашем случае будет использоваться изображение ASimpleProgramlmage.png, имеющееся с этим примером на Web-сайте www.deitel.com. После указания изображения элемент управле- ния PictureBox изменит свой размер (в зависимости от реального размера изображения), а свойство image позволит увидеть само изображение. Для показа изображения целиком измените размер рамки перетягива- нием маркеров размеров (рис. 2.27). Значение свойства Image (изображение не выбрано) Рис. 2.25. Свойство Image элемента управления PictureBox Рис. 2.26. Выбор изображения для PictureBox Новое вставленное изображение (после того, как был изменен размер PictureBox) Рис. 2.27. Картинка после вставки изображения 10. Сохранение проекта. Выберите команду File | Save АП для сохранения всего решения целиком. Для сохра- нения отдельного файла отметьте его в окне Solution Explorer и выберите команду File | Save. Созданная программа сохраняет исходный код в файле C# Forml.cs. Файл проекта содержит имена и местоположения всех файлов в проекте. Выбором команды Save АП сохраняется как проект, так и файл С#. 11. Запуск проекта. До выполнения этого шага мы работали в режиме проектирования (т. е. создаваемая про- грамма не выполняется). На этот режим указывает текст Microsoft Visual C# .NET (design] в строке заго- ловка. Находясь в режиме проектирования, программисты имеют доступ ко всем окнам среды (т. е. Toolbox и Properties), меню, панелям инструментов и т. д. Однако в режиме запуска программа выполняется, и пользователи могут взаимодействовать только с некоторыми функциями IDE. Недоступные функции деак- тивизируются или отображаются серым цветом. Текст Forml.cs [Design] в строке заголовка означает то, что форма разрабатывается визуально, без программирования кода. Если бы писался код, тогда строка заго- ловка содержала бы только текст Forml.cs. Для выполнения или запуска программы ее сначала необходимо откомпилировать выбором команды Build Solution в меню Build, либо нажатием комбинации клавиш <Ctrl>+<Shift>+<B>. После этого программу можно запустить нажатием кнопки Start, выбором команд Debug | Start или нажатием клавиши <F5>. На рис. 2.28 интегрированная среда,разработки показана в ре- , жиме запуска. Обратите внимание на то, что в строке заголовка IDE отображается слово (run|, и что многие пиктограммы панели инструментов деактивизированы. > 12. Остановка выполнения. Для остановки программы щелкните кнопку Close (значок х в верхнем правом уг- лу) текущего приложения, либо нажмите кнопку End на панели инструментов, либо нажмите комбинацию клавиш <Shift>+<F5>. Любое из этих действий остановит выполнение программы и введет IDE в режим разработки.
Введение в интегрированную среду разработки Visual Studio .NET и программирование на C# 55 г Кнопка - Режим запуска г Режим разработки г Кнопка End L Запущенное приложение L Форма в режиме разработки Рис. 2.28. IDE в режиме выполнения с работающим приложением на переднем плане Замечание по технологии программирования____________________________________________ Визуальное программирование — более простой и быстрый метод, нежели написание кодов. Замечание по технологии программирования____________________________________________ Создание большинства программ требует больше, нежели простого визуального программирования. В таких программах некоторые коды должны быть написаны разработчиком. В число примеров входят приложения, в ко- торых используются обработчики событий (для реагирования на действия пользователя), базы данных, защита, организация работы сетей, редактирование текста графики и мультимедиа 2.7. Простая программа: печать строки текста Теперь обсудим не визуальное или традиционное программирование. Продемонстрируем создание программ только с помощью написания кода. По представленному материалу станет понятно, что программирование на C#— это смесь двух стилей: визуальное программирование позволяет программистам разрабатывать GUI и избегать выполнения рутинных задач, тогда как традиционное программирование задает поведение программы. Начнем с рассмотрения программы, отображающей строку текста. Эта программа и ее выходные данные пока- заны в листинге 2.1. При выполнении программа выдает текст в консольном окне. Данная программа иллюстрирует несколько важных особенностей С#. Строка 10 (см. листинг 2.1) выполняет "фактическую работу" программы, отображая фразу "Welcome to C# Programming!". Строка 1 начинается с символов //, указывая на то, что остаток строки — это комментарий. Комментарий, на- чинающийся с //, называется однострочным, потому что комментарий прерывается в конце строки. Также су- ществует синтаксис для написания многострочных комментариев Многострочный комментарий, например /* Это многострочный комментарий, который можно разбить на несколько строк*/ начинается с разделителя /* и заканчивается разделителем */. Весь текст между этими разграничителями рас- сматривается, как комментарий, и игнорируется компилятором. В IDE Visual Studio .NET все комментарии вы- делены зеленым цветом. Замечание по технологии программирования_______________________________________ По мере создания программы Visual Studio NET будет часто находить синтаксические ошибки, даже до этапа компиляции. Синтаксическая ошибка имеет место, когда компилятор не может распознать утверждение Ищите "рваные" красные строки, которые появляются сразу под синтаксической ошибкой
56 Гпава 2 В C# используется тот же синтаксис, что и в языке программирования С для многострочных комментариев (/*.. .*/), и тот же синтаксис, что и в C++ для однострочных комментариев (//). Программисты, работающие с С#, обычно используют однострочные комментарии стиля C++, вместо комментариев стиля С. В данной книге в основном используются однострочные комментарии стиля C++. I Листинг 2.1. Первая программа на C# 1 // Листинг 2.1: Welcomel.cs 2 // Первая программа на C# 3 4 using System; 5 6 class Welcomel 7 { 8 static void Main(string[] args) 9 { 10 Console.WriteLine("Welcome to C# Programming!"); 11 } 12 } Строка 4 (называется оператором using) добавляется в код IDE Visual Studio .NET и объявляет, что программа использует функции в пространстве имен System. Пространство имен группирует различные особенности C# в связные между собой категории. Одной из самых сильных сторон C# является то, что программисты могут пользоваться богатым набором пространств имен, предоставляемым .NET Framework. Эти пространства имен содержат код, который можно использовать многократно, вместо того, чтобы "изобретать колесо". В результате программировать становится проще и быстрее. Пространства имен, определенные в .NET Framework, содержат заранее существующий код, называемый библиотекой классов .NET Framework. Примером одного из членов пространства имен System является Console, который сейчас будет рассмотрена. Различные функции организо- ваны в пространстве имен, что позволяет программистам легко находить их местоположение. В книге будет описано много пространств имен и их членов. Строки 6—12 определяют первый класс (собирательно эти строки называются определением класса). Програм- мы C# состоят из частей, называемых классами, являющимися логическими группировками членов (например, методов), упрощающих организацию программы. Эти методы (подобные функциям в процедурных языках про- граммирования) выполняют задачи и возвращают информацию по завершении этих задач. Любая программа C# состоит из классов и методов, созданных программистом, и из заранее существующих классов, имеющихся в библиотеке классов .NET Framework. В книге будет рассматриваться использование в программах обоих типов классов. Каждая программа C# состоит, как минимум, из одного определения классов, выполняемых программистом. Эти классы называются классами, определенными программистом. В главе 5 рассматриваются программы, со- держащие множественные классы, определенные программистом. Ключевое слово class начинает определение класса в С#, за которым сразу следует имя класса (в нашем примере — Welcomel) Ключевые (или зарезервиро- ванные) слова резервируются для использования C# и всегда пишутся строчными буквами. (Полный перечень ключевых слов C# представлен в главе 3.) Условно каждое слово в названии класса начинается с прописной буквы, и каждое дополнительное слово в названии класса также начинается с прописной буквы (например, SampleClassName). Имя класса называется идентификатором, представляющим собой серию символов, которая состоит из букв, цифр, символов подчеркивания (_) и символов @. Идентификаторы не могут начинаться с цифр и содержать пробелы. Примерами действенных идентификаторов являются Welcomel, value, m inputSField и button?. Название 7button не является действительным идентификатором, потому что оно начинается с цифры, а название input field также не является действительным идентификатором, потому что оно содержит пробел. Символ @ может использоваться только как первый символ в идентификаторе. Язык C# является регистрозави- симым, т. е. прописные и строчные буквы рассматриваются как разные буквы, поэтому, например, al и ai — разные идентификаторы О хорошем стиле программирования___________________________________________________________ Название класса всегда следует писать с первой прописной буквы. При этом становится проще идентифициро- вать названия классов. Левая фигурная скобка ({) в строке 7 начинает тело определения класса. Соответственно, правая фигурная скобка (}) в строке 12 заканчивает определение класса. Обратите внимание, что строки 8—11 в теле класса пи- шутся с отступами.
Введение в интегрированную среду разработки Visual Studio .NETи программирование на C# 57 Строка 8 присутствует во всех консольных и Windows-приложениях. Эти приложения начинают выполнение в Main, называемом точкой входа программы. Круглые скобки после Main указывают на то, что Main — это стан- дартный блок, называемый методом. Обычно определения классов C# содержат один или более методов, а приложения C# — один или более классов. Для консольного или Windows-приложения C# только один из мето- дов должен называться Main, и он должен быть определен так, как показано в строке 8, иначе программа не бу- дет выполняться. Левая фигурная скобка ({) в строке 9 начинает тело определения метода (кода, который будет выполняться, как часть программы). Соответствующая правая фигурная скобка (}) прерывает тело определения метода (стро- ка 11). Обратите внимание, что строка в теле метода пишется с отступом между этими скобками (так задается корректный стиль программирования). О хорошем стиле программирования___________________________________________________________ Все тело определения метода отступается на один "уровень” между левой и правой фигурными скобками, опре- деляющими тело метода. Такое оформление выделяет структуру метода, что облегчает "читаемость" определе- ния метода. Строка 10 дает компьютеру команду на выполнение операции, т. е. на выход серии символов, содержащихся между двойными кавычками. Разделенные таким образом символы называются строками, строками символов или строковыми литералами. Обычно символы между двойными кавычками называются просто строками. Символы табуляции в строках имеют важное значение: компилятор не игнорирует эти символы, когда они по- являются в строках Класс Console дает программе возможность вывода информации на стандартное устройство вывода компью- тера — экран монитора. Класс Console также обеспечивает метод, позволяющий программам C# отображать строки и другие типы информации в консольном окне. Метод Console .WriteLine отображает (или печатает) строку текста в консольном окне. Когда Console.WriteLine завершает выполнение задачи, он помещает курсор вывода (место, в котором будет отобра- жен следующий символ) в начало текстовой строки в консольном окне. Вся строка 10, включая Console.WriteLine, его аргумент В круглых скобках ("Welcome to C# Programming!") И точку с запятой (;), называется оператором. Каждый оператор должен заканчиваться точкой с запятой. Когда этот оператор выполняется, он отображает сообщение "Welcome to c# Programming!” в консольном окне (см. листинг 2.1). В операторах C# обычно названию каждого класса предшествует его пространство имени и период. Например, для того чтобы программа выполнялась корректно, строка 10 должна быть написана так: System.Console.WriteLine("Welcome to C# Programming!"); Однако директива using в строке 4 устраняет необходимость явного задания пространства имени System при использовании классов в этом пространстве имени. Такая практика экономит время и не дает программисту запутаться. Распространенная ошибка программирования___________________________________________________ Отсутствие точки с запятой в конце оператора является синтаксической ошибкой. Совет по тестированию и отладке_______________________________________________________ Когда компилятор сообщает о синтаксической ошибке то ее может и не быть в строке, указанной сообщением об ошибке. Во-первых, проверьте строку, в которой сообщена ошибка Если в этой строке синтаксических оши- бок нет, проверьте строки, предшествующие той, о наличии ошибки в которой сообщено. Теперь после представления программы рассмотрим по шагам процесс ее запуска и выполнения в Visual Stu- dio NET. 1. Создание консольного приложения. В меню File выберите команды New) Project... Появится диалоговое окно. В левой части выберите Visual C# Projects; в правой части — Console Application. В нижней части данного диалогового окна можно указать другую информацию о проекте (местоположение и название про- екта). После ввода всей необходимой информации нажмите кнопку ОК для создания проекта Далее для ре- дактирования откроется окно кода. Новое приложение показано на рис. 2.29. Обратите внимание, что пре- дыдущее приложение создавалось точно так же, за исключением того, что теперь создается консольное, а не Windows-приложение. Это приложение можно скомпоновать (скомпилировать) и выполнить, но ничего не будет сделано до добавления большей части кода (выполняется в шаге 3). Рассмотрим кратко код, созданный IDE. Обратите внимание, что в данном коде содержатся некоторые мо- менты, не описанные ранее. Мы не обсуждали эти особенности, чтобы не затруднять описание кода про-
58 Глава 2 граммы на данном этапе. Большая часть дополнительного кода, предоставляемого IDE, используется либо для документирования, либо в помощь при создании графических пользовательских интерфейсов. Читатель наверняка заметит, что авторы не показывают строки непосредственно над и под определением классов. Эти строки используются для создания пространства имен, и данная тема рассматривается в главе 5. Примечание________________________________________________________________________________ Несколько раз ранее мы просили читателей имитировать представляемые особенности С#. Это делается осо- бенно тогда, когда еще не важно знать все подробности функции для того, чтобы пользоваться ею в С#. Про- граммисты часто учатся, имитируя или воспроизводя то, что уже было сделано их коллегами. Код для всех при- меров книги имеется на Web-сайте www.deitel.com под ссылкой Downloads/Resources. Рис. 2.29. Консольное приложение, сгенерированное Visual Studio NET 2. Изменение имени файла кода. Для программ, рассматриваемых в книге, как правило, будет изменяться имя файла кода. По умолчанию имя файла — Classl.cs. Это имя можно изменить щелчком правой кнопки мыши на имени файла в окне Solution Explorer и выбором команды Rename в контекстном меню. После этого чи- татель может ввести новое имя файла, при условии, что оно заканчивается на cs (расширение файла, содер- жащего код на С#). 3. Завершение кода. В текстовом редакторе замените комментарий // // TODO: добавьте код для начала приложения // размещенный в рамках метода Main, на строку 10 из листинга 2.1 (этот комментарий не нужен, потому что в программу добавляется код). 4. Запуск программы. Теперь все готово для компиляции и исполнения программы. Для этого выполним те же шаги, что и для примера, описанного в начале главы. Для компилирования программы в меню Build и выбе- рите команду Build Solution. Если программа не содержит синтаксических ошибок, то будет создан новый файл с именем Welcomel.exe, содержащий код MSIL (промежуточный язык Microsoft) приложения. Для ис- полнения программы выберите опцию Start Without Debugging1 в меню Debug. Выполнение программы начинается с метода Main, являющегося точкой входа в программу. Затем оператор в строке 10 метода маш отображает приглашение "Welcome to c# Programming! ”. На рис. 2.30 показан резуль- тат выполнения программы. Выбор команды Debug | Start Without Debugging вызывает подсказку командного окна пользователю о нажатии любой клавиши после остановки программы, что позволяет наблюдать за выходными данными программы. Напротив, если запустить программу с помощью команды Debug | Start, как было сделано для Windows-приложения в данной главе ранее, откроется командное окно, программа отобразит сообщение "Welcome to C# Programming", после чего командное окно сразу закроется.
Введение в интегрированную среду разработки Visual Studio NET и программирование на C# 59 ( Atjc.uk A. 002\ckplep1\c-pfepl ,exanple*\chO2\Fie0? 30\Welc®mLlUnn\Debuy\We)uome1 хе ’ ;•<> г Welcome to C# Prograrwiingt pi Fri ss any key to continue. >_<4 Рис. 2.30. Выполнение программы Welcomel Несмотря на то, что представленная программа отображает результат своей работы в консольном окне, боль- шинство приложений C# использует окна (диалоговые) для отображения выводимых данных. Для создания диа- логовых окон библиотека классов .NET Framework включает в себя класс MessageBox. Класс MessageBox опреде- лен в пространстве имен System.windows.Forms. Программа в листинге 2.2 отображает ту же строку, что и в листинге 2.1, но в диалоговом окне сообщения с помощью MessageBox. 1 // Листинг 2.2: Welcome2.cs 2 // Печать нескольких строк в диалоговом окне 3 4 us Lng System; 5 using System.Windows.Forms; 6 7 class Welcome2 8 { 9 static void Ma in(string [J args) 10 { 11 MessageBox.Show("Welcome\nto\nC#\nprogramming!"); 12 ) 13 } Рис. 2.31. Отображение нескольких строк в диалоговом окне В данном примере каждое слово в диалоговом окне выводится на отдельной строке (рис. 2 31). В строке 11 соз- дается четыре отдельных строки текста в дйалоговом окне. Обычно символы в строке отображаются точно так же, как они появляются между двойными кавычками. Однако обратите внимание, что два символа \ и п на экра- не не появляются. Обратная косая или обратный слэш (\) называется знаком переключения кода. Он указывает на то, что выходом будет "особый" символ. При появлении в строке символа обратной косой следующий сим- вол комбинируется с ней для образования последовательности переключения кода. Последовательность пере- ключения кода \п — символ новой строки. Он заставляет курсор (указатель текущего положения на экране) пе- ремещаться в начало следующей строки в консольном окне. Некоторые распространенные последовательности переключения кода перечислены в табл. 2.2. Таблица 2.2. Некоторые распространенные последовательности переключения кодов Последовательность переключения кода Описание \п Новая строка. Перемещение курсора на экране в начало следующей строки \t Горизонтальная табуляция. Перемещение курсора на экране на следующую позицию та- буляции \г Возврат каретки Перемещение курсора на экране в начало текущей строки; перехода на следующую строку не происходит Любые выходные символы после возврата каретки пе- резаписывают предыдущий выход символов на этой строке \\ Обратная косая. Печать символа обратной косой черты (\) \" Двойные кавычки. Печать символа двойных кавычек (") На многие скомпилированные классы в C# (включая MessageBox) не нужно делать ссылок или задавать их как часть проекта до того, как они будут использованы в программе. В зависимости от типа создаваемого про- граммного приложения, классы можно компилировать в файлы с расширением ехе (исполняемый), dll (динами- чески подсоединяемая библиотека) или с несколькими другими. Такие файлы называются скомпонованными блоками и являются блоками пакетирования для кода в С#. Примечание Скомпонованные блоки могут состоять из множества файлов нескольких различных типов.
60 Гпава 2 Пространства имен объединяют связанные классы между собой; скомпонованный блок — это пакет, содержа- щий код MS1L, в котором компилируется проект, а также любая другая необходимая для этих классов информа- ция. Скомпонованный блок, на который необходимо сделать ссылку, можно найти в документации Visual Studio .NET (также называется документацией MSDN) для класса, который необходимо использовать. Самым простым способом доступа к этой^информации является выход в меню Help в Visual Studio .NET и выбор команды Index. После этого нужно ввести название класса, о котором необходимо получить информацию. Класс MessageBox расположен в скомпонованном блоке System Windows.Forms.dll. В этот скомпонованный блок необходимо до- бавить ссылку для использования в программе класса MessageBox. Теперь рассмотрим пример добавления ссыл- ки в System.Windows.Forms в рамках IDE. Распространенная ошибка программирования____________________________________________________ Результатом включения пространства имен с директивой using, но не добавление ссылки на соответствующий скомпонованный блок, становится ошибка компилирования Для начала убедитесь в том, что приложение открыто. Выберите команду Add Reference... в меню Project или щелкните правой кнопкой мыши на папке References в окне Solution Explorer и выберите команду Add Refer- ence... в контекстном меню. При этом откроется диалоговое окно Add Reference (рис. 2.32). Дважды щелкните на файле System.Windows.Forms.dll для добавления его в список Selected Components в нижней части диалого- вого окна. После этого нажмите кнопку ОК. Обратите внимание, что теперь System.windows.Forms появляется в папке Reference окна Solution Explorer (рис. 2.32). SjntemXrridi VSLartf’r, ViMacroHrererchyL-b CAWlNNT\Miaosoft.NET\Fre C:\W1NNT\M icrosoftNElAFra r\WlNNT\Microsoft.NET\Fra.. C:\WfNNT\Miaosoft NETVia C:\WINNT\Mictosofl.NE T\Fia. C:\WtNNT\MicKJtoft NET\Fia. C \WlNNTWtaosoftNET\F<a SyriamRuVime Rending System.Ru ame SmafaaT "nF. System Sect-..у SystemServcaProcess di - SystemWeb.rffl System. WabReguiarExpressio SystemWabSerndes di 70.3300.0 C:\ProoramFiles\MicrosoftVfs 7.0.3300.0 C:\ProgiamFies\MicrosoitVts 1-МвЙу» 1.0.3300.0 1.03300.0 1.03300.0 1.0 3300.0 1.0.3300.0 1.0.3300.0 Sthd-iCtyryonentx ' Ccr^tNaH»' '7 ...... | lype | Scxacs - ' : | SMtaniVndbMftxtMa fc-'JIEF CVWINNTSMKWdt.NETVrarr ~---------------------------- ^Welcome? — Окно Solution Explorer Add Reference Диалоговое окно Add Reference Папка References — KJ System •O SystemDjta . System.Windows Forms KJ >ystam>WL „ App.ico A*sembMn1o.cs Q Wdcome4.cs Ссылка System. Windows. Forms Рис. 2.32. Добавление ссылки в скомпонованный блок в Visual Studio NET После ссылки на нужный скомпонованный блок и указания директивы using для соответствующего простран- ства имен (строка 5) в этом пространстве имен можно использовать классы (например, MessageBox). Читатели наверняка заметили, что'в предыдущих программах не добавлялось никаких ссылок. Visual Studio .NET добавляет несколько ссылок при создании проекта. Также по умолчанию некоторые скомпонованные бло- ки не требуют ссылок. Например, класс Console расположен в скомпонованном блоке mscorlib.dll, но для ис- пользования этого блока ссылка не требуется. Пространство имен System.Windows.Forms содержит много классов, помогающих программистам на C# опреде- лять графические пользовательские интерфейсы (GUI) для их приложений. Компоненты GUI (например, кноп- ки) упрощают как ввод данных пользователем, так и форматирование или представление выходных данных пользователю. Например, на рис. 2.33 показано окно Internet Explorer со строкой меню (File, Edit, View и т. д.). Под строкой меню имеется несколько кнопок, для каждой из которых в Internet Explorer определена своя задача.
Введение в интегрированную среду разработки Visual Studio .NET и программирование на C# 61 Под кнопками размешается поле раскрывающегося списка, где пользователь может ввести адрес Web-сайта, который он хочет посетить. Слева от раскрывающегося списка имеется метка, указывающая на его предназна- чение. Меню, кнопки, раскрывающиеся списки и метки являются частью графического интерфейса Internet Ex- plorer. Они обеспечивают взаимодействие пользователя с Internet Explorer. C# содержит классы, создающие описываемые здесь компоненты GUI. Другие классы, создающие компоненты GUI, описаны в главах 9 и 10 со- ответственно. p Метка p Кнопка 1__________L________ Пункт [- Раскрывающийся меню | список p Строка меню __ 1_______________________________ i DEITEL”* Home 3age Mictoaoll Intnnet Exploits iod. Hev ДЫ.1 » | J Sparer , 4rwp<*y : .J Media f Http7/www. Melcom Link*' iGotoTXIpV/w June w.2002 Sisn up for th* DeTTEL BUZZ OnUHE ♦-meil Newsletter! DEITEL’________________ ... ________ Home | Book Store | i Io . j - _u«- e I ( orporate Training I Whafs Nrw I FftOs Register Mew Titles Announcements Рис. 2.32. Графический пользовательский интерфейс Internet Explorer В методе Main (см. листинг 2.2) строка 11 вызывает метод show класса MessageBox. Этот метод берет строку в качестве аргумента и отображает ее пользователю в диалоговом окне сообщений. Метод Show называется ста- тическим методом. Такие методы вызываются с помощью имен их классов (в данном случае MessageBox), по- сле чего следует оператор точка (.) и название метода (в данном случае Show). Статические методы рассматри- ваются в главе 5. С помощью строки 11 отображается диалоговое окно, показанное на рис. 2.34. В том же окне располагается кнопка ОК, дающая пользователю возможность закрыть диалоговое окно. Кнопка ОК позволяет закрыть диалоговое окно Кнопка закрытия окна Размер диалогового окна подстраивается под размещенные в нем данные l-Указатель мыши Рис. 2.34. Отображенное диалоговое окно вызовом MessageBox. Show C# обеспечивает разбиение больших операторов по нескольким строкам. Например, оператор в строке 11 мож- но разбить на следующие две строки: MessageBox.Show ( "Welcome\nto\nC#\nprograniining! "); Все операторы заканчиваются точкой с запятой (;), поэтому программа-компилятор распознает, что эти две строки представляют только один оператор. Однако оператор нельзя разбить в середине идентификатора (т. е. имени класса) или строки. Распространенная ошибка программирования_____________________________________________________ Разбиение оператора в середине идентификатора или строки является синтаксической ошибкой Пользователь может закрыть диалоговое окно нажатием кнопки ОК или кнопки закрытия. После этой операции выполнение программы прекращается, потому что прекращается работа метода Main.
62 Глава 2 2.8. Арифметика Большинство разработчиков программирует выполнение арифметических вычислений. В табл. 2.3 перечислены арифметические операции, используемые в программах. Обратите внимание на то, что различные особые сим- волы не используются в алгебре. Символ звездочки (*) обозначает умножение, знак процентов (%) — деление по модулю. Арифметические операции, показанные в табл. 2.3, являются бинарными, потому что каждая из них требует двух операндов. Например, высказывание sum+value содержит бинарную операцию + и два операнда — sum И value. / C# применяет операции в арифметических высказываниях в точной последовательности, определяемой сле- дующими правилами операторного предшествования, которые, в общем, такие же, как и в алгебре. 1. Операции в высказываниях, заключенных в пары круглых скобок, оцениваются в первую очередь. Таким об- разом, скобки можно использовать для установки порядка оценки в любой последовательности по выбору программиста. Скобки устанавливают самый высокий уровень предшествования. С помощью вложенных скобок операции в самых внутренних скобках применяются первыми. 2. Следующими применяются операции умножения, деления и модульные. Если высказывание содержит не- сколько операций умножения, деления и модульных, то они применяются слева направо. Операции умноже- ния, деления и модульные имеют один уровень предшествования. 3. Операции сложения, вычитания применяются в последнюю очередь. Если высказывание содержит несколь- ко операций сложения и вычитания, то они применяются слева направо. Сложение и вычитание имеют один уровень предшествования. Правила предшествования операций позволяют C# применять операции в корректном порядке. Когда говорит- ся, что операции применяются слева направо, имеется в виду их ассоциативность. При наличии нескольких операций, которые имеют одинаковый уровень предшествования, ассоциативность определяет порядок приме- нения операций. Позже читателю станет понятно, что некоторые операции ассоциируются справа налево. В табл. 2.4 приведены правила операторного предшествования. По мере представления дополнительных опера- ций C# в последующих главах таблица будет расширяться. Полный список операторного предшествования представлен в приложении 1. Таблица 2.3. Арифметические операции Название операции C# Арифметическая операция Алгебраическое высказывание Высказывание C# Сложение + f+7 f + 7 Вычитание - р-с р-с Умножение * bm b * m Деление / х!у или — или х + у У х / у Деление по модулю % г mod s Г % S Таблица 2.4. Приоритет выполнения арифметических операций Операция Название Порядок оценки (предшествования) ( ) Скобки Оцениваются в первую очередь. Если скобки вложенные, тогда первым оценивается высказывание в самых внутренних скобках. При наличии нескольких пар скобок на одном уровне (т. е. скобки не вложенные) они оцениваются слева направо *, / или % Умножение, деление или деление по модулю Оцениваются во вторую очередь. При наличии нескольких таких опе- раций они оцениваются слева направо + или - Сложение или вычитание Оцениваются в последнюю очередь. При наличии нескольких таких операций они оцениваются слева направо
Введение в интегрированную среду разработки Visual Studio .NET и программирование на C# 63 2.9. Принятие решений: операции отношения и равенства В данном разделе представлен оператор if языка С#, позволяющий программе принимать решение на основа- нии истинности или ложности некоторого условия. Если условие соблюдается (т. е. условие истинно), тогда оператор в теле оператора if выполняется. Если условие не соблюдается (т. е. оно ложно), тогда тело оператора не выполняется. Условия в операторе if могут формироваться с помощью операций равенства и операций от- ношения, перечисленных в табл. 2.5. Все операции отношения имеют один уровень предшествования и ассо- циируются слева направо. Операции равенства имеют один уровень предшествования, которые ниже, чем предшествование реляционных операций. Операции равенства также ассоциируются слева направо. » Распространенная ошибка программирования________________________________________________ Если операции ==, ! =, >= и <= содержат пробелы между символами (как в = =, ! =, > = и < =, соответственно), то это будет синтаксической ошибкой. Таблица 2.5. Операции отношения и сравнения Стандартная алгебраическая операция сравнения или отношения Операция сравнения или отношения C# Пример условия C# Значение условия C# Операции сравнения == X = у х равно у * i = X != у х не равно у Операции отношения > X > У х больше у < < X < У х меньше у £ >= X >= У х больше или равно у < <- X <= У х меньше или равно у Распространенная ошибка программирования______________________________________ Обращение операций !=, >= и <= (как в =!, => и =<, соответственно) является синтаксической ошибкой. Распространенная ошибка программирования___________________________________________________ Если перепутать операцию сравнения (==) с операцией присваивания (=), то это будет логической ошибкой. Операция сравнения должна читаться как "равно чему-либо", а операция присваивания должна читаться как "получает" или "получает значение...". Некоторые предпочитают читать операцию равенства "двойной знак рав- но" или "равно равно". В следующем примере используется шесть операторов if для сравнения ввода пользователем двух чисел в про- грамму. Если условие в любом из операторов if истинно, тогда операция присваивания с этим оператором if выполняется. Пользователь вводит значения, которые программа преобразует в целые числа и сохраняет в пе- ременных numberl и number2. Программа сравнивает числа и отображает результаты сравнения в консольном окне (листинг 2.3). Листинг 2.3Использование операций отношения и сравнения 1 // Листинг 2.3: Conqparison.cs 2 // Использование операторов if, операций отношения 3 //и сравнения 4 5 using System; 6 7 class Comparison 8 { 9 static void Main(string [] args) 10 { 11 int -numberl, // первое число для сравнения 12 number2; // второе число для сравнения 13
64 Гпава 2 14 // считывание в первое число от пользователя 15 Console.Write("Please enter first integer: "); 16 nuiriberl = Int32.Parse(Console.ReadLine()); 17 18 // считывание во второе число от пользователя 19 Console.Writе("\nPlease enter second integer: "); 20 number2 = Int32.Parse(Console.ReadLine()); 21 22 if (numberl = number2) 23 Console.WriteLine (numberl + " == " + number2); 24 25 if (numberl != number2) 26 Console.WriteLine (numberl + " != " + number2); 27 28 if (nuiriberl < number2) 29 Console. Writ eLine (numberl + "<’’ + number2); 30 31 if (numberl > number2) 32 Console.WriteLine (numberl + " > " + number2); 33 34 if (numberl <= number2) 35 Console.WriteLine (numberl + " <= " + number2); 36 37 if ( numberl >= number2) 38 Console.WriteLine (numberl + " >= " + number2); 39 40 } // окончание метода Main 41 42 } // окончание класса Comparison Результат выполнения программы: Please enter first integer: 2000 Please enter second integer: 1000 2000 != 1000 2000 > 1000 2000 > = 1000 Please enter first integer: 1000 Please enter second integer: 2000 1000 != 2000 1000 < 2000 1000 <= 2000 Please enter first integer: 1000 Please enter second integer: 1000 1000 == 1000 1000 <= 1000 1000 >= 1000 Как отмечалось выше, каждая программа C# состоит, как минимум, из одного определения класса. В строке 7 начинается определение класса Comparison. В строках 8—42 определяется тело класса. Программа начинает выполнение с метода Main в строке 9. Строки 11—12 — объявление. Слова numberl и number2 — имена пере- менных. Переменная — это местоположение в памяти компьютера, где может храниться значение для использо- вания программой. Все переменные должны быть объявлены с именем и типом данных до того, как программа сможет их использовать. Данное объявление указывает, что переменные numberl и number2 — данные типа int; это означает, что эти переменные будут содержать целые значения (т. е. целые числа, например, -11, 7, 0 и 319 145). Конкретные типы данных уже определены в .NET Framework; они называются встроенными типами
Введение в интегрированную среду разработки Visual Studio .NET и программирование на C#65 данных или простыми типами данных. Такие типы, как string, int, double и char — примеры простых типов данных. Названия простых типов — ключевые слова 15 простых типов кратко описаны в главе 4. Именем переменной может быть любой действующий идентификатор. Объявления заканчиваются точкой с за- пятой (;) и из соображений читаемости могут разбиваться на несколько строк, где каждая переменная в объяв- лении отделена запятой (т. е. разделенный запятыми список имен переменных). Несколько переменных одного типа могут быть представлены в одном или нескольких объявлениях. В данном примере можно было написать два объявления, по одному для каждой переменной, однако используемое здесь объявление более сжатое. В строке 15 предлагается ввести целое число, а в строке 16 считывается введенная пользователем строка, пред- ставляющая первое из двух целых чисел. Переменные типа string сохраняют строки символов. Сообщение в строке 15 называется подсказкой, потому что оно подсказывает пользователю выполнение определенного дей- ствия. Подсказка отображается с помощью метода Console — Write. В отличие от метода WriteLine, write не переводит курсор вывода в начале следующей строки в консольном окне после отображения строки; вместо этого курсор вывода размещается непосредственно после последнего символа, отображенного с помощью ме- тода write. Таким образом, пользователь будет вводить целые значения непосредственно после подсказки. Метод ReadLine (строка 16) приостанавливает выполнение программы и ожидает ввода со стороны пользовате- ля. Пользователь вводит символы с клавиатуры, после чего нажимает клавишу <Enter> для передачи строки в программу. К сожалению, .NET Framework не предоставляет простого диалогового окна ввода. По этой причине примеры в начальных главах книги принимают входные данные через консольное окно. Практически, в качестве входных данных пользователь может отправить в программу все что угодно. Для дан- ной программы, если пользователь введет не целое число, то возникнет логическая ошибка прогона (ошибка, происходящая во время выполнения программы). В главе 8 описывается повышение устойчивости программ путем исправления таких ошибок. Когда пользователь вводит число и нажимает клавишу <Enter>, программа возвращает строковое представление этого числа, теперь являющееся аргументом метода Int32.Parse. Метод int32. Parse (статический метод класса Int32) преобразует свой аргумент string в целое число. Класс int32 :— это часть пространства имен System. В строке 16 присваивается целое число, которое int32. Parse возвращает переменной numberl, с помощью опе- ратора присваивания (=). Оператор = — бинарный, потому что он имеет два операнда — numberl и результат применения метода Int32. Parse. Весь оператор является оператором присваивания, потому что он присваивает переменной значение. В операторе присваивания правая часть, присваивания оценивается в первую очередь; после этого результат присваивается переменной в левой части присваивания. Все последующие ссылки на numberl в программе используют это значение. Строка 20 позволяет ввести другое значение, которое преобразуется в int и присваивается number^. Все последующие ссылки на number2 в про- грамме используют это значение. Мы преобразовали значения в целые, потому что арифметические, реляцион- ные операторы и операторы равенства нельзя применять к строкам. Оператор if в строках 22—23 сравнивает значения переменных numberl и number2 на равенство. Если эти зна- чения равны, то программа выдает значение numberl + " == " + number2 Обратите внимание, что это утверждение использует операцию + для "добавления" (или комбинирования) чисел и строк Данная версия операции + применяется для сцепления строк. Сцепление (конкатенация) — это процесс, активизирующий строку и значение другого типа данных (включая другую строку) для объединения с целью образования новой строки. Если numberl содержит значение 1000, a number2 тоже содержит значение 1000, тогда утверждение оценивается следующим образом: C# определяет, что операнды оператора + принадлежат к разным типам, и что один из них— string (т. е. хотя numberl и number2 имеют тип int, но " == " — string). Затем numberl и number2 пре- образуются в string и сцепляются с помощью " == ". На данном этапе string, а именно— "1000 == 1000", отправляется в метод Console.WriteLine для вывода. По мере прохождения программой операторов if, строко- вые результаты выводятся с помощью конструкции Console. Writ eLine. Например, при заданном значении 1000 для numberl и number2 условия if в строках 34 (<=) и 37 (>=) также будут истинными. Таким образом, отобра- женные выходные данные будут следующими: 1000 = = 1000 1000 < = 1000 1000 > = 1000 Третий выведенный результат демонстрирует этот случай. 5 Зак. 3333
66 Глава 2 Распространенная ошибка программирования___________________________________________________ Смешение операции +, используемой для сцепления строк, с операцией +, применяемой для сложения, может привести к странным результатам. Например, если предположить, что целочисленная переменная у имеет значение 5, тогда результатом высказывания "у + 2 - " + у + 2 становится строка "у + 2 = 52", а не "у + 2 = 7". Во-первых, значение у (равное 5) сцепляется со строкой "у + 2 = затем значение 2 сцепляется с новой, более длинной строкой "у + 2 = 5". Высказывание "у + 2 = " + (у + 2) выдает нужный результат. Распространенная ошибка программирования___________________________________________________ Замена операции == в условии оператора if, например if (х == 1), на операцию =, например if (х = 1), яв- ляется синтаксической ошибкой. Распространенная ошибка программирования___________________________________________________ Отсутствие левой и правой скобок вокруг условия в операторе if является синтаксической ошибкой. Скобки не- обходимо ставить в любом случае. В конце первой строки каждого оператора if, показанной в листинге 2.3, нет точки с запятой. Результатом по- становки точки с запятой станет логическая ошибка во время выполнения программы. Например, if (numberl == number2); Console.WriteLine'numberl + " == " + number2); фактически будет рассматриваться C# как if (numberl == number2) r Console.WriteLine(numberl + " == " + number2); где сама по себе точка с запятой в строке, называемая пустым оператором, является высказыванием для ис- полнения, если условие истинно. При выполнении пустого оператора не выполняется никаких операций. Про- грамма продолжается высказыванием console.WriteLine, выполняющимся независимо от того, истинно усло- вие или ложно; разумеется, выходные данные оказываются некорректными. Обратите внимание на использование пробелов в листинге 2.3. Помните, что компилятор, как правило, игнори- рует символы пустых строк: табуляцию, новые строки и пробелы. Следовательно, операторы можно разбивать на несколько строк, и программист может ставить между ними пробелы по своему усмотрению, что не повлияет на смысл программы. Однако разбивать идентификаторы и строковые литералы некорректно. В идеале опера- торы (высказывания) должны быть короткими, но добиться этого не всегда возможно. О хорошем стиле программирования___________________________________________________________ Длинное высказывание можно разбить на несколько строк. Если одно высказывание необходимо разбить по строкам, выберите осмысленные точки разрыва, например, после запятых в списке, разделенном запятыми, ли- бо после оператора в длинном высказывании. Если высказывание разбивается на две или более строк, следует делать отступ каждой последующей строки на один уровень с целью показа того, что строка (строки) продолже- ния является (являются) частью одного высказывания. В табл. 2.6 представлены операции, описанные в данной главе, с учетом приоритета их выполнения. Операции перечислены сверху вниз в нисходящем порядке предшествования. Обратите внимание, что все операции, за исключением операции присваивания (=), ассоциируются слева направо. Сложение ассоциативно слева, поэтому такое выражение, как х + у + z, оценивается так, если бы оно было записано как (х + у) + .z. Операция при- сваивания ассоциативна справа, поэтому такое выражение, как х = у = о, оценивается так, если бы оно было записано как х = (у = 0). Последнее выражение х = (у = 0) сначала присваивает значение 0 переменной у; затем результат присвоения — 0 — присваивается х. Данный метод является укороченным путем присвоения одного и того же значения нескольким переменным. Таблица 2.6. Приоритет и ассоциативность рассмотренных в главе операций Операция Ассоциативность Тип (, ) Слева направо Скобки *, /. % Слева направо Мультипликативный +, - Слева направо Аддитивный <, <=, >, >= Слева направо Реляционный ==, ! = Слева направо Равенство = Справа налево Присваивание
Введение в интегрированную среду разработки Visual Studio .NET и программирование на C#67 О хорошем стиле программирования _______________________________________________________ При написании выражений, содержащих много операций, обращайтесь к таблице операторного предшествова- ния. Удостоверьтесь, что операции выражениях выполняются в ожидаемом порядке. Если программист не уве- рен в порядке оценки в сложных выражениях, следует использовать круглые скобки для указания порядка, по- добно тому, как это делается в алгебраическом выражении. Помните о том, что некоторые операции, например операция присваивания (=), ассоциируются справа налево, а не слева направо. В данной главе представлены важнейшие особенности Visual Studio .NET, а также различные инструментальные4 средства, предоставляемые IDE. Также приведены важные функции С#, включая функции отображения данных на экран, ввод данных с клавиатуры, выполнение вычислений и принятие решений. В следующей главе рас- сматривается много сходных методик по мере повторения Windows-приложений C# (приложений, обеспечи- . вающих графический пользовательский интерфейс), а также структуры управления С#. 2.10. Резюме Интегрированная среда разработки (IDE) Visual Studio .NET — это программная среда для создания, докумен- тирования, запуска и отладки программ. В Visual Studio .NET имеются разнообразные окна. В их число входит окно Solution Explorer, где приводится список всех файлов решения, панель Toolbox, содержащее элементы управления, позволяющие выполнить пользовательские настройки форм, а также окно Properties, где показаны свойства того или иного элемента управления (например, цвета и гарнитуры шрифтов). В Visual Studio .NET также имеются функции оперативной справки, как контекстной, так и динамической. Большинство программ C# построены комбинированием визуального и не визуального методов программиро- вания. Программирование консольных приложений— приложений, преимущественно записывающих текст приглашений на вход в программу — является формой невизуального программирования. Существуют различные составные части программ, написанных на С#: пространства имен, классы и методы. Все программы C# содержат один или более классов, только один из которых должен содержать метод Main, называемый точкой входа в программу. Методы могут выполнять задачи и возвращать результат по завершении выполнения этих задач. Данные могут передаваться методу, если они необходимы для выполнения методом поставленной задачи. Библиотека классов .NET Framework предоставляет множество пространств имен, одно из которых содержит класс с методом, отображающим диалоговое окно сообщений. Ссылки на пространства имен необходимо делать до того, как последние будут использованы. Разработчики могут добавить ссылку в проект, используя команды Project | Add Reference.... C# предоставляет арифметические, реляционные операции и операции равенства для манипулирования значе- ниями в программе. Часто значения должен вводить сам пользователь. Данные пользователя можно считывать с помощью метода ReadLine.
ГЛАВА 3 (ш Управляющие структуры Давайте все сделаем один шаг. Льюис Кэрролл Колесо сделало полный оборот. Уильям Шекспир Сколько яблок упало Ньютону на голову, пока он понял намек? Роберт Фрост Кто может управлять его судьбой? Уильям Шекспир Темы данной главы: □ условные операторы if и if/else для выбора альтернативных операций; □ оператор цикла while для повторяющегося выполнения высказываний в программе; □ использование операций инкремента, декремента и присвоения; □ операторы цикла for и do/while для повторяющегося выполнения высказываний в программе; □ изучение множественной выборки, упрощенной оператором выбора switch; О применение программно-управляемых операторов break и continue; □ использование логических операций. 3.1. Введение В данной главе представлены структуры, дающие программистам возможность управления порядком выполне- ния. Условные операторы, оператор выборки и циклов C# используются для выбора и повторения различных высказываний, что обеспечивает выполнение сложных алгоритмов. В процессе представления этих структур авторы рассматривают часто используемые кратко записываемые операторы, позволяющие программисту опе- ративно рассчитывать и присваивать новые значения переменным. При глубоком рассмотрении объектно- ориентированного программирования в главе 5 станет понятно, что структуры управления очень полезны при построении объектов и манипуляциях ими. 3.2. Управляющие структуры Как правило, высказывания в программе выполняются одно за другим в порядке их появления в ней. Этот про- цесс называется последовательным выполнением. Впрочем, различные высказывания (операторы) C# позволя- ют программисту указать, что следующее для выполнения высказывание не обязательно будет следующим в последовательности. Передача управления имеет место, когда выполняется не очередное в программе высказы- вание. Язык C# имеет три типа операторов выборки. Условный оператор if выполняет (выбирает) операцию, если ус- ловие истинно, либо пропускает операцию, если условие ложно. Условный оператор if/eise выполняет одну операцию, если условие истинно, и другую операцию, если условие ложно. Оператор выбора switch выполняет одну из многих операций, в зависимости от значения выражения. Оператор if называется оператором одной выборки, потому что он выбирает или игнорирует одну операцию (или одну группу операций). Оператор if/else называется оператором двойной выборки, потому что выбирает
Управляющие структуры 69 между двумя разными операциями (или группами операций). Оператор switch называется оператором множе- ственной выборки, потому что выбирает среди многих различных операций (или групп операций). В языке C# представлено четыре структуры повторения: операторы циклов while, do/while, for и foreach. (foreach описывается в главе 4). Слова if, else, switch, while, do, for и foreach являются ключевыми в С#. Ниже представлен полный набор ключевых слон С#. abstract do in protected true as double int public try base else interface readonly typeof bool enum internal ref uint break event is return ulong byte explicit lock sbyte unchecked case extern long sealed unsafe catch false namespace short ushort char finally new sizeof using checked fixed null stackalloc virtual class float object static void const for operator string volatile continue foreach out struct while decimal goto override awitch default if params this delegate implicit private throw C# имеет только восемь структур управления: одна последовательная структура, три типа структур выборки и четыре типа структур повторения. Каждая программа формируется комбинированием как можно большего ко- личества типов структур управления. Структуры управления "один вход/один выход" упрощают построение программ; структуры управления прикре- пляются одна к другой путем присоединения точки выхода одной структуры управления к точке входа другой. Такое построение сходно с пакетированием стандартных блоков, поэтому будем называть его пакетированием управляющих структур. Существует только один альтернативный путь соединения управляющих структур — вложение управляющих структур, когда одна структура управления размещается другой. Следовательно, ал- горитмы в программах C# составляются только из восьми различных типов управляющих структур, комбини- руемых лишь двумя способами. 3.3. Условный оператор if В программе условный оператор выбирает между альтернативными образами действия. Например, предполо- жим, что проходной балл на экзамене — 60 (из 100). Тогда высказывание псевдокода Если балл студента равен или превышает 60, Напечатать "Сдано" определяет истинность или ложность условия "балл студента равен или превышает 60". Псевдокод — это искус- ственный и неформальный язык, помогающий программистам в разработке алгоритмов. Псевдокод чем-то на- поминает обычный разговорный английский язык; он удобен, непритязателен и не является компьютерным язы- ком программирования. Псевдокод помогает программисту "продумать" программу до того, как приступать к ее написанию на языке программирования, например на С#. Если условие истинно, тогда печатается сообщение "Сдано", и выполнится следующее по порядку высказыва- ние псевдокода. Если условие ложно, тогда сообщение "Сдано" проигнорируется, и выполнится следующее по порядку высказывание псевдокода. Предшествующее псевдокоду высказывание if может быть записано на C# следующим образом: . if (studentGrade >= 60) Console.WriteLine("Passed"); Обратите внимание, что код C# очень похож на псевдокод, демонстрируя пользу псевдокода в качестве инстру- мента разработки программы. Оператор в теле оператора if выдает в консольном окне строку символов
70 Гпава 3 "Passed" ("Сдано"). Решение принимается на основе условия. Решение может основываться на любом выраже- нии, оценивающем до значения типа bool C# (т. е. на любом выражении, имеющем значение true или false). 3.4. Условный оператор if/else Условный оператор if выполняет указанную операцию, только когда условие оценивается как истинное; в про- тивном случае операция пропускается. Условный оператор if/else позволяет программисту задавать различ- ные операции для выполнения, когда условие — истинное и когда — ложное. Например, утверждение псевдо- кода Если балл студента равен или превышает 60, Напечатать "Сдано" Либо Напечатать "Не сдано" распечатывает сообщение "Сдано”, если балл студента превышает или равен 60, и распечатывает сообщение "Не сдано", если балл студента меньше 60. В любом случае, после распечатки "выполняется" следующее выра- жение псевдокода. Предыдущую псевдокодовую структуру if/else можно записать в C# следующим образом: if (srudentGrade >= 60) Console.WriteLine("Passed"); else Console.WriteLine("Failed"); Условная операция (?:) тесно связана с oneparopoMif/else. ?: — единственная трехместная операция С#: он имеет три операнда. Операнды и ?: образуют условное выражение. Первый операнд — это условие (т. е. выра- жение, оценивающее до значения Ьо61);?второй — значение для условного выражения, если условие оценивает- ся как истинное; третий — значение для условного выражения, если условие оценивается как ложное. Напри- мер, оператор вывода Console.WriteLine(studentGrade >= 60 ? "Passed" : "Failed"); содержит условное выражение, которое оценивает к строке "Passed", если условие studentGrade >= 60— ис- тинно, и к строке "Failed", если условие ложно. Высказывание с условной операцией выполняется так же, как и предыдущее высказывание if/else. Степень приоритета условной операции— низкая, поэтому обычно все условное выражение целиком заключается в скобки. Условные операции можно использовать в ситуациях, когда нельзя применять операторы if/else, на- пример, в аргументе метода WriteLine, описанного выше. Условный оператор if обычно может иметь в своем теле только одно высказывание. Для включения в тело опе- ратора if нескольких высказываний заключите их в фигурные скобки ({ и }). Набор высказываний, содержа- щийся в паре фигурных скобок, называется блоком. Блок может содержать объявления. Объявления в блоке обычно размещаются в нем первыми, до каких бы то ни было операторов действий, но объявления могут также смешиваться с операторами действий. 3.5. Оператор цикла while Оператор цикла позволяет программисту указать, что действие должно повторяться, пока условие остается ис- тинным. Высказывание псевдокода Пока в моем списке наименовании для покупки больше нуля Приобретайте следующее наименование и вычеркивайте его из списка описывает повторение, которое имеет место во время похода по магазинам. Условие "в моем списке наимено- ваний для покупки больше нуля" может быть истинным или ложным. Если оно истинно, тогда выполняется дей- ствие "приобретайте следующее наименование и вычеркивайте его из списка". Данная операция выполняется многократно, пока условие остается истинным. Один или более операторов, содержащихся в операторе цикла while, образует тело while. Тело цикла while может быть как одним оператором, так и блоком. Условие, в ко- нечном итоге, становится ложным (в данном случае, когда приобретается последнее наименование и вычерки- вается из списка). В этот момент повторение прерывается, и выполняется первый оператор (высказывание) по- сле оператора цикла.
Управляющие структуры 71 В качестве примера оператора while рассмотрим фрагмент программы, предназначенной для расчета первой степени двойки, превышающей 1000. Предположим, что int-переменная product содержит значение 2. По окончании выполнения следующего оператора цикла while переменная product содержит результат: int product = 2; while(product <= 1000) product = 2 * product; Когда начинает выполняться цикл while, значение переменной product равно 2. Переменная product много- кратно умножается йа 2, принимая значения 4, 8, 16, 32, 64, 128, 256, 512 и 1024. Когда product принимает зна- чение 1024, условие product <= 1000 в цикле while становится ложным. После этого повторение прекращается с окончательным значением product, равным 1024. Выполнение продолжается следующим оператором после цикла while. Примечание________________________________________________________________________________ Если условие цикла while изначально ложно, тогда операторы тела никогда не будут выполняться. В листинге 3.1 представлен пример программы, демонстрирующей цикл while. Программа создана для решения следующей задачи. Колледж предлагает учебный курс подготовки студентов к лицензионным экзаменам по специальности "Брокер по работе с недвижимостью". В прошлом году несколько студентов, прослушавших данный курс, сдавали экзамен на получение лицензии. Руководство колледжа интересуется успехами студентов на экзаме- не. Была поставлена задача написания программы, резюмирующей результаты, со списком из 10 студентов. Против имени каждого студента пишется 1, если студент сдал экзамен, и 2, если не сдал. Программа должна анализировать результаты экзамена следующим образом: 1. Ввод результата каждого теста (т. е. 1 или 2). Отображение на экране сообщения "Enter result" ("Введите результат") всякий раз, когда программа требует результат другого очередного теста. 2. Подсчет количества результатов тестов каждого типа. 3. Отображение резюме результатов тестирования с указанием количества студентов, сдавших и не сдавших экзамен. Если экзамен сдали более восьми студентов, программа распечатывает сообщение "Raise tuition" ("Поднять плату за обучение"). --------------------------—’П | Листинг 3.1. Программа C# для решения проблемы результатов экзаменов »Л'waVMabw.................• to.... ,,.¥. йнЬ< — Лйа.аа aiorttK 1 /7 Листинг 3.1: Analysis.cs 2 // Анализ результатов экзаменов 3 4 using System; 5 б class Analysis 7 { 8 static void Main(string[] args) 9 { 10 int passes = 0, // количество сдавших экзамен 11 failures = 0, 11 количество не сдавших экзамен 12 student =1, // счетчик студентов 13 result; // результат одного экзамена 14 15 // обработка 10 студентов 16 while (student <= 10) 17 { 18 Console.Write("Enter result (l=pass, 2=fail): ”); 19 result = Int32.Parse(Console.ReadLine()); 20 21 if (result = 1) 22 passes = passes + 1; 23
72 Гпава 3 24 else 25 failures = failures +1; 26 27 student = student + 1; 28 ) 29 30 // фаза прерывания 31 Console.WriteLine(); 32 Console.WriteLine("Passed: " + passes); 33 Console.WriteLine("Failed: *' + failures); 34 35 if (passes > 8) 36 Console.WriteLine("Raise tuition\n"); 37 38 } // конец метода Main 39 40 } // конец класса Analysis Результат работы программы: Enter result (l=pass, 2=fail) :1 Enter result (l=pass, 2=fail) :2 Enter result (l=pass, 2=fail) :1 Enter result (l=pass, 2=fail) :1 Enter result (l=pass, 2=fail) :1 Enter result (l=pass, 2=fail) :1 Enter result (l=pass, 2=fail) :1 Enter result (l=pass, 2=fail) :1 Enter result (l=pass, 2=fail) :1 Enter result (l=pass, 2=fail) :1 Passed: 9 Failed: 1 Raise Tuition Enter result (l=pass, 2=fail) :1 Enter result (l=pass, 2=fail) :2 Enter result (l=pass, 2=fail) :2 Enter result (l^pass, 2=fail) :2 Enter result (l=pass, 2=fail) :2 Enter result (l=pass, 2=fail) :2 Enter result (l=pass, 2=fail) :1 Enter result (l=pass, 2=faxl) :1 Enter result (l=pass, 2=fail) :1 Enter result (l=pass, 2=fail) :1 Passed: 5 Failed: 5 В строках 10—13 объявляются переменные, использованные в методе Main, для обработки результатов экзаме- на. Мы воспользовались свойством С#, позволяющим объединить инициализацию переменных в объявления (passes присваивается значение 0, failures присваивается значение 0, a student присваивается значение 1). Программы, содержащие повторения, могут требовать инициализации в начале каждого повторения; такая ини- циализация обычно происходит в операторах присваивания. Строки 16—28 определяют цикл while, который должен повторяться до тех пор, пока значение student меньше или равно 10. Обратите внимание на использование вложенного оператора if/else (строки 21—25) в теле опе- ратора цикла while.
Управляющие структуры 73 3.6. Операции присваивания C# имеет несколько операций присваивания для сокращения выражений присвоения. Например, высказывание с = с + 3; можно сократить с помощью операции присваивания сложения (+=) до вида с += 3; Операция += прибавляет значение выражения справа от знака операции к значению переменной слева от знака операции и сохраняет результат в переменной слева от знака. Любое высказывание в форме переменная - переменная операция выражение; где операция— одна из бинарных операций +, *, / или % (или другие, рассматриваемые далее в книге) — можно записать в форме переменная операция^ выражение; * В табл. 3.1 приведены операции арифметического присваивания, представлены примерные выражения, в кото- рых используются эти операции, и дается объяснение значения каждого примерного выражения. Таблица 3.1. Операции арифметического присваивания Операция присваивания Примерное выражение Пояснение Присвоения += с += 7 с = с + 7 10 К с —= d -= 4 d = d - 4 1 Kd *= е *= 5 е = е * 5 20 к е /= f /= 3 f = f / 3 2 Kf %= g %= 9 g = g % 9 3 к g Примечание. В таблице примеры приведены в предположении, что int с = 3, d = 5, е = 4, f = 6, g = 12; Распространенная ошибка программирования___________________________________________________ Ввод символа пробела между символами, составляющими оператор арифметического присваивания, является синтаксической ошибкой. 3.7. Операции инкремента и декремента C# предоставляет унарную операцию инкремента ++ и унарную операцию декремента —, определения которых в общем виде представлены в табл. 3.2. Программа может использовать операцию инкремента для приращения на единицу значения переменной, например с, вместо использования выражения с = с + 1 или с += 1. Если операция инкремента или декремента вводится до переменной, то она называется операцией префиксного при- ращения или префиксного уменьшения соответственно. Если операция инкремента или декремента вводится после переменной, то он называется операцией постфиксного приращения или постфиксного уменьшения со- ответственно. Таблица 3.2. Операции инкремента и декремента Операция Название Примерное выражение Пояснение ++ Префиксное приращение ++а Увеличивает а на 1; затем использует новое значение а в выраже- нии, где присутствует а ++ Постфиксное приращение а++ Использует текущее значение а в выражении, где присутствует а; затем выполняет увеличение а на 1 — Префиксное уменьшение —Ь Уменьшает ь на 1; затем использует новое значение Ь в выражении, в котором присутствует ь — Постфиксное уменьшение Ь— Использует текущее значение b в выражении, где присутствует ь; затем выполняет уменьшение b на 1
74 Гпава 3 Распространенная ошибка программирования____________________________________ Попытка использования операции инкремента или декремента в выражении, отличном от ссылки на перемен- ную, является синтаксической ошибкой. Ссылка на переменную — это переменная или выражение, которые мо- гут появиться в левой части операции присваивания. Например, ++ (х+1) будет синтаксической ошибкой, потому что (х+1) не является ссылкой на переменную1. В табл. 3.3 приведены операции с учетом их приоритетов и указана их ассоциативность. Операции перечислены сверху вниз в нисходящем порядке приоритета. Во втором столбце описана ассоциативность операторов на каждом уровне. Обратите внимание, что условная операция (?:), унарные операции инкремента (++) и декре- мента (—), операция +, операция - и операции присваивания (=, +=, -=, /= и %=) ассоциируются справа налево. Все остальные операции в табл. 3.3 ассоциируются слева направо. В третьем столбце перечислены названия групп операций. Таблица 3.3. Приоритет и ассоциативность рассмотренных в книге операций Операция * Ассоциативность Тип (, ) Слева направо Справа налево Круглые скобки Унарный постфикс ++, —. +- Справа налево Унарный префикс *, /, % Слева направо Мультипликативный +, - Слева направо Аддитивный <, <=, >, >- Слева направо Реляционный ==, ! = Слева направо Равенство 9 ; Справа налево Условное =, +=. *=. /=, %= Справа налево Присваивание 3.8. Оператор цикла for Оператор цикла for осуществляет повторения, управляемые счетчиком, где счетчик нужен для вычисления ко- личества повторений. При повторениях будет выполняться набор высказываний (операторов). В листинге 3.2 иллюстрируется "сила" цикла for. Метод Main (строки 8—14) работает следующим образом: когда начинает выполняться цикл for (строки 12—13), программа устанавливает переменную цикла counter в 1. Затем про- грамма проверяет условие продолжения цикла: counter <= 5. Первоначальное значение counter равно 1, следо- вательно, условие истинно, поэтому строка 13 выдает значение counter. После этого программа увеличивает переменную counter в выражении counter ++, и опять проверяется условие продолжения цикла. Переменная цикла теперь равна 2. Это значение не превышает окончательного значения, поэтому программа еще раз выпол- няет оператор тела (т. е. выполняет следующую итерацию цикла). Этот процесс продолжается до тех пор, пока переменная цикла counter не примет значение б, после чего тест продолжения цикла и повторение прекращает- ся. Программа продолжает выполнение первого оператора после оператора for. (В данном случае прекращается выполнение метода Main, потому что программа доходит до конца тела Main.) I1 Листинг 3.2. Управляемое счетчиком повторение в цикле for _________________________________к _____________________________ 1 // Листинг 3.2: ForCounter.cs 2 // Управляемое счетчиком повторение в цикле for 3 4 using System; 5 6 class ForCounter 7 { 8 static void Main(string[] args) 1 Термин ссылка на переменную эквивалентен термину 1-значение ("левое значение"), популярному среди программистов на С и C++.
Управляющие структуры 75 9 { 10 // инициализация, условие повторения и инкрементирование 11 // включены в цикл for 12 for (int counter =1; counter <= 5; counter++) 13 * Console.WriteLine(counter); 14 ) < 15 } Результат выполнения программы: 1 2 3 4 5 Первая строка цикла for (включая ключевое слово for и все в круглых скобках после for) иногда называется заголовком цикла. Обратите внимание на то, что оператор for задает каждый из элементов, необходимых для управляемого счетчиком повторения с переменной цикла. Если в теле цикла for более одного оператора, необ- ходимы фигурные скобки ({ и }) для определения тела цикла. В листинге 3.2 используется условие продолжения цикла counter <= 5. Если программист запишет его непра- вильно, например, как counter < 5, тогда цикл повторится (выполнится) только четыре раза. Данная распро- страненная логическая ошибка называется ошибкой завышения или занижения на единицу. Общий формат цикла for таков: for (вЕцражение!; вьраженме2; вьражениеЗ) выражение; где выражение! определяет переменную цикла и выдает первоначальное значение, вьражение2— условие про- должения цикла, а вьражениеЗ увеличивает или уменьшает переменную цикла на единицу. В C# программисты могут объявить переменную цикла в вьражении! заголовка цикла for (т. е. тип переменной цикла указывается до имени переменной), а не раньше в коде. При принятии данного подхода переменную цик- ла можно использовать только в теле оператора for (т. е. имя переменной цикла не будет известно за пределами оператора for). Такое ограничение на использование имени переменной цикла определяет область действия переменной или место, где эта переменная может использоваться в программе. Подробно область действия рас- сматривается в главе 4. Распространенная ошибка программирования_____________________________________________________ Когда переменная цикла объявляется в разделе инициализации заголовка цикла for, то использование этой пе- ременной цикла после тела оператора for является ошибкой компиляции. Выражение! и вьражениеЗ в операторе for также могут быть разделенными запятыми списками выражений, что позволяет программисту использовать множественные выражения инициализации и/или множественные выра- жения инкремента или декремента. Например, в одном операторе for может быть несколько переменных цикла, которые должны быть инициализированы, инкрементированы или декрементированы. О хорошем стиле программирования_____________________________________________________________ Размещайте в разделах инициализации, инкремента или декремента оператора for только выражения с пере- менными цикла. Манипуляции с другими переменными должны появляться либо до цикла (если они выполняют- ся только один раз, как операторы инициализации), либо в теле цикла (если они выполняются один раз за одну итерацию цикла, подобно операторам инкремента или декремента). Эти три выражения в операторе for jie являются обязательными. Если вьражение2 опускается, тогда C# предпо- лагает, что условие продолжения цикла всегда истинно, создавая, таким образом, бесконечный цикл. Програм- мист должен опустить выражение!, если программа инициализирует переменные цикла до цикла. ВыражениеЗ можно опустить, если операторы цикла for выполняют самоприращение или самоуменьшение на единицу, ли- бо, если в приращении и уменьшении на единицу нет необходимости. Выражение инкремента (или декремента) в операторе for работает так, если бы оно было отдельным оператором в конце цикла for. Следовательно, вы- ражения counter = counter + 1 counter += 1 ++counter counter++ эквивалентны при использовании в выраженииЗ.
76 Гпава 3 Некоторые программисты предпочитают форму counter++, потому что она вызывает приращение переменной цикла после выполнения тела цикла. По этой причине форма постприращения (или постуменьшения), при кото- рой переменная увеличивается после ее использования, кажется более естественной. Однако по причине того, что увеличиваемая или уменьшаемая переменная не появляется в крупных выражениях, предварительное при- ращение (или предварительное уменьшение) и постприращение (или постуменьшение) переменной имеет одно и то же действие. В операторе for требуются два двоеточия. Распространенная ошибка программирования_____________________________________________ Использование запятых в заголовке оператора for вместо двух требуемых двоеточий является синтаксической ошибкой. Распространенная ошибка программирования_____________________________________________ Размещение двоеточия непосредственно справа от правой скобки заголовка оператора for делает тело такого цикла пустым высказыванием. Обычно это является логической ошибкой. Инициализация, условие продолжения цикла и части инкремента или декремента оператора for могут содер- жать арифметические выражения. Например, предположим, что х = 2, а у = 10. Если х и у не изменяются в теле циклов, тогда утверждение for (int j = х; j <= 4 * х * у; j += у / х) эквивалентно утверждению for (int j = 2; j <= 80; j += 5) Приращение оператора for может быть отрицательным, и в этом случае оно на самом деле является уменьше- нием, а цикл, по сути дела, выполняется в обратном порядке. 3.9. Пример: использование оператора for для расчета депозитного процента Очередной пример демонстрирует простое применение оператора повторения for. В программе, представлен- ной в листинге 3.3, используется цикл for для решения следующей задачи. Человек вкладывает $1000 на депозит под 5% годовых. Если предположить, что вся сумма процента распо- ложена слева от депозита, рассчитайте и распечатайте денежную сумму на счете в конце каждого года за 10 лет. Для определения этих сумм воспользуйтесь формулой а=р(1 + г)и, где: • р — первоначально вложенная сумма (т. е. принципал); • г — ежегодная процентная ставка; • п — количество лет; • а — сумма на депозите в конце n-го года. Эта задача предполагает цикл, выполняющий указанное вычисление по каждому году из 10 лет, в течение кото- рых деньги находятся на депозите. ---- Яртвтжтг .................................. ........------------------«.nr I Листинг 3.3, Вычисление депозитного процента с помощью оператора for .......... ...... «..«.и . ...... .. .. 1 // Листинг 3.3: Interest.es 2 // Расчет депозитного процента 3 4 using System; 5 using System.Windows.Forms; 6 7 class Interest 8 { 9 static void Main(string[] args)
Управляющие структуры 77 10 { 11 decimal amount, principal = (decimal) 1000.00; 12 double rate = .05; 13 string output = "Year\tAmount on deposit\n"; 14 15 for (int year = 1; year <= 10; year++) 16 { 17 amount = principal * 18 (decimal) Math.Pow (1.0 + rate, year); 19 20 output += year + “\t" * 21 String.Format("{0:0", amount) + "\n"; 22 } 23 24 MessageBox.Show(output, "Compound Interest", 25 MessageBoxButtons.OK, MessageBoxIcon.Information); 26 27 } // конец метода Main 28 29 } // конец класса Interest Результат работы программы представлен на рис. 3.1. Аргумент 2: строка заголовка (не обязательно) Аргумент 4 значок диалогового окна Аргумент 1: сообщение -Аргумент 3: кнопка ОК в диалоговом окне (не обязательно) Рис. ЗЛгВычисление депозитного процента с помощью оператора for Язык C# предоставляет возможность определять несколько методов с одним и тем же именем. Программисты часто пользуются этой методикой, известной как перегрузка метода, для определения нескольких методов, вы- полняющих сходные операции, но применяющих разные наборы аргументов. Одним из примеров перегружен- ного метода является MessageBox.show. В прошлом для этого метода предоставлялся только один аргумент — string — для отображения в диалоговом окне сообщения. Однако в программе из листинга 3.3 используется версия метода MessageBox.show (строки 24—25), в котором четыре аргумента. Диалоговое окно с выходными данными иллюстрирует эти четыре аргумента. Первый аргумент— сообщение для отображения. Второй аргу- мент — строка для отображения в заголовке диалогового окна. Третий аргумент — это значение, указывающее, какие кнопки появятся в окне. Четвертый аргумент определяет отображаемую слева от сообщения пиктограмму. В табл. 3.4 и 3.5 представлены перечни отображаемых в окне кнопок и пиктограмм соответственно. Информа- ция о других версиях метода MessageBox.Show имеется в документации MSDN, прилагаемой к Visual Studio .NET. Более подробно перегрузка методов рассматривается в главе 4. Таблица 3.4. Кнопки диалогового окна MessageBox Метод Описание MessageBoxButtons.ОК Диалоговое окно должно содержать кнопку ОК MessageBoxButtons.OKCancel Диалоговое окно должно содержать кнопки ОК и Cancel, информирует пользователя о некотором условии и дает возможность либо продолжить работу, либо отменить операцию
78 Гпава 3 Таблица 3.4 (окончание) Метод Описание MessageBoxButtons.YesNo Диалоговое окно должно отображать кнопки Yes и No. Применяется для постановки вопроса пользователю MessageBoxButtons.YesNoCancel Диалоговое окно должно содержать кнопки No и Cancel. Обычно приме- няется для постановки вопроса пользователю, но и дает возможность отмены операции MessageBoxButtons.RetryCancel Диалоговое окно должно включать в себя кнопки Retry и Cancel. Обычно используется для информирования пользователя о прерванной опера- ции, а также дает возможность повторить или отменить операцию MessageBoxButtons.AbortRetryIgnore Диалоговое окно должно отображать кнопки Abort, Retry и Ignore Обычно используется для информирования пользователя о том, что выполнение серии операций прервалось, а также дает возможность от- мены серии операций, повтора или продолжения прерванной операции Таблица 3.5. Пиктограммы диалоговых окон сообщений Метод Пиктограмма Описание Отображает пиктограмму восклицательного знака Обычно ис- MessageBoxIcon.Exclamation пользуется для предупреждения пользователя о потенциальных проблемах MessageBoxIcon.Information оИ Указывает, что диалоговое окно содержит информационное сообщение для пользователя MessageBoxIcon.Quest ion Отображает пиктограмму вопросительного знака. Обычно при- меняется в диалоговых окнах для постановки вопросов пользо- вателю MessageBoxIcon.Error til Указывает диалоговое окно с символом в красном круге Преду- преждает пользователя об ошибках или важных сообщениях В строке 11 в методе Main объявляются две decimal-переменные — amount и principal — и инициализируется principal к 1000.00. decimal— простой тип данных, используемый для денежных расчетов. .NET Framework также предоставляет простые типы данных float и double для хранения вещественных чисел (т. е. чисел с пла- вающей точкой или числа с десятыми, например, 3.4, 0.0 и -11.19). По умолчанию C# рассматривает такие кон- станты, как 1000.00 (см. листинг 3.3), как тип double, тогда как целочисленные константы, такие как 7 и -22, воспринимаются как тип int. К сожалению, неточность вещественных чисел может вызывать ошибки, результа- том которых могут стать неправильные денежные расчеты. Тип decimal доступен для выполнения корректных денежных расчетов, поэтому значение 1000.00 необходимо преобразовать из типа double в тип decimal. C# обеспечивает два типа преобразования — неявное и явное. Неявное преобразование — процесс, при котором C# преобразует (или продвигает) тип некоторого значения в другой тип так, что тот или иной оператор будет вы- полняться корректно. Неявные преобразования управляются C# и не указываются в коде явно. В листинге 2.3 уже был представлен пример неявного преобразования, где несколько целых чисел были неявно преобразованы в последовательности символов в строках 23,26,29,32, 35 и 38. К сожалению, C# не выполняет неявного преобразования из типа double в тип decimal, потому что такое преоб- разование может вызвать потерю данных (из-за ошибок окружения), либо значение double может быть слишком большим для представления типом decimal. Вместо этого используется явное преобразование, где код явно ука- зывает на то, что должно иметь место преобразование. Явное преобразование может быть выполнено несколь- кими способами. В данном примере применяется операция приведения типа. Эта операция берет один операнд и создает временное значение этого операнда в нужном типе. Операция приведения типа называется унарной операцией (т. е. операцией, принимающей только один операнд) и образуется размещением круглых скобок во- круг имени нужного типа данных результирующего значения. В строке 11 операция приведения типа использу- ется для преобразования double-значения 1000100 в тип decimal. Также возможно указать, что константа имеет тип decimal путем добавления символаm к константе, как в 1000.0m. Строка 12 объявляет переменную rate типа double, которая инициализируется на .05. Обратите внимание, что тип int не может представлять веществен- ные числа, потому что Доли не являются целыми числами. Оператор for (строки 15—22) выполняется 10 раз, варьируя переменную цикла year от 1 до 10 с шагом I. Обра- тите внимание, что year соответствует п в постановке задачи. C# не имеет оператора возведения в степень, по-
Управляющие структуры 79 этому для этой цели в классе Math используется статичный метод Pow. Pow(x, у) рассчитывает значение х, воз- веденное в степень у. Метод Math. Pow берет два аргумента типа double и возвращает значение double. Обратите внимание на использование операции приведения типа — в данном случае для преобразования результата мето- да Math.Pow в значение decimal. Строки 17—18 выполняют расчет из постановки задачи, т. е. а = р (1 + г)" где а— amount, р— principal, г— rate, ап— year. Обратите внимание, что вычисление 1.0 + rate появля- ется в теле оператора for. Данное вычисление дает один и тот же результат каждый раз в цикле, поэтому повто- рять вычисление необходимости нет. Совет по повышению производительности_____________________________________________________ Избегайте размещения выражений, содержащих в цикле неизменяющиеся значения. Такие выражения должны оцениваться только один раз перед циклом. Большинство программ-компиляторов не производят избыточные оценки в выполняемом ими процессе, называемые оптимизацией. Строки 20—21 добавляют дополнительный текст в конец выводимой последовательности символов. Текст со- держит текущее значение year, символ табуляции для расположения второго столбца, результат обращения к методу string. Format (" {0: с}", amount) и символ новой строки для расположения на следующей строке. Об- ращение к методу string.Format преобразует amount в тип string и форматирует последовательность string так, что она будет отображаться с двумя десятичными позициями. Примечание________________________________________________________________________________ Метод Format использует символы форматирования последовательности .NET для представления числовые и денежные значения в соответствии с пользовательскими настройками локали\ Например, USD представлены как $634,307.08, а малазийские ринггиты — R634.307,08. Первый аргумент метода string.Format — это форматирующая строка, тогда как второй аргумент задает зна- чение для форматирования. Обратите внимание на то, что форматирующие строки начинаются с открывающей фигурной скобки ({), а заканчиваются закрывающей фигурной скобкой (}). В этих скобках заключены два зна- чения, разделенные двоеточием. Первое значение — это число, указывающее, какой аргумент должен быть от- форматирован, где о обозначает первый аргумент, найденный после форматирующей строки, 1 — второй аргу- мент и т; д. В данном примере задан о, указывающий, что первый аргумент будет отформатирован после форма- тирующей строки (переменная amount). Аргумент после двоеточия задает форматирование аргумента и называется символом форматирования или спецификатором формата. В данном случае используется символ форматирования с ("currency" — валюта), определяющий, что последовательность должна быть отображена как денежная сумма с двумя цифрами после точки, отделяющей десятичньГе от целых. Существует несколько дру- гих форматирующих символов. Эти коды описаны в документации MSDN. В табл. 3.6 показаны числовые сим- волы форматирования — символы, используемые для представления последовательностей числовых значений. Таблица 3.6. Символы числового форматирования Символы форматирования Описание С или С Форматирует строку как валюту. Предшествует числу с соответствующим символом валюты (для настройки USD— $). Разделяет цифры соответствующим символом разделения (для USD — запятая) и задает количество цифр после точки по умолчанию — две БИЛИ d Форматирует последовательность как десятичную дробь N или п Форматирует последовательность символами разделения (запятые) и двумя десятичными цифрами Били е Форматирует последовательность по научному представлению с шестью десятичными цифра- ми по умолчанию (например, значение 27,900,000 превращается в 2.790000Е+007) F или f Форматирует последовательность фиксированным количеством десятичных знаков (два по умолчанию) G или д Форматирует последовательность как десятичную дробь с помощью символа форматирования Е или F в зависимости от того, который из них обеспечивает более сжатый результат 1 Локалью называется настройка программных средств (например, операционной системы) для отображения информации в соот- ветствии с культурными традициями и языками конкретного географического региона. Настройки докали можно задать в меню запуска Start выбором Control Panel | Regional и Language Options | Regional Options в Windows XP или Control Panel | Regional Options в Windows 2000.
80 Глава 3 Таблица 3.6 (окончание) Символы форматирования Описание РИЛИ p Форматирует последовательность по процентам. По умолчанию это значение умножается на 100 и добавляется символ процентов R ИЛИ Г Обеспечивает обратное преобразование значения, преобразованного в последовательность, без потери точности данных X или X Форматирование последовательности как шестнадцатеричного числа 3.10. Оператор выбора switch Иногда алгоритм содержит серию решений, тестирующую переменную или выражение отдельно для каждого постоянного целочисленного представления или постоянного последовательного представления^ которое мо- жет принять переменная или выражение. Постоянное целочисленное представление — это любое выражение, включающее в себя символ и целочисленные константы, оценивающие до целочисленного значения (например, значения типа int или char). Постоянное последовательное представление — любое выражение, состоящее из строковых литералов, результатом которых всегда является та же последовательность (string). Затем алгоритм выполняет разные операции, исходя из своих значений. Для управления таким принятием решений C# имеет оператор выбора switch. В следующем примере (листинг 3.4) предположим, что группа из 10 студентов сдавала экзамен, и каждый сту- дент получил оценку а, в, с, D или F. Программа вводит буквенные оценки и резюмирует результаты с помощью оператора switch для расчета количества появления каждой из них. В строке 10 объявлена переменная grade типа char. Эта переменная сохраняет пользовательские данные ввода для каждой буквенной оценки. Строки 11—15 определяют переменные счетчиков, используемых программой для каждой буквенной оценки. Строка 17 начинает оператор for, повторяющийся 10 раз. При каждой итерации строка 19 подсказывает пользователю следующую оценку, а строка 20 активизирует метод char.Parse для считывания входных данных пользователя как char. В тело цикла for вложен оператор switch (строки 22—55), обрабатывающий буквенные оценки сту- дентов. Оператор switch состоит из серии меток case и необязательного варианта default. Когда поток правления достигает оператора switch (строка 22), программа оценивает управляющее выражение (в данном примере grade) в скобках, следующих за ключевым словом switch. Значение этого выражения срав- нивается с каждой меткой case, до того, пока не будет найдено соответствие. Например, предположим, что пользователь ввел букву в в качестве оценки студента. После этого в сравнивается с каждым вариантом case оператора switch до тех пор, пока в строке 29 не будет иметь место соответствие (case 'в*:). На данном этапе выполняются операторы для этого case. Для буквы в в строках 31—32 увеличивается количество оценок в пе- ременной bCount, и оператор switch сразу завершается оператором break. Оператор break обеспечивает пере- дачу управления программы на первый оператор, следующий за оператором switch. В этом случае пользователь подходит к концу тела цикла for, так что управление переходит к выражению увеличения переменной цикла в заголовке оператора for. Затем увеличивается переменная счетчика в цикле for, и оценивается условие про- должения цикла для определения необходимости очередной его итерации. { Листинг 3.4. Использование оператора выбора switch ,| 1 // Листинг 3.4: SwitchText.cs 2 // Подсчет буквенных оценок 3 4 using System; 5 б class SwitchText 7 { 8 static void Main(string!] args) 9 { 10 char grade; // одна оценка 11 int aCount = 0, // количество A 12 bCount = 0, // количество В 13 cCount = 0, // количество С 14 dCount = 0, // количество D 15 fCount = 0, // количество F 16
Управляющие структуры 81 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 for (irit i = 1; i <= 10; i++) { Console.Write("Enter a letter grade: "); grade = Char.Parse(Console.ReadLine()); switch (grade) { case 'A': If оценка — прописная A case 'a': // или строчная a ++aCount; break; case 'В': // оценк! — прописная В case 'b': // или строчная b ++bCount; break; case 'С: // оценка — прописная С case 'с': // или строчная с ++cCount; break; / 39' 40 41 42 43 case 'D': // оценка — прописная D case 'd': ZZ или строчная d ++dCount; break; 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 } case 'F*: // оценка — прописная F case 'f: // или строчная f ++fCount; break; default: // обработка всех прочих символов Console.WriteLine( "Incorrect letter grade entered." + "\nGrade not added to totals."); break; }‘ // конец switch } // конец for Console.WriteLine( "\nTotals for each letter grade are:\nA: (0)" + "\nB: {l)\nC: (2}\nD: {3}\nF: {4}", aCount, bCount, cCount, dCount, fCount); } // конец метода Main // конец класса SwitchTest Результат выполнения программы: Enter a letter grade: а Enter a letter grade; А Enter a letter grade: С Enter a letter grade: F Enter a letter .grade: z Incorrect letter grade entered. Grade not added to totals. Enter a letter grade: D . Enter a letter grade: d Enter a letter grade: В 6 Зак. 3333
82 Гпава 3 Enter a letter grade: а Enter a letter grade: С Totals for each letter grade are: A: 3 В: 1 C: 2 D: 2 F: 1 О хорошем стиле программирования_______________________________________________________ Делайте отступы в теле каждого варианта case в операторе switch. Если между значением управляющего выражения и любой меткой case не возникает соответствия, тогда вы- полняется случай default (строка 49). В данном примере строки 50—52 отображают сообщение об ошибке. Обратите внимание, что случай default в операторе switch — не обязательный. Если управляющее выражение не совпадает с case, и нет случая default, тогда управление программы переходит к следующему оператору после оператора switch. Обратите внимание, что в операторе switch могут выполняться операторы только для одного варианта case; после выполнения этих операторов будет выполнен оператор break для текущего case, что обусловит немедленный выход из switch. Каждый случай case может содержать одну операцию, несколько операций или вообще не иметь операций. Случай case без оператора называется пустым и может опускать оператор break. Оператор break необходим для каждого случая case (включая default), содержащего операторы. Последний вариант case в операторе switch не может быть пустым. Если управляющее выражение совпадает с пустым вариантом case, тогда будут выполняться операторы в следующем не пустом случае case. Такое поведение называется сквозным (fall- through). Оно обеспечивает программиста способом указания выполнения определенных операторов для не- скольких case. В листинге 3.4 продемонстрировано сквозное поведение. Например, строки 26—27 выполняют- ся, когда введенная оценка — а или а, а строки 31—32 выполняются, когда введенная оценка — в или ь. Распространенная ошибка программирования_______________________________________________ Не включение оператора break в конце каждого case в операторе switch является синтаксической ошибкой Исключение из этого правила — пустые варианты case, для которых break можно опустить Распространенная ошибка программирования_______________________________________________ При создании case рекомендуется проверять все возможные значения для подтверждения того, что в операторе switch не присутствуют два варианта case для одного проверяемого значения. Если какие-то значения одина- ковы, то будет иметь место ошибка компиляции. Наконец, важно отметить, что оператор switch отличается от других управляющих структур тем, что в case множественные операции не заключаются в фигурные скобки. О хорошем стиле программирования_______________________________________________________ Обеспечьте случай default в каждом операторе switch. Варианты, не проверенные явно в операторе switch, в котором не хватает случая default, игнорируются. Включение случая default заставляет программиста об- рабатывать исключительные условия. Впрочем, бывают ситуации, когда обработка default не требуется. О хорошем стиле программирования_______________________________________________________ Несмотря на то, что варианты case в операторе switch могут встречаться в любом порядке, хорошим тоном программирования считается размещение случая default в последнюю очередь. При использовании оператора switch помните, что выражение после каждого case в конкретном операторе switch должно быть либо вычисляемым значением, либо последовательностью (строкой). Символьная кон- станта представлена как специальный символ в одинарных кавычках (например, *А'). Целочисленная констан- та— это просто целочисленное значение. Выражение после каждого случая case также может быть констан- той — переменной, содержащей значение, которое не меняется на протяжении выполнения всей программы. Такая переменная объявляется с ключевым словом const (см. главу 4). В главе 7 описан более тонкий метод реализации логики switch. В этой главе используется методика, называе- мая полиморфизмом, для создания программ, которые проще обслуживать и расширять, нежели программы, в которых используется логика switch.
Управляющие структуры 83 3.11. Оператор цикла do/while Оператор цикла do/while сходен с оператором while. В операторе while проверка условия продолжения цикла имеет место в начале цикла, до выполнения его тела, тогда как цикл do/while анализирует условие продолжения цикла после выполнения его тела, поэтому тело цикла всегда выполняется, как минимум, один раз. При прекра- щении цикла do/while выполнение продолжается оператором после выражения while. В программе из листин- га 3.5 для выхода значений используется структура do/while. В строках 12—15 демонстрируется цикл do/while. Когда выполнение программы доходит до этого оператора, программа выполняет строку 14, в которой отображается значение counter (на данном этапе— 1). Затем про- грамма оценивает условие в строке 15. Условие в строке 15 увеличивает значение counter, после чего проверя- ет, меньше или равно пяти новое значение. Здесь переменная counter имеет значение 2, которое меньше или равно пяти, поэтому тело оператора do/while выполняется еще раз. После пятого выполнения цикла строка 14 выдает значение 5, а строка 15 увеличивает counter до 6. Затем условие в строке 15 оценивается как ложное, и происходит выход из оператора do/while. Обратите внимание, что в цикле do/while нет необходимости использования фигурных скобок, если в его теле только один оператор. Однако фигурные скобки добавлены, чтобы пользователь не перепутал операторы while и do/while. Например, while (условие) обычно — заголовок оператора while. Оператор do/while без фигурных скобок в теле с одним оператором вы- глядит следующим образом: do оператор; while (условие); что может вызвать путаницу. Последняя строка— while (условие); — может быть неправильно интерпрети- рована читателем как оператор while, содержащий пустой оператор (точку с запятой). Таким образом, цикл do/while с телом с одним оператором, во избежание путаницы, часто записывается следующим образом: do { оператор; } while (условие); О хорошем стиле программирования_________________________________________________ Некоторые программисты всегда включают фигурные скобки в оператор do/while, даже когда в них нет необхо- димости. Такая практика помогает избежать путаницы между операторами while и do/while, содержащими только один оператор. [ Листинг 3.5. Использование оператора цикла do/while -АС 1 // Листинг 3.5: DoWhileLoop.cs 2 // Оператор цикла do/while 3 4 using System; 5 б class DoWhileLoop 7 { 8 static void Main(string(] args) 9 { 10 int counter =1; 11 12 do 13 { 14 Console.WriteLine(counter); 15 } while ( ++counter <= 5 ); 16 17 } // конец метода Main 18 19 } // конец класса DoWhileLoop
84 Гпава 3 Результат выполнения программы: 1 2 3 4 5 3.12. Операторы break и continue Операторы break и continue осуществляют передачу управления. Оператор break при выполнении в циклах while, for, do/while или операторе выбора switch вызывает немедленный выход из этих структур. Выполнение продолжается с оператора, следующего за структурой. Оператор break обычно используется для преждевре- менного выхода из цикла или из оператора switch (как показано в листинге 3.4). В листинге 3.6 продемонстри- ровано использование оператора break в операторе цикла for. Когда оператор if в строке 16 обнаруживает, что значение count равно 5, тогда выполняется оператор break. Это действие прекращает цикл for, и программа переходит к строке 24 (непосредственно после оператора for). Этот оператор в строке 24 сцепляет последовательность "\nBroke out of loop at count = " co значением count. Обратите внимание, что если в операторе for определена переменная count, то здесь она будет недос- тупна. Оператор сцепления создает последовательность, отображаемую в диалоговом окне сообщений, в стро- ках 26—27. Тело цикла выполняется только четыре раза. 1 // Листинг 3.6: BreakTest.cs 2 // Использование оператора break в цикле for 3 4 using System; 5 using System.Windows.Forms; 6 7 class BreakTest 8 { 9 static void Main(string[] args) 10 { 11 string output = ""; 12 int count; 13 14 for (count = 1; count <= 10; count++) 15 { 16 if (count = 5) 17 break; // пропустить оставшийся код в цикле, 18 // если count — 5 19 20 output += count + " "; 21 22 } // конец цикла for 23 24 output + = “\nBroke out of loop at count = " + count; 25 26 MessageBox.Show(output, "Demonstrating the break statement",. 27 MessageBoxButtons.OK, MessageBoxIcon.Information); 28 29 ) // конец метода Main 30 31 } // конец класса BreakTest Рис. 3.2. Использование оператора break в цикле for Результат работы программы представлен на рис. 3.2. Оператор continue при выполнении в циклах while, for или do/while пропускает оставшиеся операторы в теле этих операторов и переходит к следующей итерации цикла. В циклах while и do/while условие продолжения
Управляющие структуры 85 цикла оценивается сразу после выполнения оператора continue. В операторе for выполняется выражение при- ращения или уменьшения, после чего оценивается возможность продолжения цикла. Ранее упоминалось, что в большинстве случаев цикл while может заменить цикл for. Одно исключение имеет место, когда выражение приращения или уменьшения в операторе while следует за оператором continue. В этом случае увеличение или уменьшение не выполняется до проверки условия продолжения повторения и, ' следовательно, цикл while выполняется не так, как цикл for. В листинге 3.7 используется оператор continue в цикле for для пропуска оператора сцепления последователь- ности в строке 19, когда оператор if (строка 15) определяет, что. значение count равно 5. При выполнении опе- ратора continue управление программой продолжается с приращением переменной цикла в операторе for. Совет по повышению производительности______________________________________________________ При правильном использовании операторы break и continue выполняются быстрее, нежели соответствующие им методики структурного программирования. 1 // Листинг 3.7: ContinueTest.cs 2 // Использование оператора continue в структуре for 3 4 using System; 5 us ing System.Windows.Forms; 6 7 class ContinueTest 8 { 9 static void Main(string!] args) 10 { 11 string output - 12 13 for (int count = 1; count <= 10; count ++) 14 { 15 if (count == 5) 16 continue; // пропуск оставшегося кода в цикле, 17 // только если count = 5 18 19 output += count + " "; 20 } 21 22 output + = "\nUsed continue to skip printing 5"; 23 24 MessageBox.Show (output, "Using the continue statement", 25 MessageBoxButtons.OK, MessageBoxIcon.Information); 26 27 } // конец метода Main 28 29 } // конец класса ContinueTest Рис. 3.3. Использование оператора continue в цикле for Результат работы программы представлен на рис. 3.3. О хорошем стиле программирования_____________________________________________________ Некоторые программисты полагают, что операторы break и continue нарушают концепцию структурного про- граммирования. Поскольку подобных эффектов можно добиться через методики структурного программирова- ния, то программисты просто избегают использовать break и continue. Замечание по технологии программирования -___________________________________________ В настоящее время имеет место полемика между качественной разработкой программного обеспечения и полу- чением программных средств, демонстрирующих высокую производительность. Часто выходит так, что одна из этих целей достигается за счет другой. Для всех ситуаций, за исключением тех, что требуют наивысшей произ- водительности, применяйте следующий практический метод: во-первых, старайтесь,' чтобы код был простым и корректным, после чего при необходимости обеспечьте его быстродействие и компактность.
86 Гпава 3 3.13. Логические и условные операции C# предоставляет несколько логических и условных операций, которые можно использовать для образования сложных условий. Операции следующие: □ && — условное "И" (AND); □ & — побитовая операция логического "И" (AND); □ 11 — условное "ИЛИ" (OR); □ | — побитовая операция логического "ИЛИ" (OR); □ л — побитовая операция логического исключающего "ИЛИ" (OR); □ ! — логическое отрицание. В данном разделе будут рассмотрены примеры, в которых использована каждая из этих операций. Распространенная ошибка программирования___________________________________________________ Пробел между символами операций && и 11 является синтаксической ошибкой. Предположим, необходимо убедиться в том, что два условия в программе истинны до выбора определенного пути выполнения. В этом случае условную операцию && можно использовать следующим образом: if (gender = 1 && age >= 65)* ++seniorFemales; Данный оператор if содержит два простых условия. Условие gender = 1 может быть оценено для определения того, каков пол человека: мужской или женский. Условие аде >= 65 оценивается для определения того, являет- ся ли человек пенсионером. Эти два простых условия оцениваются в первую очередь, потому что приоритет выполнения операций — и >= более высок, нежели &&. Тогда оператор if рассматривает объединенное условие gender = 1 && age >= 65 Данное условие истинно, если и только если истинны оба простых условия. Наконец, если данное объединенное условие истинно, тогда тело оператора приращивает счетчик seniorFemales на 1. Если одно или оба из простых условий являются ложными, тогда программа пропускает шаг приращения и переходит к оператору, следующе- му за оператором if. Теперь рассмотрим операцию ||. Предположим, что необходимо убедиться в том, что какой-либо один или оба условия истинны, до того как будет ыбран определенный путь выполнения. В этом случае условную операцию 11 можно использовать следующим образом: if (semesterAverage >=90 I | finalExam >= -90) Console.WriteLine("Student grade is A”); Данный оператор if также содержит два простых условия. Условие semesterAverage >= 90 определяет, заслу- живает ли студент оценки А за курс из-за того, что он усиленно работал в течение семестра. Условие finalExam >= 90 определяет, заслуживает ли студент оценки а за курс из-за того, что он сдал окончательный экзамен "на отлично". Затем оператор if рассматривает объединенное условие semesterAverage >=90 I| finalExam >= 90 и ставит студенту оценку а, если истинно каждое простое условие или оба. Обратите внимание, что сообщение "Student grade is А" печатается только в том случае, если оба простых оператора не ложны. Операция && имеет более высокий уровень предшествования, нежели операция | |. Обе операции ассоциируются слева направо. Выражения, содержащие операции && или 11, оцениваются только до тех пор, пока не будет из- вестна их истинность или ложность. Следовательно, оценка выражения gender == 1 && > = 65 немедленно прекращается, если gender не равно 1 (т. е. если одно условие — ложно, тогда ложно все выраже- ние целиком), и продолжается, если gender равно 1 (т. е. все выражение целиком может быть истинным, если условие аде >= 65 — истинно). Такая особенность производительности для оценки условного выражения AND и условного выражения OR называется оценкой "короткого замыкания". Совет по повышению производительности______________________________________________________ В выражениях, где используется операция &&, если отдельные условия независимы друг от друга, тогда сделай- те так, чтобы крайнее левое условие вероятнее всего было ложным В выражениях, где используется операция 11, сделайте так, чтобы крайнее левое условие вероятнее всего было истинным. Такое применение оценки ко- роткого замыкания может сократить время выполнения программы.
Управляющие структуры 87 Побитовые операции логического "И" (&) и логического "ИЛИ" (|) сходны с условными операциями "И" и "ИЛИ", соответственно, с одним исключением: логические операции всегда оценивают оба своих операнда (т. е. оценки "короткого замыкания" нет). Следовательно, условие gender == 1 & age >= 65 оценивает age >= 65, независимо от того, равно ли gender единице. Эта особенность полезна, если правый операнд логических операций "И" и "ИЛИ" включает в себя необходи- мый побочный эффект— модификацию значения переменной. Например, выражение birthday = true & Console.WriteLine(age) гарантирует, что переменная age печатается в предыдущем выражении, независимо от того, истинно общее вы- ражение или ложно. Подобным же образом, если нужно, чтобы условие в правом операнде стало результатом математической операции, и необходимо, чтобы эта операция выполнялась в любом случае, можно воспользо- ваться операцией |. ' Распространенная ошибка программирования____________________________________________________ Избегайте использования выражений с побочными эффектами в условиях. Побочные эффекты могут выглядеть разумными и обусловленными, однако они часто вызывают незначительные ошибки и могут сбивать с толку коллег-программистов, которые считывают или обслуживают код. Условие, содержащее побитовую операцию логического исключающего "ИЛИ" (XOR) (иногда его называют логическим оператором XOR), истинно, если и только если результатом одного из операндов становится ис- тинное значение, а другого — ложное. Если оба операнда истинны или ложны, то все условие целиком будет ложным. Данная операция оценивает оба операнда (т. е. оценки "короткого замыкания" нет). C# предоставляет операцию логического отрицания ! для того, чтобы программист мог "обратить" значение условия. В отличие от операций &&, &, 11, | и Л, объединяющих два условия (и, следовательно, являющихся би- нарными операциями), операция логического отрицания имеет в качестве операнда только одно условие (следо- вательно, она является унарной операцией). Операция логического отрицания размещается перед условием для выбора пути выполнения, если оригинальное условие (без операции логического отрицания) — ложно. Обрати- те внимание, что операция логического отрицания имеет более высокую степень предшествования, чем опера- ция равенства. Консольное приложение в листинге 3.8 демонстрирует все условные и логические операции путем вывода их таблиц истинности. [ Листинг 3.8. Условные и логические операции ______ _____ _ J 1 // Листинг 3.8: LogicalOperators.cs 2 // Демонстрация логических операций 3 4 using System; 5 6 class LogicalOperators 7 { 8 // главная точка входа для приложения 9 static void Main(string[] args) 10 { 11 // тестирование условной операции AND (&&) 12 Console.WriteLine("Conditional AND (&&)" + 13 "\nfalse && false: " + ( false && false ) + 14 "\nfalse && true: " + ( false && true ) + 15 "\ntrue && false: " + ( true && false ) + 16 "\ntrue && true: " + ( true && true ) ); 17 18 // тестирование условной операции OR (II) 19 Console.WriteLine("\nConditional OR (II)" + 20 "\nfalse I I false: " + ( false I I false ) + 21 "\nfalse I I true: " + ( false I| true ) + 22 "\ntrue I I false: " + ( true I I false ) + 23 "\ntrue || true: " + ( true || true ) ); 24
88 Глава 3 25 // тестирование логической операции AND (&) 26 Console.WriteLine("\nLogical AND (&)" + 27 "\nfalse & false: " + ( false & false ) + 28 "\nfalse & true: " + ( false & true ) + 29 ”\ntrue & false: " + ( true & false ) + 30 "\ntrue & true: " + ( true & true ) ); 31 32 // тестирование логической операции OR (/) 33 Console.WriteLine("\nLogical OR (I)" + 34 "\nfalse 1 false: " + ( false | false ) + 35 "\nfalse I true: " + ( false I true ) + 36 "\ntrue I false: " + ( true I false ) + 37 ”\ntrue I true: ” + ( true I true ) ); 38 39 // тестирование логического исключающего OR (Л) 40 Console.WriteLine("\nLogical exclusive OR (л)" + 41 "\nfalse л false: " + ( false л false ) 42 "\nfalse л true: ” + ( false л true ) + 43 "\ntrue л false: " + ( true л false ) + 44 ”\ntrue л true: ” + ( true л true ) ); 45 46 // тестирование логической операции NOT (!) 47 Console.WriteLine ("\nLogical NOT (!>" + 48 "\n!false: " + ( !false ) + 49 "\n!true: " + ( ! true ) ); 50 51 } // конец метода Main 52 53 } // конец класса LogicalOperators Результат выполнения программы: Conditional AND (&&) false && false: Falsfe false && true: False true && false: False true && true: True Conditional OR (I|) false || false: False false I I true: False true I I false: True true I I true: True Logical AND (&) false & false: False false & true: False true & false: False true & true: True Logical OR (I) false I false: False false I true: True true I false: True true I true: True Logical exclusive OR (Л) false Л false: False false л true: True true л false: True true л true: False Logical NOT (!) !false: True !true: False
Управляющие структуры 89 В строке 6 начинается класс LogicalOperators. Метод Main (строки 9—51) содержит код данной программы В строках 12—16 демонстрируется операция &&, а в строках 26—30 — операция &. Остальная часть метода ма ш демонстрирует операции 11, |,Л и !. Когда значение типа bool сцепляется со значением типа string, C# преобразует это значение bool в представ- ление последовательности, которое будет либо "False", либо "True". В табл. 3.7 перечислены операции с учетом приоритета их выполнения (сверху вниз в нисходящем порядке) и указана их ассоциативность. Таблица 3.7. Приоритет и ассоциативность рассмотренных операций Операция Ассоциативность Тип ( ) ++, — Слева направо Справа налево Круглые скобки Унарный постфикс ++. ”, +, ". ! Справа налево Унарный префикс *. /, % Слева направо Мультипликативный +, - Слева направо Аддитивный <, <=, >, >= Слева направо Реляционный == 1 = » • Слева направо Равенство & Слева направо Побитовое логическое "И" Л Слева направо Логическое исключающее "ИЛИ" (XOR) 1 Слева направо Побитовое логическое "ИЛИ" (OR) && Слева направо Условное "И" (AND) 11 Слева направо Условное "ИЛИ" (OR) 7: Справа налево Условный = += -= *= /= %= Справа налево Присваивание 3.14. Введение в программирование Windows-приложений Современные программисты нуждаются в программных средствах с расширенными графическими пользова- тельскими интерфейсами (GUI), в которых можно пользоваться кнопками, выбирать элементы меню и делать многое другое. В данной и предыдущих главах уже рассматривался процесс создания консольных приложений. Однако большинство используемых в промышленности программ C# являются Windows-приложениями с GUI. В главе 2 представлена концепция визуального программирования, позволяющая создавать GUI без написания программного кода. В настоящем разделе визуальное программирование объединяется с традиционными мето- диками программирования, представленными в данной и предыдущей главах. С помощью такой комбинации можно значительно расширить Windows-приложение, описанное в главе 2. Загрузите в IDE проект ASimpleProject из главы 2. Для упрощенной идентификации формы и ее элементов управления в программном коде измените свойства Name формы, метки и рамки изображения на ASimpleProgram, welcomeLabel и bugPirctureBox соответственно. Примечание_______________________________________________________________________ Данное свойство может выглядеть в окне Properties как (Name). Для изменения свойств компонента GUI выберите этот компонент (щелчком левой кнопки мыши на нем) в окне проектирования, затем найдите в окне Properties свойство, которое необходимо изменить. Щелкните в строке справа от имени свойства для ввода нового значения; по окончании нажмите клавишу <Enter>. При визуальном программировании IDE генерирует программный код, формирующий GUI. Этот код содержит команды для создания формы и каждого ее элемента управления. В отличие от консольного приложения, про- граммный код приложения Windows изначально не отражается в окне редактирования. Как только проект про- граммы (например, ASimpleProgram) открывается в IDE, программный код можно просмотреть выбором команд View | Code. На рис. 3.4 показан редактор кода с отображенным в нем кодом программы.
90 Гпава 3 Каждое Windows-приложение состоит, как минимум, из одного класса, наследующего из класса Form (представ- ляющего форму) в пространстве имен System.windows.Form библиотеки классов .NET Framework. Ключевое слово class начинает определение класса, и за ним непосредственно следует имя класса (ASimpleProgram). Помните, что имя формы задается с помощью свойства Name. Двоеточие (:) указывает на то, что класс ASxnpleProgram наследует имеющиеся части из другого класса. Класс, из которого наследует ASimpleProgram,— здесь System.Windows.Forms.Form— появляется С правой стороны от двоеточия. В данном отношении наследования Form называется базовым классом (или надклассом), a ASimpleProgram называется производным классом (или подклассом). При наследовании определение класса ASimpleProgram имеет атрибу- ты (данные) и поведения (методы) класса Form. В главе 5 рассматривается значимость ключевого слова public. Маркеры Свернутый код Свернутый комментарий Рис. 3.4. IDE с кодом для программы, представленной в листинге 2.3 Примечание__________________________________________________________________________________ Изменение имени управляющего элемента в окне Properties может не изменить все случаи появления имени управляющего элемента в коде. Пользователь должен просмотреть код и исправить имена, которые не были изменены IDE. Например, первоначальное имя формы (и имя класса) было Formi. Найдите в коде Forml и из- мените все оставшиеся экземпляры на ASimpleProgram. Ключевым преимуществом наследования из класса Form является то, что кто-то уже определил "то, что должно быть формой". Операционная система Windows предполагает, что каждое окно (т. е. форма) будет иметь кон- кретные атрибуты и поведения. Однако, по причине того, что класс Form уже обеспечивает такие возможности, программистам нет нужды "изобретать колесо" определением всех этих особенностей самостоятельно. Факти- чески, класс Form уже имеет сотни методов! До сих пор при рассмотрении программ авторы пользовались толь- ко одним методом (wain), так что можно себе представить объем работы, потребовавшийся для создания класса Form. Использование двоеточия для расширения из класса Form обеспечивает оперативное создание форм. В окне редактирования кода (см. рис. 3.4) обратите внимание на то, что определенные части текста заключены в небольшие квадратные скобки с небольшими рамками слева от них со знаком плюс. Рамки со знаком плюс ука- зывают на то, что этот раздел кода свернут. Несмотря на то, что свернутый код невидим, он является частью программы. Свертывание кода позволяет программистам скрывать его в редакторе, чтобы сосредоточиться на более мелких (и, возможно, более важных) сегментах. При нажатии на рамку с плюсом код развертывается (отображается весь сегмент кода). Можно щелкнуть мышью на рамке со знаком минус слева от расширенного кода для его свертывания. Если программист хочет оперативно просмотреть свернутый код, нужно поместить курсор на рамку, относящуюся к нужной части кода. При этом свернутая часть кода отобразится в небольшом окне. Внешний вид свернутого кода различается, в зависимости от природы кода. На рис. 3.4 демонстрируется свер- нутый комментарий (/**/) в рамке, тогда как свернутые операторы выглядят в рамке как эллипсы (...). Нако- нец, в программе C# можно создать область кода. Область — это часть кода, вводимая между тегами #region и #endregion. Эти теги обозначают текст, появляющийся в рамке, когда код сворачивается. Один из таких регио- нов ^показан в нижней части рис. 3.4 в виде прямоугольника, содержащего текст Windows Form Designer generated code. Описание в прямоугольнике указывает на то, что свернутый код был создан Windows Form De- signer (т. е. частью IDE, формирующей код для GUI). Данный свернутый код содержит код, созданный IDE для формы и ее элементов управления, а также код, позволяющий программе выполняться. Вкратце опишем созда- ние области. При первом рассмотрении расширенный код (рис. 3.5) выглядит слишком сложным. Этот код создан IDE и, как правило, не редактируется программистом. Такой код присутствует в любом приложении Windows. То, что этот код создается IDE, экономит значительное количество времени. В противном случае, писать код пришлось бы самому программисту. Подавляющая часть показанного кода еще не описывалась, поэтому читателю могут быть не понятны принципы его работы. Однако определенные логические конструкции программирования, на-
Управляющие структуры 91 пример комментарии и структуры управления, должны быть знакомы. По мере изучения C# (особенно в гла- вах 5—10) назначение кода, созданного IDE, станет более понятным. Ближе к верхней части рис. 3.5 заметен текст flregion. Этот текст указывает на начало свертываемой области кода. После flregion виден текст, который будет отображен при свертывании области. Данная область заканчи- вается с появлением текста flendregion по окончании метода InitializeComponent. private void InitialixeConiponent () Маркеры «region Windows Гоги Designer generated code editor nev System.Resources.Res // welcomeLabel Required method for Designer support - do the contents of this method with the code Развернутый код System. Resources. Resourcellhnagex resources this.weIcomeLabel - new System.Windows.Forms.Label(); this.bugPiccureBox - new System.Windows.Forms.PictureBox(); this.SuspendLayout(); f VartPage Forfut^|Daagi{* Fo<m1.v«| ij^ASbipiPiomctASx" Рис. 3.5. Расширенный код сгенерированный Windows Form Designer this, we Iconic Lube 1. Font “ nev System-Drawing.Font("Rictosoft Sans Seri this.weleoneLabel.Location “ net System.Drawing.Point(19, 8): this.weIcomeLabel.Name « "welcomeLabel"; chls.welcomeLabel.SiEe « new System.Drawing.Sire(254, 48); this.welcomeLabel.Tabindex « 0; this-welcomeLabel.Text - "Visual Ctt .NET"; this.weIcomeLabel.TextAlign = System-Drawing.ContentAlignment.TopCent При создании приложения в главе 2 для задания свойств формы, метки и рамки изображения использовалось окно Properties. После указания свойств сразу обновляется форма или элемент управления. Формы и элементы управления содержат набор свойств по умолчанию, изначально показанные в окне Properties при выборе фор- мы или элемента управления. Эти свойства по умолчанию обеспечивают изначальные характеристики формы или элемента управления при их создании. Когда элемент управления, например метка, помещается в форму, IDE добавляет код классу (например, ASimpleProgram), создающему элемент управления, и указывает некоторые из значений свойств элемента управления, например, имя элемента управления и его местоположение в форме. На рис. 3.6 показана часть кода, сгенерированная IDE для настройки свойств метки (в данном случае, welcomeLabel). Набор свойств метки включает в себя Font (гарнитура шрифта), Location (местоположение), Name (имя), Text (текст) и TextAlign (выравнивание текста). Вспомните из главы 2У что в окне Properties свой- ства метки Name, Text и TextAlign задаются явно (открыто). Другие свойства, например, Location задаются только тогда, когда метка помещается на форму. Инициализация Щелкните здесь для переключения в режим разработки Щелкните здесь для переключения в режим просмотра кода welcomLabel Рис. 3.6. Код, сгенерированный IDE для welcomeLabel
92 Гпава 3 Присваиваемые свойствам значения основаны на значениях, указываемых в окне Properties. Теперь продемон- стрируем, как IDE обновляет код, сгенерированный Windows Form Designer при изменении значения свойства в окне Properties. Во время этого процесса программисты должны переключаться из режима просмотра кода в режим проектирования. Для переключения режимов выберите соответствующие вкладки— Forml.cs* для просмотра кода и Fornil.cs*(Design] для просмотра проектирования. Примечание_________________________________________________________________________ Звездочка после имени файла означает, что файл был изменен, и, следовательно, его нужно сохранить. Другой способ: выбор View | Code или View | Designer соответственно. Выполните следующие шаги: 1. Изменение свойства Text метки с помощью окна Properties. Вспомните, что свойства можно изменять в ре- жиме проектирования щелчком на форме или элементе управления для их выбора и изменением соответст- вующего свойства в окне Properties. Измените свойство Text метки на Deitel (рис. 3.7). 2. Изучение изменений в режиме просмотра кода. Перейдите в режим просмотра кода (View | Code) и проана- лизируйте код. Обратите внимание, что свойству Text метки теперь присвоен текст, введенный в окно Properties (рис. 3.8). Когда свойство изменяется в режиме проектирования, тогда Windows Form Designer обновляет соответствующую строку кода в классе для обновления нового значения. Свойство Рис. 3.7. Использование окна Properties для указания значения свойства Рис. 3.8. Код, сгенерированный Windows Form Designer, отражающий новые значения свойств 3. Изменение значения свойства в режиме просмотра кода. В редакторе кода найдите три строки комментариев, указывающих на инициализацию welcomeLabel, и измените string, присвоенную this.welcomeLabel.Text из Deitel на visual c# .net (рис. 3.9). Затем перейдите в режим проектирования (View | Designer). Теперь мет- ка отображает обновленный текст, а окно Properties для welcomeLabel отображает новое значение Text (рис. 3.10). Примечание_________________________________________________________________________________ Значения свойств не должны задаваться с помощью методик, описанных в данном шаге. Здесь значение свой- ства в IDE-сгенерированном коде модифицируется только в качестве демонстрации взаимоотношения между программным кодом и Window Form Designer 4. Изменение свойства Text метки во время выполнения. В предыдущих шагах свойства задавались во время проектирования. Однако часто бывает необходимо модифицировать свойство во время выполнения про- граммы. Например, для отображения результата вычислений метке можно присвоить строку, содержащую результат. В консольных приложениях такой код размещается в методе Wain. В Windows-приложениях необ-
Управляющие структуры 93 ходимо создать метод, который будет выполняться, когда форма загружается в память во время работы про- граммы. Подобно Main, этот метод вызывается во время выполнения программы. При двойном щелчке кноп- кой мыши на форме в режиме проектирования в класс добавляется метод с именем ASimpleProgram_Load (рис. 3.11). Курсор помещается на тело определения метода ASimpleProgram Load. Следует отметить, что ASimpleProgram Load не является частью кода, сгенерированного Windows Form Designer. Добавьте в тело определения метода оператор welcomeLabel.Text = "С#"; (рис. 3.11). В C# свойства оцениваются размеще- нием имени свойства (т. е. Text) после имени объекта (т. е. welcomeLabel), разделенные точечным операто- ром. Данный синтаксис похож на применяемый при оценке методов объекта. Обратите внимание, что функ- ция IntelliSense отображает свойство Text в списке членов после ввода имени класса и точечного оператора (рис. 3.12). В главе 5 рассматриваются способы создания собственных свойств. Свойство Text Рис. 3.9. Изменение свойства в редакторе в режиме просмотра кода Значение свойства Text-i Рис. 3.10. Новое значение свойства Text, отраженное в режиме проектирования: а — метка в форме; б — свойство Text в окне Properties Метод ASimpleProgram_Load Рис. 3.11. Метод ASimpleProgram_Load 5. Изучение результатов работы метода ASimpleProgram_Load. Обратите внимание, что текст в метке выглядит так же, как и на рис. 3.10. В окне Properties по-прежнему отображается значение "visual c# .net" в каче- стве свойства Text метки, и что сгенерированный IDE код не изменился. Выберите команду Build | Build, а затем команду Debug | Start для запуска программы. Как Только отобразится форма, в метке отобразится присвоенный ASimpleProgram_Load текст (рис. 3.13). 6. Приостановка выполнения программы. Щелкните кнопку закрытия для остановки программы. Еще раз обра- тите внимание на то, что и метка, и свойство Text метки содержат текст visual c# .net. Код, сгенерирован- ный IDE, также содержит текст visual c# .net, который присвоен свойству Text метки.
94 Гпава 3 [property] tiring Control. Т ext et~ or; Г ‘ thetextassociated;vth this control Список СВОЙСТВ,-------j> открываемый благодаря функции IntelliSense j ‘•Я TextAigrChanged ; Я TextChanged Й? Top Всплывающая подсказка выводит - описание выбранного свойства Рис. 3.12. Функция IntelliSense Visual Studio .NET Рис. 3.13. Результат изменения значения свойства во время выполнения программы В данной главе рассматривалась компоновка программ из структур управления, содержащих операции и реше- ния. В главе 4 представлен другой блок структурирования программы, называемый методом. Читатели научат- ся строить крупные программы путем объединения методов, состоящих из структур управления. Здесь также рассматривается продвижение методами возможности многократного использования программных средств. В главе 5 более подробно рассматривается другой блок структурирования программ, называемый классом. По- сле этого мы обсудим, как из классов создаются объекты, а затем перейдем к изучению объектно- ориентированного программирования — основной темы данной книги. 3.15. Резюме Программное управление имеет важное значение в любом языке программирования, поскольку оно определяет порядок, в котором в компьютерной программе выполняются операторы. Обычно операторы выполняются по очереди, в порядке их появления. Этот процесс называемся последовательным выполнением. Существует не- сколько структур управления, позволяющих программисту указать, что следующим для выполнения оператором будет не очередной в последовательности. Данная методика называется передачей управления. Условные операторы if и if/else используются для выбора операций, исходя из истинности или ложности ус- ловия. Эти структуры Moiyr быть одинарной и двойной выборки соответственно. Оператор switch, известный как структура множественной выборки, выбирает между несколькими операциями. Операторы циклов повторяют операции, исходя из истинности или ложности условия. Циклы C# включают в себя операторы while, for и do/while. Оператор break служит для осуществления немедленного выхода из структуры, тогда как оператор continue используется для выполнения следующей итерации в операторе цикла. Унарные операции инкремента и декремента прибавляют или вычитают единицу из переменной. Условные и логические операции нужны для образования сложных условий путем объединения простых условий. Как правило, при создании программных приложений разработчики объединяют принципы визуального про- граммирования с традиционными принципами. Такое объединение обеспечивает приложениям интерактивность и расширенный графический пользовательский интерфейс. Visual Studio .NET позволяет разработчикам выпол- нять задачи (например, изменение значения свойства) как программным, так и визуальным способами.
ГЛАВА 4 Методы и массивы Форма всегда следует за функцией. Луис Генри Салливан Один из многих. Виргилий Теперь пойди, начертай это на доске у них, и внеси это в книгу. Исайя 30:8 Когда назовете мне это, улыбнитесь. Оуэн Уистер Темы данной главы: □ модульное построение программ из методов; □ ознакомление с распространенными математическими методами, имеющимися в библиотеке классов FCL; □ представление механизмов передачи информации между методами; □ понятие об ограничении видимости идентификаторов особыми областями программ; □ ознакомление со структурой массива данных; □ объявление массива, инициализации массива и обращения к отдельным его элементам; □ объявление многомерных массивов и манипуляции ими. 4.1. Введение Большинство компьютерных программ, решающих реальные проблемы, — намного более крупные, нежели те, что описывались в предыдущих главах. Опыт показал, что лучшим способом разработки и обслуживания круп- ных программ является их компоновка из маленьких простых частей. Такая методика называется "разделяй и властвуй". В предыдущих главах для решения определенных задач вызывались конкретные методу. Также бы- ли декларированы собственные методы Main, определяющие операции для программы. В данной главе эти ме- тоды рассматриваются подробно с описанием ключевых особенностей языка С#, упрощающих проектирование, реализацию, функционирование и техническое обслуживание крупных программ. В главе также представлены структуры данных. Массивы — это структуры данных, состоящие из элементов данных одного типа. Массивы — это "статичные" логические категории; это означает, что после создания их размер не меняется. В данной главе демонстрируется создание и доступ к массивам,, после чего полученные знания используются для выполнения более сложных манипуляций массивами, включая мощные методики по- иска и сортировки. Затем авторы демонстрируют создание более сложных — многомерных — массивов. В гла- ве 20 представлены динамические структуры данных, такие как списки, очереди, стеки и деревья, которые мо- гут увеличиваться и уменьшаться во время выполнения программы. В той же главе представлены предвари- тельно заданные структуры данных С#, дающие программисту возможность использовать существующие структуры данных для списков, очередей, стеков и деревьев, вместо того, чтобы заниматься изобретением ко- леса. 4.2. Методы в C# Программы в C# создаются путем объединения новых методов и классов, которые программист разрабатывает с помощью "предварительно пакетированных" методов и классов, доступных в библиотеке классов .NET
96 Глава 4 (Framework Class Library, FCL). В данной главе упор сделан на методы Классы подробно рассматриваются в главе 5. Библиотека FCL предоставляет богатую коллекцию классов и методов для выполнения распространенных ма- тематических вычислений, построковой обработки, манипуляций символами, операций ввода/вывода, проверки ошибок и многих других полезных операций. Такой набор заранее подготовленного кода облегчает работу про- граммиста предоставлением ему множества полезных возможностей. Методы FCL являются частью .NET Framework, включающей классы FCL Console и MessageBox, использованные в более ранних примерах. Замечание по технологии программирования_______________________________________________ Познакомьтесь с расширенным набором классов и методов FCL. Замечание по технологии программирования_______________________________________________ По возможности пользуйтесь классами и методами .NET Framework, вместо написания новых классов и мето- дов. Такая практика сокращает как время на проектирование, так и количество ошибок. Программист способен писать собственные методы для определения специфических задач, которые можно ис- пользовать в разных точках программы. Такие методы называются методами, определенными программиста- ми. Фактические операторы, определяющие метод, пишутся только один раз и скрываются от других методов. Метод активизируется (т. е. запускается на решение предназначенной задачи) вызовом метода. Вызов метода обозначает название метода и может представлять информацию (в виде аргументов), необходимую вызванному методу для решения поставленной задачи. По завершении вызова метод либо возвращает результат вызываю- щему методу, либо просто передает ему управление. Вызывающий метод "не знает", как вызванный метод вы- полняет поставленную задачу; в частности, вызванный метод может вызывать другие методы, и вызывающий метод не будет "знать" об этих вызовах. Далее будет видно, как подобное "сокрытие" подробностей реализации продвигает эффективные методы технологического проектирования программного обеспечения. Методы вызываются написанием имени метода, за которым следует левая круглая скобка, аргумент (или спи- сок аргументов, разделенных запятыми) метода и правая круглая скобка. Скобки могут быть пустыми, если вы- званный метод не требует аргументов. Объявленные в определениях методов переменные являются локальными: об их существовании "знает" только определяющий их метод. Большинство методов имеют список параметров, дающий вызовам методов возмож- ность передачи информации между методами. Параметры метода также являются локальными переменными для этого метода и невидимы для других методов. 4.3. Определения методов В каждой из представленных ранее программ содержится минимум одно определение метода (например, Main), вызывающее существующие методы FCL для решения задач программы. Теперь рассмотрим механизм написа- ния заказных методов. Рассмотрим Windows-приложение из листинга 4.1, в котором используется метод Square для вычисления квад- ратных корней целых чисел от 1 до 10. Следует отметить, что в этом примере показан весь код, сгенерирован- ный Windows Form Designer (программа-проектировщик форм Windows). Далее на протяжении книги авторы опускают части сгенерированного кода, не относящиеся к делу. В программах, из которых удален код, есть комментарий, указывающий на то, где в оригинале исходного файла появляется код, сгенерированный Visual Studio .NET. Полные примеры доступны по адресу www.deitel.com под ссылкой Downloads/Resources; их мож- но скомпилировать и запустить для получения тех же результатов, что показаны на иллюстрациях в книге. В данном примере код, сгенерированный Visual Studio .NET, не рассматривается; обсуждение перенесено в гла- ву 9 после ознакомления читателей с материалами по объектно-ориентированному программированию (см. гла- вы 5—7). До сих пор в рассмотренных программах использовались методы класса Console для получения пользователь- ских данных ввода из консольного окна. Эти программы выводят результаты либо в консольное окно, либо в окно MessageBox. Несмотря на действенность данных подходов для получения входных данных от пользователя и отображения выходных данных, их возможности весьма ограничены: консольное окно может принять от пользователя только одно значение за один раз, и диалоговое окно сообщений может отобразить только одно сообщение. Гораздо более распространен метод, когда программы считывают множественные входные данные одновременно (например, когда пользователь вводит имя и информацию об адресе), либо одновременно ото- бражают сразу несколько частей данных (например, — для данного примера — квадратные корни целых чисел от 1 до 10). Для представления более проработанных пользовательских интерфейсов программа в листинге 4.1
Методы и массивы 97 демонстрирует две концепции GUI: прикрепление множественных компонентов GUI к приложению и управле- ние событиями. t - Результат работы программы представлен на рис. 4.1. 1 // Листинг 4.1: Squarelnt.cs 2 // Демонстрирует определенный программистом метод Square 3 4 using System; 5 using System.Drawing; 6 using System.Collections; 7 using System.Component.Model; 8 using System.Window.Forms; 9 using System.Data; 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 namespace Squarelnt { /// <summary> /// Описание .резюме для Squarelnt. /// </summary> public class Squarelnt : System.Windows. Forms. Form { private System.Windows.Forms. Button calculateButton; private System.Windows.Forms.Label outputLabel; III <summary> /// Требуемая переменная проектировщика. /// </summary> private System.ComponentModel.Container components = null; public Squarelnt () { // // Требуется для поддержки Windows Form Designer // InitializeComponent (); } III <summary> /// Сброс любых используемых ресурсов. Ill </summary> protected override void Dispose (bool disposing) { if (disposing) { if (components ! = null) { components.Dispose(); ) } base.Dispose(disposing); ) #region Windows Form Designer generated code /// <summary> /// Требуемый метод для поддержки Designer — не модифицировать /// содержимое этого метода с помощью редактора кодов. /// </summary> private void IntializeComponent() { this.calculateButton = new System.Windows.Forms. Button(); 7 Зак. 3333
98 Гпава 4 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 this.outputLabel = new System.Windows.Forms.Label(); this.SuspendLayout(); // // calculateButton // this.calculateButton.Location = new System. Drawing. Point (.32, 8); this.calculateButton.Name = "calculateButton"; this.calculateButton.Size = new System.Drawing.Size (120, 23); this.calculateButton.Tabindex = 0; this.calculateButton.Text = "Calculate Squares"; this.calculateButton.Click += new. System.EventHandler( this.calculateButton_Click); // // outputLabel // this.outputLabel.Location = new System.Drawing.Point (16, 48); this.outputLabel.Name = "outputLabel"; this.outputLabel.Size = new System.Drawing.Size (152, 144); Кнопка this.outputLabel.Tabindex = 1; // // Squarelnt // this.AutoScaleBaseSize = new.System.Drawing.Size (5, 13); this.Clientsize = new.System.Drawing.Size (184, 197); this.Controls.AddRange( new.System.Windows.Forms.Control[] { this.outputLabel, this.calculateButton)); this.Name = "Squarelnt"; this.Text = "Squarelnt"; this.ResumeLayout (false); } #endregion /// <summary> /// Главная точка входа для приложения /// </summary> [STAThread] static void Main () { Application.Run(new Squarelnt()); ) 11 Определение метода Square int Square(int y) { return у * у; // возврат квадратного корня у ) // конец метода Square private void calculateButton_Click ( object sender, System.EventArgs e) { outputLabel.Text = ""; Squarelnt -----p Ovulate Squares | ».............. ,IIJ ' JThe square of 1 is 1 The square of 2 >$ 4 Thesquereof 3isS The square of 4 is T6 ® 4': Thesquareof 5 ts25 V The squareof 6 is 36 f Trie square icf 7is4! The square ей 8 is 64 Therqutre of $b81 Thesquareof lOislOO л Рис. 4.1. Демонстрация работы метода Square Метки
Методы и массивы 99 120 // цикл 10 раз 121 for (int counter = 1; counter <= 10; counter ++) 122 { 123 // расчет квадратного корня счетчика и его 123а // сохранение в результате 124 int result = Square(counter); 125 126 // добавление результата в outputLabel 127 outputLabel.Text += "The square of " + counter + 128 ” is " + result + "\n"; 129 } 130 131 } // конец метода calculateButton_Click 132 133 J // конец класса Squarelnt 134 135 } // окончание пространства имени Squarelnt Для создания графического пользовательского интерфейса для данной программы "перетяните" соответствую- щие компоненты (Button и Label) из окна Toolbox (Панель инструментов) в форму в Windows Form Designer. Эта операция была продемонстрирована в разд. 2.6. Расположите компоненты, как показано на рис. 4.1, и за- дайте свойства Text для Label и Button. Программа вызывает специальный метод, называемый обработчиком событий, когда пользователь щелкает кнопкой мыши на кнопке Calculate Squares. Обработчик событий — это метод, выполняющий определенную операцию в ответ на событие. События имеют место, когда в графическом пользовательском интерфейсе выполняются определенные операции, например, когда пользователь щелкает кнопкой мыши. Использование компонентов GUI и событий дает программистам возможность создавать при- ложения, взаимодействующие с пользователями более сложными путями, нежели рассматривалось ранее. В Windows Form Designer двойной щелчок кнопкой мыши на объекте компонента GUI заставляет Visual Studio .NET генерировать пустой метод обработчика событий. Имя метода обработчика событий по умолчанию совпа- дает с именем компонента GUI, за которым следует символ подчеркивания и имя события Затем программист может заполнить метод обработчика событий кодом, выполняющим соответствующую задачу при возникнове- нии этого события. Заполните пустой обработчик событий кодом, представленным в строках 118—129. В стро- ке 124 активизируется метод Square для определения квадратного корня указанного целого числа. В листинге 4.1 метод calculateButton_Click (строки 115—131) является методом обработчика события Click метода calculateButton (т. е. события, которое имеет место при щелчке пользователем кнопкой мыши). Дан- ный обработчик событий начинается с присвоения пустой строки ("") свойству outputLabel объекта Text в строке 118. Такое присвоение подразумевает, что выходные данные не выходят за пределы формы, если поль- зователь нажимает кнопку Calculate Squares более одного раза. В строках 121—129 многократно активизиру- ется метод square (строки 109—113) для вычисления квадратных корней целых чисел от 1 до 10. В строке 124 активизируется метод square и передает переменную counter в качестве аргумента. Результат вычисления каж- дого квадратного корня сохраняется в переменной result. Строки 127 и 128 соединяют каждый результат со свойством outputLabel объекта Text. Это свойство сохраняет текст, который будет отображен в этой метке. В конце цикла Label содержит результаты вычисления квадратного корня целых чисел от 1 до 10. Обратите внимание, доступ к свойству осуществляется с помощью оператора "точка" (.). Рассмотрим более внимательно активизацию метода square в строке 124. Круглые скобки о после метода Square обозначают оператор вызова метода, имеющий высокий уровень приоритета. На данном этапе про- грамма создает копию значения counter (аргумент вызова метода), и программное управление переходит к ме- тоду Square (определен в строках 109—113). Метод Square получает копию значения counter в параметре у. Затем методом Square вычисляет у*у (строка 111). Метод Square использует оператор return для возврата ре- зультата вычислений в оператор, который активизировал Square (расположен в строке 124). Затем строка 124 присваивает возвращенное значение переменной result. Строки 127 и 128 соединяют "The square of ", значе- ние counter, "is", значение result и символ новой строки в конце свойства outputLabel объекта Text. Цикл for повторяет этот процесс 10 раз. Определение метода Square (строка 109) показывает (в скобках), что Square ожидает целочисленный пара- метр у. Параметр у— это переменная со значением, передаваемым square в качестве аргумента. Имя параметра обеспечивает доступ к значению аргумента с тем, чтобы код в теле метода мог использовать это значение. Клю- чевое слово int, предшествующее имени метода, указывает, что метод Square возвращает целочисленный ре- зультат. Оператор return в Square (строка 111) передает результат вычисления у*у назад в вызывающий опера- тор (строка 124). Обратите внимание, что все определение метода появляется в фигурных скобках класса Squarelnt. Все методы должны определяться в рамках определения класса.
100 Глава 4 О хорошем стиле программирования___________________________________________________ Введите пустую строку между смежными определениями метода для разделения этих методов и повышения удобочитаемости программы. Распространенная ошибка программирования______________________________________________ Определение метода за пределами фигурных скобок определения класса является синтаксической ошибкой Формат определения метода следующий: тип_результата имя_метода (список_параметров) { объявления и операторы } Первая строка иногда называется заголовком метода, имя метода — любой действующий идентификатор. тип_результата— тип данных результата, которые данный метод возвращает вызывающему методу. Тип ре- зультата void указывает на то, что метод не возвращает значение. Методы могут возвратить максимум одно значение. Распространенная ошибка программирования______________________________________________ Пропуск типа результата в определении метода является синтаксической ошибкой. Если метод не возвращает значение, тогда тип результата метода должен быть задан как void. Распространенная ошибка программирования______________________________________________ Если забыть возвратить значение от метода, который, предположительно, должен возвратить значение, то это будет синтаксической ошибкой. Если указано иной тип результата, нежели void, тогда метод должен содержать оператор return, который возвращает значение. Распространенная ошибка программирования______________________________________________ Возвращение значения от метода, тип результата которого был объявлен как void, является синтаксической ошибкой. список-парамечров — это разделенный запятыми список, в котором метод объявляет тип и имя каждого пара- метра. Вызов метода должен указывать один аргумент для каждого параметра в определении метода, и аргумен- ты должны появляться в том же порядке, что и параметры в определении метода. Каждый аргумент также дол- жен быть совместим со своим соответствующим типом параметра. Например, параметр типа double мог бы по- лучить значения 7.35, 22 или - .03546, но не значение "hello", потому что string нельзя неявно преобразовать в double. Если метод не получает никаких значений, тогда список параметров остается пустым (т. е. за именем метода следует пара пустых круглых скобок). Каждый параметр в списке параметров метода должен содержать тип данных; в противном случае имеет место синтаксическая ошибка. Распространенная ошибка программирования______________________________________________ Использование одного типа объявления для нескольких параметров метода одного типа, как в float х, у, вме- сто float х, float у является синтаксической ошибкой, потому что каждый параметр в списке требует указа- ния отдельного типа. Распространенная ошибка программирования______________________________________________ Ввод точки с запятой после правой скобки, закрывающей список параметров определения метода, является син- таксической ошибкой. Распространенная ошибка программирования______________________________________________ Переопределение параметра метода в теле метода является ошибкой компилирования. Распространенная ошибка программирования______________________________________________ Передача в метод аргумента, не совместимого с соответствующим типом параметра, является синтаксической ошибкой Если аргумент имеет иной тип, что и соответствующий ему параметр, тогда тип аргумента может быть неявно преобразован в тип соответствующего параметра. Объявления и операторы в фигурных скобках образуют тело метода. Тело метода также называется блоком. Как упоминалось ранее, блок — это набор операторов, заключенных в фигурные скобки. Переменные могут быть объявлены в любом блоке, а блоки могут быть вложенными.
Методы и массивы 101 Распространенная ошибка программирования______________________________________________ Определение метода в рамках другого метода является синтаксической ошибкой (т. е. методы не могут быть вложенными). Замечание по технологии программирования______________________________________________ Количество, тип и порядок аргументов в вызове метода должны в точности совпадать с количеством, типом и порядком аргументов в соответствующем заголовке метода. Существуют три способа передачи управления в точку, в которой был активизирован метод. Если метод не воз- вращает результат (т. е. метод имеет тип результата void), управление передается, когда программа достигает правой фигурной скобки, закрывающей метод, либо когда оператор return; выполняется. Если метод возвращает результат, тогда оператор return выражение; возвращает вызывающему методу значение выражения. Когда выполняется оператор return, управление немед- ленно возвращается в точку, в которой был активизирован метод. Обратите внимание на синтаксис, активизирующий метод square в листинге 4.1: используется имя метода, за которым в скобках следуют аргументы к методу. Методы в определении класса позволяют активизировать с помощью этого синтаксиса все прочие методы в том же определении класса (исключение из этого правила рас- сматривается в главе 5). Мы видели три способа вызова метода с аргументами: с помощью самого имени метода (например, Square (х)), с помощью ссылки на объект, за которой следует оператор "точка" (.), и имени метода (например, stringl.CompareTo(string2)) и с помощью имени класса, за которым следует имя метода (напри- мер, Math.Sqrt (9.0), где нужно вызвать метод Sqrt класса Math, расположенного в пространстве имен System). Данный последний синтаксис предназначен для вызова методов static-класса; подробно эти методы рассмат- риваются в главе 5. 4.4. Приведение типов аргументов Другой важной особенностью определений методов является приведение типов аргументов (т. е. преобразова- ние имеющегося типа аргумента в нужный тип). Обычно этот процесс называется неявным преобразованием (вкратце описано в главе 3), потому что копия значения переменнбй преобразуется в другой тип без явного при- ведения. Явное преобразование (также представленное в главе 3) имеет место, когда явное приведение указыва- ет, что должно иметь место преобразование. Такие преобразования можно провести с классом Convert в про- странстве имен System. C# поддерживает как расширяющиеся, так и сужающиеся преобразования. Расширяю- щееся преобразование имеет место, когда один тип преобразуется в другой тип (обычно в тип, который может удерживать большее количество данных) без потери данных, а сужающееся преобразование имеет место, когда данные в результате преобразования могут быть утеряны (обычно в типы, содержащие меньший объем данных). В табл. 4.1 представлена информация о размерах различных встроенных типов С#, а в табл. 4.2 показаны допус- тимые неявные преобразования. Таблица 4.1. 7j нпы данных, встроенные в C# Тип Размер в битах Значения Стандарт bool 8 true или false char 16 ’\u0000* ДО '\uFFFF' Набор символов Unicode byte 8 От 0 до 255 Без знака sbyte 8 От-128 до+127 short 16 От-32 768 до+32,767 ushort 16 От 0 до 65 535 Без знака int 32 От -2 147 483 648 до +2 147 483 647 uint 32 От 0 до 4 294.967,295 Без знака long 64 От -9 223 372 036 854 775 808 ДО +9 223 372 036 854 775 807 ulong 64 От 0 ДО 18 446 744 073 709 551 615 Без знака
102 Глава 4 Таблица 4.1 (окончание) Тип Размер в битах Значения Стандарт decimal 128 От 1,0 х 10-28 до 7,9 х 1028 float 32 От ±1,5 х 1СГ45 до ±3,4 х 1038 Плавающая точка IEEE 754 double 64 От ±5,0 х 1СГ324 до ±1,7 х 1О308 ’ Плавающая точка IEEE 754 object string Набор символов Unicode Таблица 4.2. Допустимые неявные преобразования Тип Может быть преобразован в тип bool object byte decimal, double, float, int, uint, long, ulong, short, ushort или object sbyte decimal, double, float, int, uint, long, short или object char decimal, double, float, int, uint, long, ulong, ushort или object decimal object double object float double или object int decimal, double, float, long или object uint decimal, double, float, long, ulong или object long decimal, double, float или object ulong decimal, double, float или object short decimal, double, float, int, long или object ushort decimal, double, float, int, uint, long, ulong или object Например, метод Sqrt класса Math можно вызвать с целочисленным аргументом, несмотря на то, что метод оп- ределен в классе Math для получения аргумента double. Оператор Console.WriteLine(Math.Sqrt(4)); корректно оценивает Math.Sqrt (4) и отображает значение 2. C# неявно преобразует значение int 4 в значение double 4.0 до передачи значения в Math.Sqrt. Во многих случаях C# применяет неявные преобразования к зна- чениям аргументов, точно не соответствующим типам параметров в определении метода. Впрочем, в некоторых случаях попытки выполнить такие преобразования приводят к ошибкам компилирования, потому что преобра- зование нарушает правила, которыми пользуется C# при определении, когда может возникнуть расширяющееся преобразование. В предыдущем примере Math.Sqrt C# преобразует int в double без изменения значения аргу- мента. Однако преобразование double в int урезает дробную часть (мантиссу) значения double. Результатом преобразования больших целых типов в меньшие типы (например, long в int) могут стать измененные значе- ния. Такие сужающиеся преобразования могут терять данные; C# не допускает сужающиеся преобразования без операции явного приведения. Правила преобразования C# применяются к выражениям, содержащим значения двух или более типов данных (которые называются выражениями смешанного типа), а также к значениям примитивного типа данных, пере- даваемого в методы как аргументы. C# преобразует тип каждого значения в выражение смешанного типа до "самого высокого" типа в выражении. Для этого C# создает временную копию каждого преобразованного зна- чения и использует ее в выражении; значения-оригиналы остаются неизменными. Тип аргумента для метода может быть преобразован до любого "высшего” типа. Табл. 4.2 можно использовать для определения самого высокого типа в выражении. Для каждого типа в левом столбце соответствующие типы в правом столбце могут рассматриваться как высший тип. Например, типы decimal, double, float, long и object выше, чем тип int. Результатом преобразования значений к низшим типам может стать потеря данных. В случаях, когда в резуль- тате преобразования могут быть утеряны данные, программа-компилятор требует от программиста использова-
Методы и массивы 103 ния приведения для того, чтобы имело место преобразование. Например, для передачи методу square (см. лис- тинг 4.1) double-переменной у, метод может быть записан так: int result = Square((int) у); Данный оператор явно приводит (преобразует) копию значения у к целому числу для использования в методе Square. Таким образом, если значение у равно 4.5, тогда метод Square возвращает 16, а не 20.25. Распространенная ошибка программирования_______________________________________________ При выполнении сужающего преобразования (например, double в int) результатом преобразования значения простого типа данных в другой простой тип данных может стать утеря данных. 4.5. Пространства имен C# Как уже отмечалось, C# содержит много предварительно определенных классов, сгруппированных в простран- ства имен. Данное собрание предварительно созданных кодов образно называется библиотекой классов FCL. Фактический код для классов имеется в файлах с расширением dll, называемых компоновочными блоками. В C# директивы using задают пространства имен, используемые в каждой программе. Например, программа включает оператор using System; для сообщения программе-компилятору о том, что она использует пространство имен System. Данный оператор using позволяет писать в программе Console. WriteLine, а не System. Console. Writ eLine. Для использования класса в конкретном пространстве имен необходимо добавить ссылку в соответствующий компоновочный блок (процедура, продемонстрированная в разд. 2 7). Ссылки компоновочного блока для пространства имен System добавляются через Visual Studio .NET, остальные компоновочные блоки добавляются открыто. В книге используется большое количество FCL-классов. В табл. 4 3 показано подмножество многих про- странств имен в FCL с кратким описанием каждого. В книге используются классы из приведенных и других пространств имен. Таблица 4.3. Пространства имен в библиотеке классов FCL Пространство имен Описание System Основные классы и типы данных (такие, как int, double и char). На System неявно ссы- лаются все программы C# System.Data Классы, образующие ADO.NET, используемые для доступа к базам данных и манипуля- ции System.Drawing Классы для рисования и графики System.IO Классы для ввода и вывода данных, например с файлами Systern.Threading Классы для многопоточного режима, используемого для указания того, какие множест- венные операции могут выполняться параллельно System.Windows.Forms Классы, используемые для создания графических пользовательских интерфейсов System. Xml Классы для обработки данных XML Набор пространств имен в FCL довольно большой (на время публикации книги он насчитывал порядка 100 про- странств имен, включая сотни классов). Помимо пространств имен, представленных в табл. 4.3, в FCL входят пространства имен для сложной графики, усовершенствованных графических пользовательских интерфейсов, печати, усовершенствованной организации сетей, защиты, мультимедиа и доступности (для пользователей с ограниченными техническими возможностями). Обзор пространств имен в FCL см. в указателе "Class Library" в справочной системе. 4.6. Типы значений и ссылочные типы В следующем разделе будет рассмотрена передача аргументов методам по значению и по ссылке. Для понима- ния этих процессов для начала необходимо разграничить типы данных — значения, и типы данных — ссылки. Переменная типа "значение" содержит данные этого типа. Переменная же типа "ссылка" содержит адрес место- положения в памяти, где хранятся данные для этой переменной. Типы значения обычно представляют отдель-
104 Глава 4 ные части данных, например, значения int или bool. С другой стороны, типы ссылки относятся к объектам, ко- торые могут содержать множество отдельных частей данных. Подробно объекты описаны в главах 5—7. Язык C# включает встроенные типы значений и ссылок. Встроенные типы значения являются целочисленными типами (sbyte, byte, char, short, ushort, int, uint, long и ulong), типами с плавающей точкой (float и double) и типами decimal и bool. Встроенные типы ссылок — это string и object. Программисты могут созда- вать новые типы значений и ссылок; такие типы ссылок включают в себя классы (глава 5), интерфейсы (глава 7) и делегаты (глава 7). В табл. 4.1 перечислены простые типы данных; эти данные часто используются как компоновочные блоки'для более сложных типов. Подобно своим предшественникам, — языкам С и C++, C# требует, чтобы каждая пере- менная обладала своим типом до ее применения в программе. По этой причине C# еще называется сильно типи- зированным языком. В программах С и C++ разработчикам часто приходится писать отдельные версии программ для поддержки различных компьютерных платформ, потому что простые типы данных не гарантированно идентичны для каж- дого компьютера. Например, значение int на одном компьютере может занимать 16 битов (2 байта) памяти, тогда как значение int на другом компьютере может занимать 32 бита (4 байта) памяти. В C# значения int все- гда занимают 32 бита (4 байта). \ Совет по повышению переносимости______________________________________________________ Простые типы данных в C# переносимы на все платформы, поддерживающие С#. Каждый тип данных представлен в табл. 4.1 с указанием размера в битах и диапазона значений. Разработчики C# хотели, чтобы код был портативным (переносимым), поэтому было решено использовать международно- признанные стандарты как для форматов символов (Unicode), так и для чисел с плавающей точкой (IEEE 754) Более подробно кодировка Unicode описана в приложении 7. 4.7. Передача аргументов по значению и по ссылке Во многих языках программирования двумя способами передачи аргументов методам является передача по зна- чению и передача по ссылке. Когда аргумент передается по значению, то вызываемый метод получает копию значения этого аргумента. Совет по тестированию и отладке_______________________________________________________ При использовании передачи по значению изменение копии значения вызванного метода не влияет на значение первоначальной переменной. Данное условие предотвращает появление некоторых возможных побочных эф- фектов, задерживающих разработку корректных и устойчивых систем программного обеспечения. Когда аргумент передается по ссылке, то вызывающий дает методу возможность непосредственного доступа и изменения оригинальных данных. Передача по ссылке может повысить производительность, потому что она устраняет издержки копирования крупных элементов данных, например, объектов; впрочем, передача по ссылке может ослабить защиту, потому что вызванный метод способен изменить (модифицировать) данные вызываю- щего метода. Замечание по технологии программирования______________________________________________ При возвращении информации от метода посредством оператора return переменные типа "значение" всегда возвращаются по значению (т. е. возвращается .копия), а переменные типа “ссылка" всегда возвращаются по ссылке (т. е. возвращается ссылка на объект). Для передачи методу ссылки на объект просто укажите в вызове метода имя ссылки. Затем в теле метода вос- пользуйтесь именем параметра для ссылки на объект. Это имя относится к объекту-оригиналу в памяти, что по- зволяет вызванному методу осуществлять непосредственный доступ к объекту-оригиналу в вызывающем опера- торе. В разд. 4.6 рассматривалось различие между типами значений и типами ссылок. На этом этапе читатели могут понять одно из основных различий между этими двумя типами данных. Переменные типа "значение" передают- ся методам по значению, а переменные типа "ссылка" передаются методам по ссылке. А что будет, если про- граммист захочет передать значение по ссылке7 Для активизации такого способа программирования C# предос- тавляет ключевые слова ref и out. Ключевое слово ref указывает на то, что аргумент-значение должен переда- ваться по ссылке, что позволяет вызванному методу изменять оригинальную переменную. Данное ключевое слово используется для уже инициализированных переменных. Ключевое слово out указывает выходной пара- метр, являющийся аргументом, которому вызванный метод будет присваивать значение. Как правило, когда метод получает не инициализированное значение, компилятор генерирует ошибку. Ввод ключевого слова out
Методы и массивы 105 перед параметром указывает на то, что вызванный метод будет инициализировать переменную и не даст про- грамме-компилятору сгенерировать сообщение об ошибке для не инициализированной переменной. В листин- ге 4.2 показано использование ключевых слов ref и out для манипуляций целочисленными переменными1. I- Листинг 4.2. Демонстрация ключевых слов ref и out ' _ ...... --------------------------------1.-------------------- 1 // Листинг 4.2: RefOutTest.cs 2 // Демонстрация ключевых слов ref и out 3 4 using System; 5 using System.Drawing; 6 using System.Collections; 7 using System.Component.Model; 8 using System.Windows.Forms; 9 using System.Data; 10 11 public class RefOutTest : System.Windows.Forms.Form 12 { 13 private System.Windows.Forms.Button showOutputButton; 14 private System.Windows.Forms.Label outputLabel; 15 16 // код, сгенерированный Visual Studio .NET 17 18 // главная входная точка для приложения 19 [STAThread] 20 static void Main() 21 { 22 Application.Run (new RefOutTest ()); 23 } 24 25 // x передается по значению, и метод не может модифицировать 26 // значение оригинальной переменной 27 void Square(int х) 28 { 29 х = х * х; 30 31 outputLabel.Text + = 32 "Value of variable within Square, " + 33 "after calculation: " + x + "\n"; 34 } ' 35 36 // x передается по ссылке, и метод модифицирует 37 // значение оригинальной переменной 38 void SquareRef(ref int x) 39 { 40 X = x * x; 41 42 outputLabel.Text + = 43 "Value of variable within SquareRef, " + 44 "after calculation: " + x + "\n"; 45 } 46 47 // x передается как параметр out, и метод инициализирует 48 //и модифицирует значение оригинальной переменной 49 void SquareOut(out int x) 50 { 51 x= 6; // инициализация 52 x = x * x; // модификация 53 1 В разд. 4.14 рассматривается передача аргументов-ссылок как по значению, так и по ссылке.
106 Гпава 4 54 outputLabel.Text += 55 "Value of variable within SquareOut, " + 56 "after calculation: " + x + "\n"; 57 } 58 59 private void showOutputButtonClick ( 60 object sender, System.EventArgs e) 61 { 62 int у = 5; // создать новый int у и инициализировать 5 63 int z; // объявление z, но не инициализация 64 65 // отображение оригинального значения у 66 outputLabel.Text = 67 "Value of у before calling SquareRef: " + у + "\n"; 68 69 // использование ключевого слова ref для передачи у по ссылке 70 SquareRef (ref у); 71 outputLabel.Text + = 72 "Value of У after exiting SquareRef: " + у " "\n"; 73 74 // отображение оригинального значения z 75 outputLabel.Text += 76 "\nValue of z before calling SquareOut: " + 77 "uninitialized\n"; 78 79 // переменная z еще не инициализирована; необходимо использовать 80 // ключевое слово out для передачи у по ссылке 81 SquareOut (out z); 82 outputLabel.Text += 83 "Value of z after existing SquareOut: " + z + ”\n"; 84 85 outputLabel.Text += 86 ",\n\n"; 87 88 // отображение значения у 89 outputLabel.Text += 90 "Value of у before calling Square: " + у + "\n"; 91 92 // передача у по значению 93 Square (у) ; 94 outputLabel.Text += 95 "Value of у after exiting Square: " + у + "\n"; 96 97 /f отображение значения z 98 outputLabel.Text += 99 "\nValue of z before calling Square: " + z + "\n"; 100 101 // передача z по значению 102 Square (z); 103 outputLabel.text += 104 "Value of z after exiting Square: " + у + ”\n"; 105 106 } // конец метода showOutputButton_Click 107 108 } // конец класса RefOutTest Результат работы программы представлен на рис. 4.2. Данная программа содержит три метода для вычисления квадратного корня целого числа. Метод Square, опре- деленный в строках 27—34, умножает аргумент на аргумент и присваивает новое полученное значение х. Пере- данное первоначальное значение — это целое число-значение и, следовательно, не изменяется после выполне- ния метода. Метод SquareRef, определенный в строках 38—45, выполняет ту же самую операцию. Метод SquareRef получает аргумент как ref int, указывая на то, что х — целое число, которое передается методу по
Методы и массивы 107 ссылке. В результате этого, присвоение в строке 40 изменяет значение первоначального аргумента в вызываю- щем элементе, а не копию этого значения. Метод SquareOut, определенный в строках 49—57, также рассчиты- вает квадратный корень целого числа, но инициализирует х до 6 в строке 51. Метод SquareOut принимает аргу- мент как out int, указывая на то, что х — целое число, которое должно быть передано по ссылке и может быть передано методу в не инициализированном состоянии. Рис. 4.2. Демонстрация использования ключевых слов ref и out Метод showOutputButton Click (строки 59—106)— обработчик событий, активизирующий методы SquareRef, SquareOut и Square, когда пользователь нажимает кнопку Show Output. Этот метод начинается инициализацией у до 5 и объявлением (но не инициализацией) z. Строки 70 и 81 вызывают методы SquareRef и SquareOut. Обра- тите внимание на синтаксис, используемый для передачи у и z; в каждом случае аргументу предшествует либо ref, либо out, как указано в заголовке метода. Если нужное ключевое слово пропускается, тогда имеет место синтаксическая ошибка. Обратите также внимание на то, что у изменено на 25, a z установлено на 36. В строках 93 и 102 вызывается метод square, передающий значения у (25) и z (36). Аргументы у и 2 передаются по значе- нию, т. е. методу передаются только копии их значений. В результате этого, значения у и z после вызова метода остаются 25 и 36. В результате отображаются значения у и z до, во время и после вызова метода. Распространенная ошибка программирования_________________________________________________ Ключевые слова ref и out в вызове метода должны соответствовать аргументам, указанным в определении ме- тода; в противном случае имеет место синтаксическая ошибка. Замечание по технологии программирования___________________________________________ По умолчанию C# не позволяет программисту выбирать то, как передавать каждый аргумент: по значению или по ссылке Переменные-значения передаются по значению Сами объекты методам не передаются; вместо этого методам передаются ссылки на объекты. Сами ссылки передаются по значению. Когда метод получает ссылку на объект, тогда метод может манипулировать объектом напрямую, однако значение ссылки изменить нельзя (например, для обращения к новому объекту). 4.8. Правила области действия Область действия (иногда называется пространством объявления) идентификатора переменной, ссылки или метода — это часть программы, в которой осуществляется доступ к идентификатору. Локальную переменную, объявленную в блоке, можно использовать только в этом блоке, либо во вложенных в него блоках. Возможные области действия для идентификатора определяются как область действия класса и область действия блока. Члены класса имеют область действия класса и видимы в том, что называете^ пространством объявления клас- са. Область действия класса начинается с открывающейся левой фигурной скобки ({) определения класса и за- канчивается закрывающейся правой фигурной скобкой (}). Область действия класса обеспечивает доступ мето- дов этого класса ко всем членам, определенным в этом классе. В главе 5 читатели отметят, что статичные ч^ены являются исключением из этого правила. В некотором смысле все переменные экземпляры и методы класса являются глобальными по отношению к методам класса, в котором они определены (т. е. методы могут моди- фицировать переменные экземпляры напрямую и вызывать другие методы класса). Объявленные в рамках блока идентификаторы имеют область действия блока (пространство объявления ло- кальной переменной). Область действия блока начинается с объявления идентификатора и заканчивается закры-
108 Гпава 4 вающейся правой фигурной скобкой (}). Локальные переменные метода имеют область действия блока, так же, как и параметры метода, являющиеся локальными переменными метода. Любой блок может содержать объяв- ления переменных. Когда блоки вкладываются в тело метода, и переменная, объявленная во внешнем блоке, имеет то же имя, что и переменная, объявленная во вложенном блоке, тогда генерируется сообщение об ошиб- ке. С другой стороны, если локальная переменная в методе имеет то же имя, что и переменная экземпляра, тогда значение в вызывающем методе (основной программе) "скрыто" до тех пор, пока метод не прекратит выполне- ние. В главе 5 рассматриваются способы доступа к таким "скрытым" переменным экземпляра. Читателю следу- ет отметить, что область действия блока также применяется к методам и операторам for. В циклах for любая переменная, объявленная в части инициализации заголовка for, будет находиться в области действия только в рамках данного цикла for. Совет по тестированию и отладке____________________________________________________________ Избегайте использования имен локальных переменных, скрывающих имена переменных экземпляров Программа в листинге 4.3 демонстрирует различные сценарии области действия с переменными экземпляра и локальными переменными. Переменная экземпляра х (строка 16) инициализирована 1. Эта переменная экземп- ляра скрыта в любом блоке (или методе), объявляющем локальную переменную с именем х. Обработчик собы- тий showOutputButton Click (строки 48—64) объявляет локальную переменную х и инициализирует ее 5 (стро- ка 51). Строки 53 и 54 отображают значение данной локальной переменной, показывая, что эта переменная эк- земпляра х (со значением 1) "скрыта" в методе showOutputButton Click. Программа определяет два других метода — MethodA и MethodB, не имеющих аргументов и ничего не возвра- щающих. Программа дважды вызывает каждый метод из метода scoping. MethodA определяет локальную пере- менную х (строка 22) и инициализирует ее 25. Каждый вызов MethodA отображает значение переменной в outputLabel, увеличивает эту переменную на единицу и отображает заново до выхода из метода. Каждое обра- щение к MethodA восстанавливает локальную переменную х и инициализирует ее 25. Метод MethodB не объявля- ет никаких переменных. Следовательно, когда он обращается к переменной х, используется переменная экземп- ляра х. Каждое обращение к MethodB отображает переменную экземпляра в outputLabel, умножает ее на 10 (строка 4) и отображает заново до выхода из метода. Таким образом, при следующем обращении к методу MethodB первоначальное значение переменной экземпляра является ее измененным значением — 10. После об- ращений к MethodA и MethodB программа еще раз отображает локальную переменную х в методе showOutputButton_ciick, чтобы показать, что ни одно из обращений к методу не изменило именно эту перемен- ную х, поскольку методы относятся к переменным в других областях действия. ригягжвюнтгж ниигдаар * * ....—..-—-----------------...... : Листинг 4.3. Область- действия 4 ’ " < . . * . 'J ......„г..:.;...;......г............;...;.:.....;.....i...........;._...............;.._J 1 // Листинг 4.3: Scoping.cs 2 // Демонстрация области действия локальных переменных 2а //и переменных экземпляра 3 4 using System; 5 using System.Drawing; 6 using System.Collections; 7 using System.Component.Model; 8 using System.Windows.Forms; 9 using System.Data; 10 11 public class Scoping : System.Windows.Forms.Form 12 { 13 private System.Windows.Forms.Label outputLabel; 14 private System.Windows.Forms.Button showOutputButton; 15 16 public int x = 1; // переменная экземпляра 17 18 // код, сгенерированный Visual Studio .NET 19 20 public void MethodA() 21 { 22 int x = 25; // инициализация всякий раз при обращении к MethodA 23 24 outputLabel.Text = outputLabel.Text + 25 "\n\nlocal х in MethodA is " + x + 26 "after entering MethodA"; 27
Методы и массивы 109 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 5b 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 ++х // увеличение локальной переменной х outputLabel.Text = outputLabel.Text + ”\nlocal x in MethodA is " + x + "before exiting MethodA"; } public void MethodB () { outputLabel.Text = outputLabel.Text + "\n\ninstance variable x is " + x + "on entering MethodB"; x ♦ = 10; outputLabel.Text = outputLabel.Text + "\ninstance variable x is " + x + " on exiting MethodB"; Рис. 4.3. Демонстрация области действия переменных экземпляра и локальных переменных private void showOutputButton_Click( object sender, System.EvenArgs e) f int x = 5; // локальная x в методе showOutputButton_Click outputLabel.Text = "local x in method showOutputButton_Click is " + x; MethodAO; // MethodA имеет локальную переменную x MethodB(); // MethodB использует переменную экземпляра х MethodAO; // MethodA создает новую локальную переменную х MethodBО; // переменная экземпляра х сохраняет свое значение outputLabel.Text = outputLabel.Text + "\n\n" + "local x in method showOutputButton_Click is " + x; } // конец метода showOutputButton_Click // главная входная точка для приложения [STAThread] static void MarnO { Application.Run(new Scoping()) ; } } // конец класса Scoping Результат работы программы представлен на рис. 4.3. 4.9. Рекурсия До сих пор рассматриваемые программы были структурированы как методы, обращающиеся друг к другу по иерархии. Из-за определенных проблем полезно, чтобы метод фактически обращался к самому себе. Рекурсив- ный метод — это метод, обращающийся сам к себе прямо или косвенно через другой метод. В данном разделе рекурсия сначала рассматривается концептуально, после чего будет представлена программа, содержащая про- стой рекурсивный метод. Принципы решения проблемы рекурсивности имеют определенные общие черты. Рекурсивный метод фактиче- ( ски "знает" решения простейших или базовых случаев. Если метод вызывается с базовым случаем, тогда этот метод возвращает результат. Если метод вызывается для решения более сложной задачи, тогда он разбивает эту задачу на две концептуальные части: одну данный метод может решить (базовый случай), а другую — не может.
110 Глава 4 Для обеспечения целесообразности рекурсивности вторая часть может напоминать первоначальную задачу, но быть более простой или более компактной ее версией. Метод активизирует (вызывает) свежую копию самого себя для работы над более мелкой задачей; это событие называется рекурсивным обращением или рекурсивным шагом. Рекурсивный шаг обычно содержит ключевое слово return, потому что его результат будет объединен с частью задачи, решение которой данный метод "знает". Такая комбинация образует результат, который будет передан назад вызывающему оператору. Рекурсивный шаг выполняется в то время, как первоначальное обращение к методу остается "открытым" (т. е. выполнение еще не закончено). Результатом рекурсивного шага может быть образование большего количества рекурсивных шагов, по мере того, как метод будет делить каждую вновь возникающую подзадачу на две кон- цептуальные части. Всякий раз, когда метод вызывает самого себя для решения немного более простой версии первоначальной задачи, последовательность все меньших и меньших задач должна сходиться в базовом случае так, что рекурсия со временем может прекратиться. На этом этапе метод распознает базовый случай и возвра- щает результат в предыдущую копию метода. Последовательность возвращений образует линию до тех пор, пока первоначальное обращение к методу не возвратит вызывающему оператору окончательный результат. Для демонстрации этих концепций на примере напишем рекурсивную программу для выполнения популярного ма- тематического расчета. Факториалом неотрицательного целого числа п (записывается л!, произносится: "факториал л") является про- изведение п х (л - 1) х (п - 2) х ... х 1, где 1! равно 1, а 0! определяется как 1. Например, 5! является произведением 5х4хЗх2х 1, что равно 120. Факториал целого number, большего или равного о, можно рассчитать итеративно (не рекурсивно) с помощью оператора for следующим образом: factorial = 1; for (int counter = number; counter >= 1; counter—) factorial *= counter; Мы подошли к рекурсивному определению факториального метода со следующим отношением: л! = л х (л - 1)! Например, 5! равно 5 х 4!, как показывают следующие вычисления: 51 = 5x4x3x2x1 5! = 5 х (4 х 3 х 2 х 1) 5! = 5 х (4!) В листинге 4.4 используется рекурсия для расчета и печати факториалов целых чисел от 0 до 10. Рекурсивный метод Factorial (строки 17—24) сначала определяет, имеет ли прерывающее условие значение true (т. е. меньше значения number или равно 1). Если number меньше или равно 1, тогда factorial возвращает 1, больше рекурсии не требуется и метод передает управление вызвавшему оператору. Если number больше 1, тогда строка 23 описывает эту задачу как произведение number и рекурсивного обращения к Factorial с оценкой факториала number-1. Обратите внимание, что Factorial (number-1) — более простая задача для решения, нежели первона- чальное вычисление Factorial (number) . Метод Factorial получает параметр типа long и возвращает результат типа long. Как видно в листинге 4.4, зна- чения факториала быстро становятся большими. Выбираем тип данных long для того, чтобы программа могла рассчитать факториалы больше 201. К сожалению, метод Factorial создает большие значения настолько быст- ро, что даже long не помогает напечатать больше факториалов до превышения размера даже переменной long. Расчет факториалов больших чисел требует от программы использования переменных float и double. Это ус- ловие указывает на слабость большинства языков программирования, а именно на то, что языки не так просто поддаются расширяемости для обработки уникальных требований различных программных приложений. В гла- ве 5 при рассмотрении объектно-ориентированного программирования будет видно, что C# — это расширяемый язык; программисты с уникальными требованиями могут расширить язык новыми типами данных (называемых классами). Разработчик, например, может создать класс Hugeinteger, который позволит программе рассчиты- вать факториалы больших чисел. Распространенная ошибка программирования_____________________________________________________ Результатом отсутствия возвращения значения от рекурсивного метода могут стать синтаксические или логиче- ские ошибки.
Методы и массивы 111 Г™*™!!*?™................... „----------------------------....... . Листинг 4.4. Расчет факториалов с помощью рекурсивного метода 1 2 3 4 5 б 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 // Листинг 4.4: FactorialTest.cs // Расчет факториалов с помощью рекурсии using System; using System.Drawing; using System.Collections; using System.Component.Model; using System.Windows.Forms; using System.Data; public class FactorialTest : System.Windows.Forms.Form { private System.Windows.Forms.Button showFactorialsButton; private System.Windows.Forms.Label outputLabel; // код, сгенерированный Visual Studio .NET public long Factorial (long number) { if (number <= 1) // базовый случай return 1; else return number * Factorial (number - 1); } // главная входная точка для приложения [STAThread] static void Main() { Application.Run (new FactiorialTest()); } private void showFactorialsButton_Click (object sender, System.EventArgs e) { outputLabel. text = for (long i = 0; i <= 10; i++) outputLabel.Text += i + != ": + Factorial (I) + "\n"; } } // конец класса FactorialTest Рис. 4.4. Демонстрация вычисления факториала с помощью рекурсивного метода Результат работы программы представлен на рис. 4.4. Распространенная ошибка программирования_____________________________________________ Если опустить базовый случай или написать рекурсивный шаг так, что он не будет сходиться в базовом случае, то это приведет к бесконечной рекурсии, что в скором времени займет весь ресурс памяти. Бесконечная рекур- сия аналогична проблеме бесконечного цикла в итеративном (не рекурсивном) решении. В большинстве книг по программированию рекурсия представлена гораздо позже, чем в данной книге. Авторы полагают, что рекурсия — это достаточно мощная и сложная тема, поэтому лучше ее представить раньше с приведением примеров по всему тексту. 4.10. Перегрузка методов C# предоставляет несколько методов с одним именем для определения в одном классе до тех пор, пока эти ме- тоды имеют разные наборы параметров (в том, что касается числа параметров, типов параметров или порядка параметров). Данная концепция называется перегрузкой методов (method overloading). При обращении к пере-
112 Глава 4 груженному методу компилятор C# выбирает нужный метод просмотром числа, типов и порядка аргументов вызова Обычно перегрузка методов используется для создания нескольких методов с одним именем, выпол- няющих сходные задачи, но с разными типами данных. В листинге 4.5 показано применение перегруженного метода Square для расчета квадратных корней int и double. О хорошем стиле программирования________________________________________________________ Перегруженные методы, выполняющие тесно связанные между собой задачи, могут сделать программу более понятной. Компилятор распознает перегруженные методы по их сигнатурам. Сигнатура метода — это комбинация его имени и параметров. Если во время компиляции программа-компилятор будет обращать внимание только на имена методов, тогда код, показанный в листинге 4.5, будет двусмысленным: компилятор не сможет различить два метода Square. Для определения того, к какому методу следует обратиться, компилятор использует разре- шение перегрузки. Сначала этот процесс осуществляет поиск всех методов, которые могут быть использованы в данном контексте, на основании числа и типа присутствующих аргументов. Может показаться, что в данном случае совпадет только один метод, однако следует помнить, что C# может неявно преобразовывать значения переменных в другие типы данных. Как только все подходящие методы будут найдены, выбирается самое близ- кое совпадение. Это совпадение основано на алгоритме "наилучшего соответствия", анализирующем неявные преобразования, которые будут иметь место. Результат работы программы представлен на рис. 4.5. 1 // Листинг 4.5: Methodoverload.cs 2 // Использование перегруженных методов 3 4 using System; 5 using System.Drawing; 6 using System.Collections; 7 using System.Component.Model; 8 using System.Windows.Forms; 9 using System.Data; . 10 11 public class MethodOverload : System.Windows.Forms.Form 12 { 13 private System.Windows.Forms.Button showOutputButton; 14 private System.Windows.Forms.Label outputLabel; 15 16 // код, сгенерированный Visual Studio .NET 17 18 // первая версия, принимает одно целое число 19 public int Square(int x) 20 { 21 return x * x 22 } 23 24 // вторая версия, принимает одно double-число 25 public double Square(double y) 26 { 27 » return у * у 28 } 29 30 // основная точка входа для приложения 31 [STAThread] 32 static void Main() 33 { 34 Application.Run(new MethoOverload()); 35 } 36 37 private void showOutputButton_Click ( 38 object sender, System.EventArgs e) Рис. 4.5. Использование перегруженных методов
Методы и массивы и 113 39 Z { 40 // обращение к обеим версиям Square 41 outputLabel.Text .= 42 "The square -of integer 7 is " + Square (7) + 43 "\nThe square of double 7.5 is " + Square (7.5); 44 } 45 46- }. // конец класса Methodoverload Рассмотрим пример. В листинге 4.5 программа-компилятор могла использовать логическое имя "Square int" для метода Square, задающего параметр int (строка 19), и "Square double" для метода Square, задающего па- раметр double (строка 25). Если определение метода Foo начинается как void Foo(int a, float b) тогда программа-компилятор могла использовать логическое имя "Foo int и float". Если параметры опреде- лены как void Foo (float a, int b) тогда программа-компилятор могла использовать логическое имя "Foo float и int". Для компилятора важен порядок параметров; программа рассматривает два метода Foo как различные До сих пор логические имена методов, использованные компилятором, не упоминали возвращаемых методами типов. Это происходит потому, что вызовы методов нельзя различить по возвращаемому типу. В программе листинга 4.6 иллюстрируется синтаксическая ошибка, сгенерированная, когда два метода имеют одинаковую сигнатуру, но разные возвращаемые типы. Перегруженные методы с разными списками параметров могут иметь разные возвращаемые типы. Перегруженным методам не обязательно иметь одно и то же количество пара- метров. Распространенная ошибка программирования_____________________________________________________ Создание перегруженных методов с идентичными списками параметров и разными возвращаемыми типами яв- ляется синтаксической ошибкой. ^Листинг 4.6. Синтаксическая ошибка, сгенерированная из перегруженных методов с идентичными списками У Р3ЗНЬ^У ^°?вР^еУыми T*™aM*_____,1__________________________...._______.___j 1 // Листинг 4.6: InvalidMethodOverload.cs 2 // Демонстрация некорректной перегрузки методов 3 4 public class InvalidMethodOverload 5 { 6 public int Square(double x) 7 { 8 return x * x; 9 } 10 11 // ОШИБКА! Второй метод Square принимает то же число, порядок 12 //и типы аргументов, но имеет другой возвращаемый тип 13 public double Square(double у) 14 {' 15 return у * у; 16 } 17 } Результат работы программы представлен на рис. 4.6. Рис. 4.6. Демонстрация синтаксической ошибки, сгенерированной из перегруженных методов 8 Зак. 3333
114 Гпава 4 4.11. Массивы Массивом называется группа ячеек памяти, имеющих одно имя и тип данных. Программа может обратиться к любому элементу массива путем представления имени массива, за которым следует индекс (значение, указы- вающее на особое местоположение в рамках массива) элемента в квадратных скобках ([]). Первый элемент в каждом массиве — нулевой элемент. Индекс в квадратных скобках более формально называется списком индек- сов. Индекс может быть целым числом или целочисленным выражением. Если в качестве индекса программа использует целочисленное выражение, тогда она сначала оценивает выражение для определения индекса. Скобки, в которые заключен индекс массива, являются операторами. Квадратные скобки имеют toj же уровень предшествования, что и круглые. В табл. 4.4 показаны предшествование и ассоциативность операций, описы- ваемых в книге до сих пор. Операции перечислены сверху вниз в нисходящем порядке предшествования, вместе с ассоциативностью и типом. Читатель должен отметить, что операции ++ и — в первой строке обозначают опе- рации постинкремента и постдекремента соответственно, тогда как операции ++ и — во второй строке обозна- чают операции преинкремента и предекремента соответственно. Также обратите внимание, что в первой строке ассоциативность частично смешана. Это происходит потому, что ассоциативность операторов постинкремента и постдекремента реализуется справа налево, а ассоциативность других операций — слева направо. Таблица 4.4. Предшествование и ассоциативность рассмотренных в книге операций Операции Ассоциативность Тип (), П, ++ Слева направо Самый высокий (унарный постфикс) — +-, ! (тип) Справа налево Унарный (унарный префикс) *, /. % Слева направо Мультипликативный +, - Слева направо Аддитивный < <= >, >= Слева направо Реляционный —s i = Слева направо Равенство & Слева направо Логическое "И" (AND) Л Слева направо Логическое исключающее "ИЛИ" (OR) 1 Слева направо Логическое включающее "ИЛИ" (OR) && Слева направо Условное "И" (AND) II Слева направо Условное "ИЛИ" (OR) ? ; Справа налево Условное =, +=. -=, *=, /=. %= Справа налево Присвоение 4.12. Объявление и распределение массивов Массивы занимают объем памяти. Программист задает тип элементов и использует оператор new для динамиче- ского распределения числа элементов, необходимого каждому массиву. Массивы распределяются с new, потому что они являются объектами, а все объекты должны создаваться с помощью new. Впрочем, скоро будет пред- ставлено исключение из этого правила. Объявление int[] с = new int[12]; распределяет 12 элементов для целочисленного массива с. Предшествующий оператор также может быть вы- полнен за два шага следующим образом: // объявление ссыпки массива int[] с; // распределение пространства для массива; // установка ссылки на это пространство с = new int[12]; Когда массивы распределены, элементы инициализируются к нулю для переменных простых типов данных, к false для переменных bool и к null — для ссылочных типов.
Методы и массивы 115 Распространенная ошибка программирования ______________________________________________ В отличие от С или C++, число элементов в массивах C# никогда не указывается в квадратных скобках после имени массива. Например, объявление int с [12]; вызывает синтаксическую ошибку. Убедитесь в том, что ис- пользуется синтаксис int [ ] с = new int [12];. Одно объявление можно использовать для резервирования памяти для нескольких массивов. Следующее объяв- ление резервирует 100 элементов для строкового массива ь и 27 элементов для строкового массива х: string[] b = new string[100], х = new string[27]; Аналогичным же образом следующее объявление резервирует 10 элементов для arrayl и 20 элементов для аггау2 (оба типа double): doublet] arrayl = new double[10], array2 = new double[20]; Для содержания любого типа данных массивы необходимо объявить. В массиве значений каждый элемент мас- сива содержит одно значение объявленного типа. Например, каждый элемент массива int — значение int. В массиве ссылок каждый элемент массива — это ссылка на объект типа данных массива. Например, каждый элемент массива string является ссылкой на string; каждая из ссылок на string по умолчанию имеет значе- ние null. Следующий пример демонстрирует объявление, распределение, инициализацию и манипуляцию элементами массива. В листинге 4.7 созданы три целочисленных массива, состоящих из 10 элементов, отображенных в таб- личном формате. Программа демонстрирует несколько методик для объявления и инициализации массивов. Результат работы программы представлен на рис. 4.7. 1 // Листинг 4.7: InitArray.cs 2 // Разные способы инициализации массивов 3 4 using System; 5 using System.Windows.Forms; 6 7 class InitArray 8 { 9 // основная точка входа для приложения 10 static void Main (string[] args) 11 { 12 string output = 13 14 int[] x; // объявление ссылки на массив 15 х = new int[10]; // динамическое распределение массива 16 //и задание значений по умолчанию 17 18 // список инициализации указывает на число элементов 19 //и значение каждого элемента 20 int[] у = { 32, 27, 64, 18, 95, 14, 90, 70, 60, 37 }; 21 22 const int ARRAY_SIZE = 10; 11 именованная константа 23 int[] z; // ссылка на массив int 24 25 // распределение элементов массива ARRAYJSIZE (т. е. 10) 26 z = new int[ARRAY_SIZE]; 27 28 // задание значений в массиве 29 for (int i = 0; i < z.Length; i++) 30 z[i] = 2 + 2 * i; 31 32 output + = "Subscript\tArray x\ tArray y\ tArray z\n"; 33 34 // выходные значения для каждого массива 35 for (int i = 0; i < ARRAY_SIZE; i++) Рис. 4.7. Разные способы инициализации массивов
116 Гпава 4 36 output + = i + "\t* + x[i] + "\t" + y[i] + 37 ”\t" + z[i] + "\n"; 38 39 MessageBox.Show (output, 40 "Initializing an array of int values", 41 MessageBoxButtons.OK, MessageBoxlcon.Information); 42 43 ) // конец Main 44 45 } // конец класса InitArray В строке 14 x объявляется как ссылка на массив целых чисел. Переменная х имеет тип int[], обозначающий массив, элементы которого имеют тип int. В строке 15 десять элементов массива распределяется с помощью оператора new и присваивается массив для ссылки на х. Каждый элемент этого массива имеет значение по умол- чанию о. В строке 20 создается другой массив int и инициализирует каждый элемент с помощью списка инициализации. В этом случае число элементов в списке инициализации определяет размер массива. Например, в строке 20 соз- дается 10-элементный массив с индексами о, ..., 9 и значениями 32, 27, 64 и т. д. Обратите внимание, что это объявление не требует оператора new для создания объекта массива; программа-компилятор распределяет па- мять для объекта, когда она сталкивается с объявлением массива, включающего в себя список инициализации. В строке 22 создается целочисленная константа array size с помощью ключевого слова const. Эта константа должна быть инициализирована в том же операторе, где и объявлена, и после этого она не может быть измене- на. Если сделана попытка модификации константы после ее объявления, тогда компилятор выдает синтаксиче- скую ошибку. Константы также называются именованными константами. Они часто используются для того, чтобы сделать программу более удобочитаемой, и обычно обозначаются именами переменных прописными буквами. Распространенная ошибка программирования________________________ Присвоение значения const после ее инициализации является синтаксической ошибкой. В строках 23 и 26 создается целочисленный массив z длиной ю с помощью именованной константы arrayjsize. Оператор for в строках 29 и 30 инициализирует каждый элемент в массиве z. Выражение z. Length возвращает длину массива. Значения элементов генерируются умножением каждого последующего значения счетчика циклов на 2 и прибавлением 2 к произведению. После такой инициализации массив z содержит четные целые числа 2, 4, 6,..., 20. Оператор for в строках 35—37 использует значения в массивах х, у и z для построе- ния последовательности (string) вывода, которая будет отображена в окне MessageBox. Отсчет от нуля (помни- те, что индексы массива начинаются с нуля) обеспечивает циклам доступ к любому элементу массива. Констан- та arrayjsize в условии цикла for (строка 35) есть длина каждого из массивов. 4.13. Передача массивов в методы Для передачи аргумента-массива в метод укажите имя массива без скобок. Например, если массив hourlyTemperatures объявлен как int[] hourlyTemperatures = new int[24]; тогда обращение к методу ModifyArray(hourlyTemperatures); передает массив hourlyTemperatures в метод ModifyArray. Каждый объект массива "знает" свой размер (через свойство Length), цоэтому, когда объект массива передается в метод, размер массива не передается в качестве дополнительного аргумента. Несмотря на то, что полные массивы передаются по ссылке, отдельные элементы массива простых типов дан- ных передаются по значению, так же, как простые переменные. (Объекты, на которые делают ссылки отдельные элементы массива не встроенного типа, передаются по ссылке.) Такие простые отдельные части данных иногда называются скалярами или скалярными величинами. Для передачи элемента массива в метод пользуйтесь ин- дексированным именем элемента массива в качестве аргумента в вызове метода; например, нулевой элемент массива scores передается как scores [0].
Методы и массивы 117 Для того чтобы метод получил массив посредством обращения к методу, список параметров метода должен указать, что массив будет получен. Например, заголовок для метода ModifуАггау должен быть записан как public void ModifуАггау(int[] b) указывая на то, что ModifyArray ожидает получить целочисленный массив в параметре ь. Массивы передаются по ссылке; когда вызванный метод использует имя ь параметра массива, тогда он относится к фактическому массиву в вызывающем операторе. 4.14. Передача массивов по значению и по ссылке В C# переменная, в которой сохранен объект, например массив, фактически сохраняет только ссылку на объект. Разделение между переменными ссылочного типа и переменными простых типов данных поднимает опреде- ленные второстепенные вопросы, в которых программисты должны разбираться для создания защищенных и устойчивых программных продуктов. Когда программа передает аргумент в метод по значению, вызванный метод получает копию значения этого аргумента. Изменения локальной копии не влияют на значение первоначальной переменной, которую про- грамма передала в метод. Если аргумент имеет ссылочный тип, тогда метод создает локальную копию самой ссылки, а не копию фактического объекта, на который делается ссылка. Локальная копия ссылки по-прежнему относится к первоначальному объекту в памяти вызывающего оператора. Таким образом, ссылочные типы все- гда передаются по ссылке, и это означает, что изменения этих объектов в вызванных методах влияют на перво- начальные объекты в памяти. Совет по повышению производительности___________________________________________________ Передача массивов и других объектов по ссылке имеет смысл в плане производительности. Если бы массивы _ передавались по значению, то передавалась бы копия каждого элемента. Для крупных, часто передаваемых массивов эта процедура была бы пустой потерей времени и занимала бы значительные объемы памяти для со- хранения копий массивов, и производительность бы заметно снижалась. C# также позволяет методам передавать ссылки с ключевым словом ref. Эта особенность — довольно тонкая, и неправильное ее использование может привести к возникновению проблем. Например, когда объект ссылочного типа, скажем, массив, передается с ref, тогда вызываемый метод фактически получает контроль над самой пе- редаваемой ссылкой, что позволяет вызванному методу заменить оригинальную ссылку в вызывающем опера- торе на другой объект или даже на null^. Такое поведение может привести к непредсказуемым результатам, ко- торые могут быть катастрофическими в критичных для бизнеса или выполнения задач программных приложе- ниях. Программа в листинге 4,8 демонстрирует тонкое различие между передачей ссылки по значению и передачей ссылки с ключевым словом ref. 1 // Листинг 4.8: ArrayReferenceTest.cs 2 // Тестирование эффектов передачи ссылок на массивы 3 // по значению и по ссылке 5 using System; 6 using System.Drawing; 7 using System.Collections; 8 using System.Component.Model; 9 using System.Windows.Forms; 10 using System.Data; 11 12 public class ArrayReferenceTest : System.Windows.Forms.Form 13 { 14 private System.Windows.Forms.Label outputLabel; 15 private System.Windows.Forms.Button showOutputButton; 16 17 [STAThread] 18 static void Main () 19 { 20 Application.Run (new ArrayReferenceTest()); 21 } 22
118 Глава 4 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 private void showOutputButton_Click ( object sender, System.EventArgs e) { // создание и инициализация firstArray int[] firstArray = { 1, 2, 3 }; // копирование ссылки firstArray (но не самого массива) int[] firstArrayCopy = firstArray; outputLabel.Text « t "Test passing firstArray reference by value"; outputLabel.Text +— ”\n\Contents of firstArray " + "before calling FirstDouble:\n\t"; // печать содержимого firstArray for (int i = 0; i < firstArray4.Length; i++) outputLabel.Text += firstArray[i] + " " ; // передача ссыпки firstArray по значению firstDouble FirstDouble(firstArray); outputLabel.Text + = "\n\nContents of firstArray after " + "calling FirstDouble\n\t"; // печать содержимого библиотеки for (int i = 0; i < firstArray.Length; i++) outputLabel.Text +« firstArray[i] + " " ; // проверьте, была ли ссылка изменена FirstDouble if (firstArray « firstArrayCopy) outputLabel.Text += "\n\nThe reference refer to the same array\n"; else outputLabel.Text += "\n\nThe reference refer to different arrays\n"; // создание и инициализация secondArray int[] secondArray = { 1, 2, 3 }; // копирование ссылки seconderyArray int[] secondArrayCopy = secondArray; outputLabel.Text += "\nTest passing secondArray " + "reference by reference"; outputLabel.Text += "\nContents of secondArray " + "before calling SecondDouble:\n\t"; // печать содержимого secondArray до вызова метода for (int i = 0; i < firstArray.Length; i++) outputLabel.Text += firstArray[i] + " "; SecondDouble (ref secondArray); outputLabel.Text +» "\n\nContents of secondArray " + "after calling SecondDouble:\n\t"; // печать содержимого secondArray до вызова метода for (int i = 0; i < firstArray.Length; i++) outputLabel.Text += firstArray[i] + " " ;
Методы и массивы 119 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 // проверьте, была ли ссылка изменена SecondDouble if (secondArray == secondArrayCopy) outputLabel.Text += "\n\nThe reference refer to the same array\n"; else outputLabel.Text += "\n\nThe reference refer to different arrays\n"; } // конец метода showOutputButton_Click // модификация элементов массива и попытка // модификации ссылки void FirstDouble(int[] array) { // удвоение значения каждого элемента for (int i = 0; i < array.Length; i++) arrayfi] * = 2; // создание новой ссылки и ее присвоение массиву array = new int[] { 11, 12, 13 }; } Рис. 4.8. Передача ссылки на массив по значению и по ссылке // модификация элементов массива и изменение массива ссылки // для обращения к новому массиву void SecondDouble(ref int[] array) { // удвоение значения каждого элемента for (int i = 0; i < array.Length; i++) array[i] * = 2; // создание новой ссылки и ее присвоение массиву array = new int[] { 11, 12, 13 ); } II конец класса ArrayReferenceTest Результат работы программы представлен на рис. 4.8. В строках 27 и 30 объявляются две переменные целочисленного массива — firstArray и firstArrayCopy (соз- дается копия с тем, чтобы определить, не переписывается ли firstArray). В строке 27 инициализируется объект firstArray значениями 1, 2 и 3. Оператором присваивания в строке 30 ссылка firstArray копируется в пере- менную firstArrayCopy, что заставляет эти переменные ссылаться на тот же объект массива в памяти. Оператор for в строках 39 и 40 печатает содержимое firstArray до.его передачи в метод FirstDouble (строка 43), так что можно подтвердить, что этот массив передан по ссылке (т. е. вызванный метод, в самом деле, изменяет содер- жимое массива). Оператор for в методе FirstDouble (строки 100 и 101) умножает значения всех элементов массива на 2. В стро- ке 104 распределяется новый массив, содержащий значения 11,12 и 13; затем ссылка на этот массив присваива- ется параметру array (при попытке переписать ссылку firstArray этого, разумеется, не произошло, потому что сама ссылка была передана по значению). После выполнения метода FirstDouble оператор for в строках 49 и 50 печатает содержимое firstArray, демонстрируя, что значения элементов были изменены методом (и под- тверждая, что в C# массивы всегда передаются по ссылке). Оператор if/else в строках 53—58 сравнивает ссылки firstArray (попытка замены которой была сделана) и firstArrayCopy. Выражение в строке 53 оценива- ется как true, если операнды бинарной операции == в самом деле ссылаются на тот же объект. В этом случае, представленный объект является массивом, распределенным в строке 27, а не массивом, распределенным в ме- тоде FirstDouble (строка 104). В строках 61—91 производятся те же проверки с помощью переменных массивов secondArray, secondArrayCopy и SecondDouble (строки 109—117). Методом SecondDouble выполняются те же операции, что и FirstDouble, но он принимает аргумент массива через ключевое слово ref. В этом случае ссылка, сохраненная в secondArray после обращения к методу, является ссылкой на массив, распределенный в строке 116 SecondDouble, указывая на то, что ссылку, переданную с ключевым словом ref, можно модифицировать вызванным методом так, что ссылка в вызывающем операторе фактически указывает на другой объект — в данном случае, на массив, рас-
120 Глава 4 пре де ленный в методе SecondDouble. Оператор if/else в строках 86—91 демонстрирует, что методы secondArray и secondArrayCopy больше не делают ссылок на один и тот же массив. Замечание по технологии программирования________________________________________ Когда метод получает параметр объекта ссылочного типа по значению, то сам объект не передается по значе- нию: он по-прежнему передается по ссылке. Скорее ссылка на объект передается по значению. Такая схема предупреждает метод от переписывания переданных ему ссылок. В большинстве случаев желательным поведе- нием является защита ссылки вызывающего оператора от модификации. В редких ситуациях, когда действи- тельно нужно, чтобы вызванный метод модифицировал ссылку вызывающего оператора, передавайте ссылку по ключевому слову ref. Замечание по технологии программирования________________________________________ В C# объекты ссылочного типа (включая массивы) всегда передаются по ссылке Поэтому вызванный метод, по- лучающий ссылку на объект в вызывающем операторе, может изменить объект вызывающего оператора 4.15. Многомерные массивы До сих пор рассматривались одномерные массивы — массивы, содержащие единые списки значений. В данном разделе представлены многомерные массивы. Такие массивы требуют двух или более индексов для идентифи- кации тех или иных элементов. Существуют два типа многомерных массивов: прямоугольные и разреженные. Прямоугольные массивы часто представляют собой таблицы значений, тогда как разреженные (зубчатые) мас- сивы сохраняют массивы массивов. Массивы, требующие двух индексов для идентификации конкретного элемента, обычно называются двумерны- ми массивами. В данном разделе упор сделан на такие массивы. Прямоугольные массивы с двумя индексами часто представляют собой таблицы значений, состоящие из информации, расположенной по строкам и столб- цам, где каждая строка и каждый столбец имеют одинаковые размеры. Массив с т строк и п столбцов называет- ся "массивом т на п". Для идентификации конкретного элемента таблицы необходимо указать два индекса; как правило, первый индекс идентифицирует строку элемента, а второй — его столбец. На рис. 4.9 показан двумер- ный прямоугольный массив а, содержащий три строки и четыре столбца (т. е. массив "три на четыре”). Каждый элемент в массиве а определен на рис. 4.9 по имени элемента в форме a [i, j ], где а — имя массива, a i и j — индексы, уникально идентифицирующие строку и столбец, соответственно, каждого элемента в а. Обра- тите внимание, что имена элементов в первой строке все имеют первый индекс, равный 0; имена элементов в четвертом столбце все имеют второй индекс, равный 3. Столбец 0 Столбец 1 Столбец 2 Столбец 3 Строка О Строка 1 Строка 2 а[0, 0] а[0,1] а[0.2] а[0, 3] а[1. 0] а[1,1] а[1.2] а[1,3] а[2,0] а[2.1] а[2, 2] а[2, 3] ------Номер столбца ------Номер строки ------Имя массива Рис. 4.9. Двумерный прямоугольный массив с тремя строками и четырьмя столбцами Многомерные массивы можно инициализировать в объявлениях так же, как и массивы с одним индексом. Дву- мерный массив Ь с двумя строками и двумя столбцами можно объявить и инициализировать как int[,] Ь = new int[2, 2]; b[0, 0] = 1; b[0, 1] = 2; b[l, 0] = 3; b[l, 1] = 4; или объявление можно записать в одну строку с помощью списка инициализации следующим образом: inti,] Ь = { { 1, 2 }, { 3, 4} };
Методы и массивы 121 Значения сгруппированы в фигурных скобках по строкам. Таким образом, 1 и 2 инициализируют Ь[0,0] и Ь[0,1] соответственно, а з и 4 инициализируют b [1,0] и Ь[1,1] соответственно. Программа-компилятор опре- деляет количество строк подсчетом числа вложенных списков инициализации (представленных набором фигур- ных скобок) в основном списке инициализации. Компилятор определяет число столбцов в каждой строке под- счетом количества значений во вложенном списке инициализации для этой строки. Метод GetLength возвраща- ет длину измерения конкретного массива. Для предыдущего примера b.GetLength(0) возвращает длину нулевого измерения массива ь, или нулевой строки. Теперь продемонстрируем создание разреженных массивов. Вспомните, что они поддерживаются как массивы массивов. В отличие от прямоугольных массивов, массивы, образующие разреженные массивы, могут иметь разные длины. Объявление int[][] с = new int[2][]; // распределение 2 строк // распределение и инициализация массива для строки 0 с[0] = new int[] { 1, 2, 3 }; // распределение и инициализация массива для строки 1 с[1] = new int[] { 3, 4, 5 }; создает целочисленный массив с со строкой 0 (которая сама по себе является массивом), содержащий три эле- мента (1, 2 и 3), и со строкой 1 (другой массив), содержащий три элемента — (з, 4 и 5). Каждый элемент масси- ва с сам по себе является массивом, поэтому разреженные массивы могут использовать свойство Length для определения длины каждого подмассива, без необходимости в методе GetLength. Для разреженного массива с размер нулевого подмассива равен с [0] .Length или 3. Для определения числа подмассивов в разреженном массиве можно воспользоваться свойством Length с самим разреженным массивом, как в с.Length (оцениваю- щем в данном примере до 2). В листинге 4.9 продемонстрирована инициализация двумерных массивов в объявлениях и использование вло- женных циклов for для прослеживания массивов (т. е. для манипуляций каждым элементом массивов). 1 // Листинг 4.9: TwoDiitiensionalArrays.cs 2 // Инициализация двумерных массивов 3 / 4 using System; 5 using System.Drawing; 6 using System.Collections; 7 using System.Component.Model; 8 using System.Windows.Forms; 9 using System. Data; 10 11 public class TwoDimensionalArrays : Systern.Windows.Forms.Form 12 { 13 private System.Windows.Forms.Button showOutputButton; 14 private System.Windows.Forms.Label outputLabel; 15 16 // код, сгенерированный Visual Studio .NET 17 18 [STAThread] 19 static void Main() 20 { 21 Application.Run(new TwoDimensionalArrays()); 22 } 23 24 private void showOutputButton_Click ( 25 object sender, System.EventArgs e) 26 { 27 // объявление и инициализация прямоугольного массива 28 int[,J arrayl = new int[,] ( { 1, 2, 3 }, { 4, 5, 6 } }; 29 30 // объявление и инициализация разреженного массива 31 int[](] array2 = new int[3] []; 32 array2[0] = new int[] {1, 2 );
122 Гпава 4 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 array2[l] = new int[] {3 }; array2[2] = new int[] (4, 5, 6 ); outputLabel.Text = "Values in array! by row are\n"; // выходные значения в arrayl for (int i = 0; i < arrayl.GetLength(O); i++) { 1 for (int j =0; j < arrayl.GetLength(1); j++) outputLabel.Text += arrayl[i, j] + " Рис. 4.10. Инициализация двумерных массивов outputLabel.Text += "\n"; } outputLabel.Text += "\nValues in array2 by row are\n"; // выходные значения в array2 for (int i = 0; i < array2.Length; i++) { for (int j = 0; j < array2[i].Length; j++) outputLabel.Text += array2[i][j] + " "; outputLabel.Text +=» "\n"; } } // конец метода showOutputButton_Click ) // конец класса TwoDimensionalArrays Результат работы программы представлен на рис. 4.10. Объявление arrayl (строка 28) представляет шесть инициализаторов в двух подсписках. Первый подсписок инициализирует первую строку массива к значениям 1, 2 и з. Второй подсписок инициализирует вторую строку массива к значениям 4, 5 и 6. Объявление аггау2 (строка 31) создает разреженный массив из трех массивов (обозначенных цифрой 3 в квадратных скобках). В строках 32—34 каждый подмассив инициализируется так, что первый подмассив содержит значения 1 и 2, второй — значение 3, а третий содержит значения 4, 5 и 6. Оператор for в строках 39—45 добавляет элементы arrayl в строковый вывод. Обратите внимание на исполь- зование вложенных операторов for для выхода строк каждого из двумерных массивов. Во вложенных циклах for для arrayl для определения количества элементов в каждом измерении массива применяется метод GetLength. Строка 39 определяет количество строк в массиве активизацией arrayl.GetLength(O), а строка 41 определяет количество столбцов в массиве активизацией arrayl.GetLength(1). Массивы с дополнительными размерами потребуют более глубоко вложенных операторов for для обработки. Вложенные циклы for в строках 50—56 выводят элементы разреженного массива аггау2. Помните о том, что разреженный массив— это, по сути дела, массив, содержащий дополнительные массивы и их элементы В строке 50 используется свойство Length массива аггау2 для определения количества строк в разреженном массиве. Строка 52 определяет длину Length каждого подмассива (т. е. строки) выражением array2 [i] .Length. Во многих распространенных манипуляциях массивами используются операторы цикла for. В оставшейся час- ти раздела рассмотрим манипуляции разреженными массивами. Представим разреженный массив а, содержа- щий три строки, или массива. Следующий цикл for устанавливает все элементы в третьей строке массива а на о: for (int col = 0; col < a[2].Length; col++) a [2] [col] = 0; Была указана третья строка, следовательно, известно, что первый индекс всегда равен 2. (О — первая строка, 1 — вторая.) Цикл for изменяет только второй индекс (т. е. индекс столбца). Обратите внимание на использова- ние а [2] .Length в условном выражении оператора for. Каждая строка а сама по себе является массивом, и, сле- довательно, программа может получить доступ к типичным свойствам массива, включая Length. Эта способ- ность продемонстрирована в строке 52 листинга 4.9. Если предположить, что длина массива а[2] равна 4, тогда предшествующий оператор for эквивалентен операторам присваивания а [2] [0] = 0; а[2][11 = 0; а[2] [2] = 0; а [2] [3] = 0;
Методы и массивы 123 Следующие вложенные операторы for определяют общее количество всех элементов в массиве а: int total = 0; for (int row =0; row < a.Length; row++) for (int col = 0; col < a[row].Length; col ++) total ♦« a[row][col]; В условных выражениях внешнего цикла for используется a.Length для определения количества строк в а (в данном случае их три). Операторы for объединяют элементы массива по одной строке за каждый раз. Внеш- ний оператор for начинается с установки индекса row на о, так что элементы первой строки могут быть подсчи- таны внутренним циклом for. Затем внешний цикл for увеличивает row на 1 так, что можно подсчитать общее количество элементов второй строки. Наконец, внешний цикл for увеличивает row на 2 так, что можно подсчи- тать общее количество элементов второй строки. Результат может быть отображен при прекращении выполне- ния вложенного оператора for. 4.16. Оператор цикла foreach Язык C# предоставляет оператор цикла foreach для итерации через значения в структурах данных, например, в массивах. При использовании одномерных массивов foreach ведет себя как цикл for, который итерируется через диапазон индексов от о до индекса, меньшего на единицу, чем длина массива. Вместо имени массива, ин- дексированного счетчиком, foreach использует переменную для представления значения каждого элемента. В программе, приведенной в листинге 4.10, применяется цикл foreach для определения минимального значения в двумерном массиве степеней. Заголовок цикла foreach (строка 16) задает переменную grade и массив gradeArray. Оператор foreach итериру- ется через все элементы gradeArray с последовательным присвоением каждого значения переменной grade. В строке 18 сравнивается каждое значение с переменной lowGrade, и если какое-либо значение оказывается меньше, чем lowGrade, тогда в строке 19 lowGrade заменяется на это значение. 1 // Листинг 4.10: UsingForeach.cs 2 // Демонстрация оператора foreach 3 4 using System; 5 6 class UsingForeach 7 { 8 // основная входная точка для приложения 9 static void Main (string[] args) 10 { 11 int[,] gradeArray = { { 77, 68, 86, 73 }, 12 { 98, 87, 89, 81 }, { 70, 90, 86, 81 } }; 13 14 int lowGrade = 100; 15 16 foreach (int grade in gradeArray) 17 { 18 if (grade < lowGrade) 19 lowGrade = grade; 20 } 21 22 Console.WriteLine ("The minimum grade is: " + lowGrade); 23 } 24 } Результат работы программы: The minimum grade is: 68 Для прямоугольных массивов повторение оператора foreach начинается с элемента, все индексы которого рав- ны нулю, и итерируется через все возможные комбинации индексов с приращением крайнего правого индекса первым. Когда крайний правый индекс достигает верхней границы, он сбрасывается до нуля, а индекс слева от
124 Гпава 4 него увеличивается на единицу. В этом случае grade принимает значения по их порядку в списке инициализа- ции в строках 11 и 12. После обработки всех степеней отображается lowGrade (строка 22). Несмотря на то, что многие расчеты массивов лучше всего управляются с помощью счетчика, цикл foreach по- лезен, когда индексы элементов не имеют особого значения. Оператор foreach удобен для прохождения циклов через массивы объектов (см. главу 7). i 4.17. Резюме Лучшим способом разработки и обслуживания крупной программы является ее построение из компонентов многократного использования, таких как методы и классы. Данная методика носит название "разделяй и власт- вуй". Программы пишутся объединением новых методов и классов, которые разработчик создает с помощью методов и классов, имеющихся в библиотеке классов .NET Framework (FCL), а также в других библиотеках. Библиотека FCL предоставляет широкий набор классов и методов для выполнения рутинных математических вычислений, манипуляций строками, символами, вводом/выводом, проверки ошибок и других полезных опера- ций. Программист вправе писать методы для определения особых задач, которые могут использоваться в раз- ных точках программы. Эти методы называются "методами, определенными пользователем". Общий формат определения метода позволяет программисту задавать возвращаемые типы и списки параметров. Область действия переменной — это часть программы, где на переменную может быть сделана ссылка. Протя- женность идентификатора (его жизненный цикл) — это период, в течение которого идентификатор существует в памяти. Рекурсия — это мощная методика решения различных проблем, при которой методы могут обращаться сами к себе. При перегрузке методов несколько методов одного класса могут быть определены с одним именем, пока они имеют различные наборы параметров. Массивы — это группы ячеек памяти, имеющие одно имя и тип данных. Массивы группируют списки логиче- ски связанных данных. Доступ к каждой части данных (элементу) может осуществляться с помощью индекса — числового значения, помещаемого в квадратных скобках после имени массива. Число в квадратных скобках определяет, к какому элементу массива осуществляется доступ. C# также обеспечивает создание многомерных массивов — массивов, содержащих два или более индексов. Су- ществуют два типа многомерных массивов: прямоугольные и разреженные. Прямоугольные массивы с двумя индексами часто представляют собой таблицы значений, состоящие из данных, расположенных по строкам и столбцам, где каждая строка и каждый столбец имеют одинаковые размеры. Разреженные массивы поддержи- вают массивы массивов. В отличие от прямоугольных массивов, строки разреженных массивов могут быть раз- ной длины. C# предоставляет еще один оператор цикла — foreach. Он похож на цикл for, но обеспечивает более естест- венный способ итерации через массивы и другие подобные массивам структуры, рассматриваемые в последую- щих главах.
ГЛАВА 5 Объектно-ориентированное программирование Своей высокой цели Успею я достичь. У. Ш. Гилберт Добродетели не утаить на свете этом. Уильям Шекспир Слуги общества служат вам хорошо. Эдлай Стивенсон Классы борются между собой; одни побеждают, другие уничтожаются. Мао ЦзеДун Превыше всего: быть верным самому себе. Уильям Шекспир Темы данной главы: О понятие инкапсуляции и ограничения доступа к данным; О концепция абстракции данных и абстрактных типов данных (Abstract Data Type, ADT); О создание и разрушение объектов; □ управление доступом к переменным экземпляра объекта и методам; □ использование свойств для поддержания объектов в согласованных состояниях; □ использование ссылки this; □ пространство имен и компоновочные блоки; □ использование окон Class View и Object Browser. 5.1. Введение В данной главе рассматривается объектное ориентирование в С#. У некоторых читателей может возникнуть вопрос: "Почему эта тема откладывалась до сих пор?". Причин такого положения дел— несколько. Во-первых, объекты, построение которых описывается в этой главе, в некоторой степени составлены из частей структурных программ. Для того чтобы объяснить организацию объектов, нам необходимо было описать базис структурного программирования с управляющими структурами. Также до представления объектного ориентирования имело смысл более подробно ознакомиться с методами. И, наконец, мы хотели познакомить читателей с понятием массивов, являющихся объектами С#. При рассмотрении объектно-ориентированных программ в главах 1—4 были представлены многие основопола- гающие концепции (т. е. "объекты в мыслях") и терминология (т. е. "объекты на словах"), имеющие отношение к объектно-ориентированному программированию в С#. Также обсуждалась методология программного проек- тирования. Авторами были проанализированы типичные проблемы, решение которых требовалось для построе- ния программ, и определены классы библиотеки FCL, необходимые для реализации каждой программы. Затем для каждой программы выбирались соответствующие переменные экземпляров и методы и задавался способ, с помощью которого объект одного класса взаимодействовал с объектами классов .NET Framework для дости- жения конечных целей программы. Рассмотрим вкратце некоторые ключевые концепции и терминологию объектного ориентирования. В объект- ном ориентировании используются классы для инкапсуляции (т. е. "объединения") данных (атрибутов) и мето- ды (поведения). Объекты имеют свойство сокрытия своей реализации от других объектов (данный принцип на-
126 Глава 5 зывается сокрытием информации). Несмотря на то, что некоторые объекты могут взаимодействовать друг с другом через четко определенные интерфейсы (подобно тому, как интерфейс водителя включает в себя рулевое колесо, педаль акселератора, педаль тормоза и рукоятку коробки передач), одни объекты "не знают”, как реали- зованы другие (точно так же, как водитель досконально не знает устройства рулевого механизма, двигателя, тормозов и трансмиссии). Как правило, подробности реализации скрыты в самих объектах. Однако классным водителем можно быть, не зная подробностей функционирования двигателя, трансмиссии и выхлопного меха- низма. Далее читателям станет понятно, почему сокрытие информации настолько важно для эффективного про- ектирования программных средств. В процедурных языках, таких как С, программирование ориентировано на операции. Программирование в C# — объектно-ориентированное. В С элементом программирования является функция (в C# функции назы- ваются методами). В C# элементом программирования является класс. Объекты, в конечном итоге, инстанции- руются (т. е. создаются) из этих классов, и функции инкапсулируются в "рамках” классов как методы. Программисты на языке С сосредотачиваются на написании функций. Они группируют операции, выполняю- щие определенные задачи, в функцию, после чего функции группируются и образуют программу. Несомненно, в языке С данные важны, но они существуют в основном для поддержки операций, выполняемых функциями. Глаголы в документации системных требований языка С, описывающих требования к новому программному приложению, помогают программисту в С определить набор функций, которые будут работать совместно для реализации всей системы. В C# — наоборот: программисты сосредоточены на создании собственных, определенных пользователем ти- пов, называемых классами. Классы еще называются типами, определенными программистом. Каждый класс содержит данные и набор методов для манипуляций ими. Компоненты данных класса называются переменными экземпляра (многие программисты C# предпочитают термин поля)'. Точно так же, как можно назвать экземпляр встроенного типа, например int, переменной, так и экземпляр определенного пользователем типа (т. е. класс) называется объектом. В C# упор сделан на классы, а не на методы. Существительные в документации систем- ных требований помогают программистам C# определить исходный набор классов, с которого будет начат про- цесс проектирования. Программисты используют эти классы для создания объектов, которые будут работать совместно в рамках целой системы. В настоящей главе рассказывается о создании и использовании классов и объектов, о теме, имеющей общее на- звание объектно-ориентированного программирования (ООП). В главах 6 и7 представлены понятия наследо- вания и полиморфизма — ключевых технологий, делающих возможным объектно-ориентированное программи- рование. Несмотря на то, что подробно наследование описано в главе 6, оно является частью нескольких опре- делений классов в данной главе, и уже использовалось в некоторых примерах предыдущих глав. Например, в программе, рассмотренной в разд. 3.14 (и в нескольких последующих программах), класс наследовался из System.Windows. Forms. Form для создания программного приложения, выполняющегося в отдельном окне. Замечание по технологии программирования_____________________________________________ Все объекты в C# передаются по ссылкам. 5.2. Реализация абстрактного типа данных времени с помощью класса Классы в C# упрощают процесс создания абстрактных типов данных (АТД), скрывающих свою реализацию от клиентов (или пользователей объектом класса). Проблема процедурных языков программирования заключается в том, что код клиента часто зависит от подробностей реализации данных, использованных в этом коде. Такая зависимость может обусловить необходимость переписывания клиентского кода, если реализация данных изме- няется. АТД устраняют эту проблему предоставлением клиентам интерфейсов, независимых от реализации. С такими интерфейсами создатель класса может изменить внутреннюю реализацию данного класса, не влияя на деятельность клиентов класса. Замечание по технологии программирования_____________________________________________ Важно, чтобы программы были написаны понятно, и чтобы их было проще обслуживать и поддерживать. Изме- нение — это, скорее, правило, а не исключение: программист должен предполагать, что его код будет модифи- цирован. Далее читателям станет понятно, что классы упрощают изменяемость программ. 1 Иногда авторы пользуются общепринятой терминологией, например, "члены данных" и "члены экземпляров", а не терминами С#, такими, как, например, поля. Список терминов, присущих языку С#, представлен в спецификации языка C# (msdn.microsoft.com /vstudio/nextgen/technology/csharpdownload.asp).
Объектно-ориентированное программирование 127 Первый пример в данной главе (как и последующие примеры) потребует определений множественных классов в одном проекте. Для добавления в проект класса выберите в меню Project команду Add Class. В открывающемся диалоговом окне Add New Item введите имя нового класса в поле Name и нажмите кнопку Open. Обратите внимание, что имя файла (с расширением cs) появится в окне Solution Explorer под названием проекта. Первый пример содержит классы Timel (листинг 5.1) и TimeTestl (листинг 5 2). Класс Timel предоставляет вре- мя суток в 24-часовом формате. Класс TimeTestl содержит метод Main, создающий экземпляр класса Timel и демонстрирующий особенности этого класса. 1 // Листинг 5.1: Timel.cs 2 // Класс Timel поддерживает время в 24-часовом формате 3 4 using System; 5 б // определение класса Timel 7 public class Timel : Object 8 { 9 private int hour; // 0-23 10 private int minute; // 0-59 11 private int second; // 0-59 12 13 // Конструктор Timel инициализирует переменные экземпляра 14 // нулем для установки времени по умолчанию на полночь 15 public Timel() 16 { 17 SetTimefO, 0, 0); 18 } 19 20 // Установка нового значения времени в 24-часовой формат. 21 // Проверка достоверности данных. Обнуление некорректных значений 22 public void SetTime( 23 int hourValue, int minuteValue, int secondValue) 24 { 25 hour = (hourValue >= 0 && hourValue < 24) ? 26 hourValue : 0; 27 minute = (minuteValue >= 0 && minuteValue < 60) ? 28 minuteValue : 0; 29 second = (secondValue >= 0 && secondValue < 60) ? 30 secondValue : 0; 31 32 } // конец метода SetTime 33 34 // преобразование времени в строку 24-часового формата 35 public string ToUniversalString() 36 { 37 return String.Format( 38 ”{0:D2}:(1:D2):{2:D2}", hour, minute,,second) ; 39 } 40 41 // преобразование времени в строку 12-часового формата 42 public string ToStandardString() 43 { 44 return String.Format("{0}:{1:D2}:(2:D2) {3}", 45 ((hour — 12 I I hour = 0) ? 12 : hour % 12), 46 minute, second, (hour < 12 ? "AM” : nPM”)); 47 } 48 49 } // конец класса Timel Строка? листинга5.1 начинается с определения класса Timel, указывающего на то, что класс Timel наследует из класса object (пространства имен System). Программисты на C# используют наследование для создания
128 Глава 5 классов из существующих классов. Фактически, каждый класс в C# (за исключением object) наследует из опре- деления существующего класса. В строке 7 символ : (двоеточие), за которым следует имя класса object, указы- вает на то, что класс Timel наследует существующие части класса object. Если определение нового класса не содержит двоеточия и имени класса справа от имени нового класса, тогда новый класс неявно наследует из класса object. Для понимания концепций и программ, представленных в данной главе, разбираться в принципах наследования не обязательно. Подробно понятие наследования и класс object рассматриваются в главе 6. Левая фигурная скобка ({) в Строке 8 и правая фигурная скобка (}) в строке 49 устанавливают границы тела класса Timel. Говорится, что любая информация, помещаемая в этом теле, инкапсулируется (т. е. "упаковывает- ся") в классе. Например, в строках 9—11 класса Timel объявляются три переменные int: hour, minute и second, представляющие время суток в универсальном формате (24-часовом). Переменные, объявленные в определении класса, но в определении метода, называются переменными экземпляра’, каждый экземпляр (объект) класса со- держит свою отдельную копию переменных экземпляра этого класса. Ключевые слова public и private являются модификаторами доступа к элементам. В данном примере пере- менные экземпляра или методы с модификатором доступа public доступны везде, где программа имеет ссылку на объект Timel. Однако переменные экземпляра или методы, объявленные с модификатором доступа private, доступны только в определении этого класса, publ ic-члены и private-члены класса могут смешиваться. О хорошем стиле программирования______________________________________________________ Каждому определению переменной экземпляра или метода должен предшествовать модификатор доступа к элементу Модификатор доступа по умолчанию для членов класса — private. В строках 9—11 объявляется каждая из трех переменных экземпляра int — hour, minute и second — с модифи- катором доступа к члену private, указывая на то, что эти переменные экземпляра класса доступны только для членов класса; такая практика называется ограничением доступа к данным (или сокрытием данных). Когда объ- ект класса инкапсулирует такие переменные экземпляра, доступ к переменным имеют только методы или класс объекта. Обычно переменные экземпляра объявляются как private, а методы — как public. Однако возможны методы private и переменные экземпляра public, как будет видно далее. Методы private часто называются утилитными или вспомогательными методами, потому что они могут вызываться только другими методами класса метода private. Утилитные методы предназначены для поддержки функционирования других методов класса. Использование данных public в классе — необычная и довольно опасная практика программирования. Предоставление такого доступа к членам данных — небезопасно: посторонний код (код в других классах) мо- жет задать член данных public некорректные значения, что может привести к катастрофическим результатам. О хорошем стиле программирования______________________________________________________ Авторы предпочитают перечислять сначала переменные экземпляра класса, чтобы при считывании кода про- граммисты видели имя и тип каждой переменной экземпляра до того, как она будет использована в методах класса. , О хорошем стиле программирования_____________________________________________________ Несмотря на то, что члены private и public могут смешиваться, сначала следует перечислить в одной группе все члены private, после чего перечислить все public-члены в другой группе. Замечание по технологии программирования______________________________________________ Объявляйте все переменные экземпляра класса как private. Архитектура доступа private-данных через свой- ства public, первыми подтверждающими корректность данных, позволяет разработчику убедиться, что данные объекта остаются в согласованном состоянии. Замечание по технологии программирования______________________________________________ Выводите private-член класса за пределы определения класса, если нет причин непосредственного доступа к этому члену класса. Классы часто включают в себя методы доступа, которые могут считывать или отображать данные. Другим общим применением методов доступа является проверка истинности условий: при таком использовании эти методы часто называются предикативными. Например, можно разработать предикативный метод isEmpty для класса-контейнера — класса, способного удерживать множество объектов, например, связанный список, стек или очередь. (Эти структуры данных рассматриваются подробно в главе 20.) IsEmpty возвратит true, если кон- тейнер пуст; в противном случае — false. Программа может протестировать IsEmpty до попытки считывания очередного члена из контейнера объекта. Точно так же программа может протестировать другой предикатив- ный метод (например, isFuii) до попытки вставки члена в контейнер объекта.
Объектно-ориентированное программирование 129 Класс Timei содержит конструктор Timei (строки 15—18) и методы SetTime (строки 22—32), ToUniversalstring (строки 35—39) и ToStandardString (строки 42—47). Эти методы являются методами public (также называются службами public или интерфейсами public) класса. Клиенты класса Timei, напри- . мер класс TimeTestl (листинг 5.2), используют методы public класса Timei для манипуляций данными, храня- щимися в объектах Timei, либо для того, чтобы класс Timei предоставил какую-либо службу. В строках 15—18 определяется конструктор класса Timei. Конструктор класса инициализирует объекты этого класса. Когда программа создает объект класса Timei с оператором new, вызывается конструктор для инициали- зации объекта. Конструктор класса Timei вызывает метод SetTime (строки 22—32) для инициализации перемен- ных экземпляра hour, minute и second на 0 (что обозначает полночь). Конструкторы могут принимать аргумен- ты, но не могут возвращать значения. Как будет видно далее, класс может иметь перегруженные конструкторы. Важным различием между конструкторами и другими .методами является то, что конструкторы не могут зада- вать возвращаемый тип. Вообще говоря, конструкторы объявляются как public. Обратите внимание, что имя конструктора должно быть тем же самым, что и имя класса. Распространенная ошибка программирования___________________________________________________ Попытка возвращения (через оператор return) значения из конструктора является синтаксической ошибкой. Метод SetTime (строки 22—32) — это метод public, получающий три параметра int и использующий их для установки времени. Условное выражение проверяет каждый аргумент для определения, находится ли значение в заданном диапазоне. Например, значение hour должно быть больше или равно 0 и меньше 24, потому что в уни- версальном формате часы представлены как целые числа от 0 до 23. Точно так же значения minute и second должны быть больше или равны нулю и меньше 60. Любые значения за предёлами этих диапазонов являются некорректными и будут по умолчанию сбрасывать параметры в ноль. Это обеспечивает нахождение в объекте Timei всегда допустимых данных (потому что в нашем примере 0 — это корректное значение для hour, minute и second). Когда пользователь вводит в SetTime недопустимые данные, тогда программа может указать, что уста- новленное время — неверно. В главе 8 будет рассматриваться обработка исключений, которую можно исполь- зовать для указания недопустимых значений инициализации. Замечание по технологии программирования___________________________________________________ Всегда определяйте класс так, чтобы каждая из его переменных экземпляра всегда содержала корректные зна- чения. Метод ToUniversalstring (строки 35—39) не принимает никаких аргументов и возвращает строку в универ- сальном временном формате, состоящем из шести цифр: двух для обозначения часов, двух— для минут и двух— для секунд. Например, если время— 1:30:07 РМ, то метод ToUniversalstring возвратит 13:30:07. Строки 37 и 38 используют string-метод Format для конфигурирования строки универсального времени. В строке 37 в метод Format передается форматирующая строка "{0:D2}: {1:D2}: {2:D2}", содержащая не- сколько спецификаций формата, указывающих на то, что аргументы о, 1 и 2 (первые три аргумента после аргу- мента форматирующей строки) должны иметь формат D2 (двухцифровой десятичный формат) для целей ото- бражения. Спецификация формата D2 выводит одноцифровые значения в виде двух цифр с предшествующим о (например, цифра 8 будет представлена как 08). Два двоеточия, разделяющие фигурные скобки { и },— это символы-разделители часов, минут и секунд в результирующей строке. Метод ToStandardString (строки 42—47) не принимает никаких аргументов и возвращает строку в стандартном формате времени, состоящем из значений hour, minute и second, разделенных запятыми, за которыми следует индикатор РМ или AM (например, 1:27:06РМ). Как и метод ToUniversalstring, метод ToStandardString ис- пользует string-метод Format для форматирования параметров minute и second как значений из двух цифр с предшествующими нулями (при необходимости). В строке 45 определяется значение для hour в строке: если hour — о или 12 (AM или РМ), то этот параметр отображается как 12; в противном случае он отображается как значение от 1 до и. После того как класс определен, его можно использовать как в качестве типа в объявлении, например: Timei sunset; // ссылка на объект Timei Имя класса (Timei) — это имя типа. Класс может выдать множество объектов так же, как встроенный тип дан- ных, например int, может выдать множество переменных. Программисты могут при необходимости создавать типы классов; эта возможность — одна из причин того, что C# называется расширяемым языком. Класс TimeTestl (листинг 5.2) использует экземпляр класса Timei. Метод Main (строки 11—40) объявляет и инициализирует экземпляр Timei— time (строка 13). Когда создается объект, оператор new распределяет па- мять, в которой будет храниться объект Timei; он называется конструктором Timei (строки 15—18 листинга 5.1) для инициализации переменных экземпляра объекта Timei. Как отмечалось выше, этот конструктор активизиру- 9 Зак. 3333
130 Глава 5 ет метод SetTime класса Timel для инициализации каждой переменной экземпляра private к 0. После этого оператор new (строка 13 листинга 5.2) возвращает ссылку на вновь созданный объект; эта ссылка присваивается . time. Замечание по технологии программирования______________________________________________ Обратите внимание на взаимосвязь между оператором new и конструктором класса. Когда оператор new создает объект класса, то конструктор этого класса вызывается для инициализации переменных экземпляра объекта. 1 // Листинг 5.2: TimeTestl.cs 2 // Демонстрация класса Timel 3 4 using System; 5 using System.Window.Forms; 6 7 // TimeTestl создает и использует объект Timel 8 class TimeTestl 9 { 10 11 главная точка входа для приложения 11 static void Main(string[] args) 12 { 13 Timel time - new Timel О; // вызывает конструктор Timel 14 string output; 15 16 // присвоение строкового представления времени выходным данным 17 output = "Initial universal time is: " + 18 time.ToUniversalStringO + 19 "\nlnitial standard time is: " + 20 time.ToStandardString(); 21 22 // попытка настроек допустимых значений 23 time.SetTime(13, 27, 6); 24 25 // добавление новых представлений'времени в выходные данные 26 output += "\n\nUniversal time after SetTime is: ” + 27 time.ToUniversalStringO + 28 "\nStandard time after SetTime is: " + 29 time.ToStandardString(); 30 31 // попытка настроек некорректных значений 32 time.SetTime(99, 99, 99); 33 34 output += "\n\nAfter attempting invalid settings: " + 35 "\nUniversal time: " + time.ToUniversalStringO + 36 "\nStandard time: " + time.ToStandardString(); 37 38 MessageBox.Show(output, "Testing Class Timel”); 39 40 ) // конец метода Main 41 42 ) // конец класса TimeTestl Обратите внимание, что файл TimeTestl не использует ключевого слова using для импортирования пространст- ва имен, содержащего класс Timel. Если класс находится в том же пространстве имен, что и класс, его исполь- зующий, тогда оператор using не требуется. Каждый класс в C# — это часть пространства имен. Если програм- мист не указывает для класса пространство имен, тогда класс помещается в пространство имен по умолчанию, включающее все скомпилированные классы в текущем каталоге, который находится вне пространства имен. В Visual Studio .NET текущий каталог — это каталог, в котором расположен текущий проект. Для классов .NET Framework необходимо задавать операторы using, потому что они определены вне пространства имен каждого нового создаваемого программного приложения. Обратите внимание, что операторы using не нужны, если про-
Объектно-ориентированное программирование 131 грамма полностью квалифицирует имя каждого класса предшествованием имени класса именем пространства имен и точечным оператором. Например, программа может активизировать метод show класса MessageBox сле- дующим образом: System.Windows.Forms.MessageBox.Show("Your message here"); Впрочем, пользоваться такими громоздкими именами не слишком удобно. В строке 14 объявляется ссылка output класса string для сохранения строки, содержащей результаты, что поз- же будет отражено в MessageBox. В строках 17—20 выходным данным output присваивается время в универ- сальном формате (активизацией метода ToUniversaistring объекта Timel) и в стандартном формате (активиза- цией метода Tostandardstring объекта Timel). Обратите внимание на синтаксис вызова метода в каждом слу- чае — за ссылкой time следует оператор доступа к члену класса . (точка), за которым, в свою очередь, указано имя метода. Имя ссылки задает объект, который получит вызов метода. В строке 23 задается время для объекта Timel, к которому относится time, путем передачи допустимых apiy- ментов hour, minute и second в метод Timel SetTime. В строках 26—29 к выходным данным output прибавляет- ся новое значение времени в универсальном и стандартном форматах для подтверждения корректной установки времени. Для иллюстрации того, что метод SetTime подтверждает переданные в него значения, в строке 32 методу SetTime передаются недопустимые аргументы времени. В строках 34—36 к выходным данным output добавля- ется новое время в обоих форматах. Все три значения, переданные в SetTime, — недопустимые, поэтому пере- менные экземпляра hour, minute и second устанавливаются на 0. В строке 38 отображается класс MessageBox с результатами программы. Обратите внимание, что в двух последних строках окна выходных данных время на самом деле было установлено на полночь, когда в метод SetTime были переданы некорректные аргументы. Результат работы программы представлен на рис. 5.1. Рис. 5.1. Демонстрация использования абстрактного типа данных Timel — первый пример класса, не содержащего метода Main. Следовательно, класс Timel нельзя использовать для начала выполнения программы. Класс TimeTestl определяет метод Main, поэтому класс TimeTestl можно использовать для начала выполнения программы. Класс, содержащий метод Main, также называется точкой входа в программу. Обратите внимание на то, что программа объявляет переменные экземпляра hour, minute и second как private. Такие переменные экземпляра недоступны за пределами класса, в котором они определены.' Клиенты класса не должны учитывать представление данных этого класса. Клиенты класса должны иметь дело только со служба- ми, предоставляемыми этим классом. Например, класс может представить время внутренне, как количество се- кунд, истекших с момента предыдущей полночи. Предположим, что после этого представление данных изменя- ется. Клиенты по-прежнему смогут использовать те же методы public и получать те же результаты, не зная об изменениях во внутреннем представлении. В этом смысле считается, что реализация класса скрыта от клиен- тов. Замечание по технологии программирования______________________________________________ Сокрытие информации стимулирует модифицируемость программы и упрощает восприятие класса клиентом. Замечание по технологии программирования_________________________________________________ Клиенты класса могут (и должны) использовать класс, не зная внутренних подробностей реализации этого клас- са. Если реализация класса изменяется (например, для повышения производительности), но интерфейс класса остается постоянным, тогда менять исходный код клиента необходимости нет. Такая практика значительно уп- рощает процесс модификации систем. В данной программе конструктор Timel инициализирует переменные экземпляра к о (универсальный формат времени, эквивалентный 12 AM) для подтверждения того, что объект создан в согласованном состоянии, т. е.
132 Глава 5 все переменные экземпляра имеют допустимые значения. Переменные экземпляра объекта Timei не могут со- хранять некорректные значения, потому что конструктор, вызывающий SetTime, нужен для инициализации пе- ременных экземпляра при создании объекта Timei. Метод SetTime тщательно исследует последующие попытки клиента модифицировать переменные экземпляра. Обычно переменные экземпляра класса инициализируются в конструкторе этого класса, но они также могут быть инициализированы при их объявлении в теле класса. Если программист не инициализирует переменные- экземпляра явно, тогда компилятор инициализирует их неявно. В таких ситуациях компилятор сбрасывает чи- словые переменные простых типов данных в 0, значение bool — в false, а ссылки — в null. Методы ToUniversalstring и ToStandardString не принимают аргументов, потому что по умолчанию эти мето- ды манипулируют переменными экземпляра конкретного объекта Timei, в котором они активизированы. Данное условие часто делает обращения к методам более сжатыми, нежели традиционные обращения к функциям в процедурных языках программирования. Оно также снижает вероятность передачи ошибочных аргументов, ошибочных типов аргументов или ошибочное количество аргументов. Замечание по технологии программирования______________________________________________ Применение принципа объектно-ориентированного программирования часто упрощает обращения к методам пу- тем сокращения количества параметров, которое необходимо передать. Это преимущество объектно- ориентированного программирования происходит из того факта, что инкапсуляция переменных экземпляра и ме- тодов в рамках объекта обеспечивает методам объекта право доступа к переменным экземпляра объекта. Классы упрощают программирование, потому что клиенту приходится принимать во внимание только операции public, инкапсулированные в объекте. Обычно такие операции ориентированы на клиента, а не на реализацию. Клиенты ничего не знают о реализации и не участвуют в ней. Интерфейсы меняются менее часто, нежели реа- лизации. При изменении реализации зависящий от него код должен изменяться соответственно. Сокрытием реализации устраняется вероятность того, что части другой программы станут зависимыми от подробностей реализации класса. Зачастую программистам не приходится создавать классы "с нуля". Они могут заимствовать классы из других классов, обеспечивающих поведения, необходимые новым классам. Классы также могут включать в себя ссыл- ки на объекты других классов, такие как члены. Такое многократное использование программного обеспечения может радикально повысить производительность труда разработчика. В главе б рассматривается наследова- ние — процесс, в соответствии с которым новые классы заимствуются из существующих. В разд. 5.8 рассмат- ривается составление (или агрегирование), при котором классы включают в себя в качестве элементов ссылки на объекты других классов. 5.3. Область действия классов В разд. 4.8 рассматривалась область действия методов. Перейдем к области действия классов. Переменные эк- земпляра и методы класса принадлежат к области действия этого класса. В рамках области действия класса его члены сразу становятся доступными для всех методов этого класса, и на них можно сделать ссылку по имени. За пределами области действия класса на его члены нельзя делать ссылки непосредственно по имени. Доступ к видимым членам класса (таким как public-члены) может быть осуществлен только через "дескриптор” (т. е. ссылки на члены можно делать через формат referenceName.memberName). Если переменная определена в методе, тогда доступ к этой переменной разрешен только этому методу (т. е. пе- ременная является для этого метода локальной). Говорится, что такие переменные имеют область действия блока. Если метод определяет переменную, имеющую то же имя, что и переменная с областью действия класса (т. е. переменная экземпляра), тогда переменная области действия метода скрывает в области действия этого метода переменную области действия класса. Доступ к скрытой переменной экземпляра может быть осуществ- лен в методе с вводом перед именем ключевого слова this и точечного оператора, как, например, this.hour. Ключевое слово this рассматривается в разд. 5.9. 5.4. Управление доступом к членам класса Модификаторы доступа к членам класса— public и private — управляют доступом к данным и методам клас- са. (В главе 6 представлены дополнительные модификаторы доступа — protected и internal.) Как отмечалось ранее, методы public обеспечивают клиентам класса просмотр служб, предоставляемых этим классом (т. е. public-интерфейс класса). Ранее упоминались преимущества написания методов, выполняющих только одну задачу. Если для расчета окончательного результата метод должен выполнять и другие задачи, то они выполняются с помощью вспомогательного метода. Клиенту не нужно их вызывать или разбираться в том,
Объектно-ориентированное программирование 133 как класс использует эти вспомогательные методы. По этим причинам вспомогательные методы объявляются как private-члены класса. Распространенная ошибка программирования_________________________________ Попытка доступа к private-члену класса за пределами этого класса является ошибкой компиляции. В примере, представленном в листинге 5.3 (в котором используется класс Timel из листинга 5.1), демонстриру- ется, что к private-членам класса нет доступа за пределами класса. В строках 12—14 делается попытка доступа к private-переменным экземпляра hour, minute и second объекта Timel, к которому относится time. После того, как эта программа будет скомпилирована, компилятор генерирует ошибку с сообщением о том, что private- члены hour, minute и second недоступны. 1 2 3 4 5 - б 7 8 9 10 11 12 13 14 15 16 17 // Листинг 5.3: RestrictedAccess.cs // Демонстрация ошибок компиляции при попытке доступа // к private-членам класса class RestrictedAccess { // главная точка входа для приложения static void Main(string!] args) { Timel time » new Timel (); time.hour = 7; time, minute = 15; time.second = 30; } } // конец класса RestrictedAccess Рис. 5.2. Ошибки компиляции Результат работы программы представлен на рис. 5.2. Доступ к private-данным должен тщательно контролироваться методами класса. Для того чтобы клиенты мог- ли считывать значения private-данных, класс может задать свойство, обеспечивающее коду клиента безопас- ный доступ к private-данным. Свойства (см. разд. 5.7) содержат методы доступа, управляющие подробностя- ми модификации и возвращения данных. Определение свойства может содержать процедуру доступа get, про- цедуру доступа set или обе. Процедура доступа get позволяет клиенту считывать значение private-данных; процедура доступа set разрешает клиенту модифицировать это значение. Может показаться, что подобная мо- дификация является нарушением понятия private-данных. Однако процедура доступа set может предоставить возможности проверки корректности данных (например, контроль попадания в интервал) для подтверждения корректности заданного значения. Процедура доступа set также может преобразовывать формат данных, ис- пользованных в интерфейсе, в формат, использованный в базовой реализации, и наоборот. Точно так же, про- цедура доступа get может не отображать данные в "сыром" формате: процедура доступа get может отредакти- ровать данные и ограничить их просмотр клиентом. Замечание по технологии программирования ________________________________________________ Разработчикам классов не нужно предоставлять процедуру доступа set или get каждому члену данных private; эта возможность должна предоставляться только тогда, когда это имеет смысл. Замечание по технологии программирования__________________________________________ Объявление переменных экземпляра класса как private, а методов и свойств (см. разд. 5.7) класса как public упрощает отладку, потому что проблемы с манипулированием данными локализованы методами класса. 5.5. Инициализация объектов классов: конструкторы Когда программа создает экземпляр класса, она активизирует конструктор класса для инициализации перемен- ных экземпляра класса (объектов данных). Класс может содержать перегруженные конструкторы для обеспече- ния различных способов инициализации объектов этого класса. Переменные экземпляра могут быть инициали- зированы либо конструктором, либо при их объявлении в теле класса. Независимо от того, получают перемен-
134 Глава 5 ные экземпляра явные значения инициализации или нет, эти переменные всегда инициализируются. В случаях отсутствия явных значений инициализации переменные экземпляра получают значения по умолчанию (о — для числовых переменных встроенных типов данных, false — для переменных bool и null — для ссылок). Замечание по технологии программирования________________________________________________ Если это уместно, создайте конструктор, обеспечивающий инициализацию каждого объекта допустимым значе- ниями. При создании объекта класса программист может ввести в скобках инициализаторы справа от имени класса. Инициализаторы — это аргументы конструктора. В общем виде объявления принимают форму ClassName objectReference =* new ClassName(arguments); где objectReference— ссылка соответствующего типа данных, оператор new указывает на то, что создается объект, ClassName указывает тип нового объекта (и имя вызываемого конструктора), a arguments задает разде- ленный запятыми список значений, используемых конструктором для инициализации объекта. В листинге 5.4 показано использование инициализаторов и перегруженных конструкторов. Если класс не определяет конструкторы,.тогда программа-компилятор обеспечивает конструктор по умолча- нию (без аргументов). Такой конструктор не содержит кода (т. е. имеет пустое тело), и не имеет никаких пара- метров. Программист также может задать конструктор по умолчанию, как демонстрировалось в классе Timel (см. листинг 5.1). Конструкторы, представленные по умолчанию программистом, могут иметь в своем теле код. Распространенная ошибка программирования________________________________________________ Если класс имеет конструкторы, но ни один из public-конструкторов не является конструктором по умолчанию, а программа делает попытку вызова конструктора без аргументов для инициализации объекта класса, то имеет место ошибка компиляции. Конструктор может быть вызван без аргументов только в отсутствие конструкторов для класса (в этом случае вызывается конструктор, предоставленный компилятором по умолчанию), либо, если класс определяет конструктор без аргументов public. 5.6. Использование перегруженных конструкторов Как и методы, конструкторы класса могут быть перегруженными. Конструктор Timel, показанный в листин- ге 5.1, инициализировал hour, minute и second к 0 (т. е. 12 пополуночи в стандартном формате времени) через вызов метода класса SetTime. Однако класс Time2 (листинг 5.4) перегружает конструктор для обеспечения раз- ных путей инициализации объектов Time2. Каждый конструктор вызывает метод Time2 SetTime; этим подтвер- ждается, что объект начинается в согласованном состоянии установкой значений вне диапазона на ноль. C# активизирует соответствующий конструктор сличением количества, типов и порядка аргументов, указанных в вызове конструктора с количеством, типами и порядком параметров, указанных в каждом определении конст- руктора. - .л. ..... . .... ...... .Л...,.- . . . ...............7 - Листинг 6.4 Перегруженные конструкторы, обеспечивающие гибкие возможности инициализации объекта s ...................................................... —---— ...................... 1 // Листинг 5.4: Time2.cs 2 // Класс Time2 предоставляет перегруженные конструкторы 3 4 using System; 5 6 // определение класса Time2 7 public class Time2 8 { 9 private int hour; 11 0-23 10 private int minute; // 0-59 11 private int second; // 0-59 12 13 // Конструктор Time2 инициализирует переменные Экземпляра 14 //на ноль для установки времени по умолчанию на полночь 15 public Time2() 16 { 17 SetTime (0, 0, 0); 18 } 19
Объектно-ориентированное программирование 135 20 21 22 23 24 25 26 27 28 29 30' 31 32 33 34 35 36 3*1 38 39 40. 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 // Конструктор Time2: представлены часы; минуты и секунды // по умолчанию установлены на 0 public Time2(int hour) { SetTime(hour, 0, 0); 1 // Конструктор Time2: представлены часы и минуты; секунды //по умолчанию установлены на 0 public Time2(int hour, int minute) { SetTime(hour, minute, 0); ) 11 Конструктор Time2: представлены часы, минуты и секунды public Time2(int hour, int minute, int second) < SetTime(hour, minute, second); } // Конструктор Time2: инициализация с помощью другого объекта Time2 public Time2(Time2 time) { . Vt SetTxme(time.hour, time .minute, time.second); 1 // Установка нового значения времени в 24-часовом формате. // Проверка корректности данных. Обнуление недопустимых значений, public void.SetTime( int hourValue, int minuteValue, int secondValue) { hour = (hourValue >= 0 && hourValue < 24) ? hourValue : 0; minute = (minuteValue >= 0 && minuteValue < 60) ? minuteValue : 0; second = (secondValue >= 0 && secondValue < 60) ? secondValue : 0; ) // преобразование времени в строку 24-часового формата public string ToUniversalstring() { return String.Format( "(0:D2):(1:D2):{2:D2}", hour, minute, second); } // преобразование времени в строку 12-часового формата public string ToStandardString() { return String.Format (’’{0}: {1:D2}: {2:D2} {3}", ((hour "12 11 hour =0) ? 12 : hour % 12), minute, .second, (hour < 12 ? "AM" : "PM")); } } // конец класса Time2 Из-за того, что большая часть кода в классе Time2 идентична коду класса Timei, в обсуждении основной упор сделан только на перегруженные конструкторы. В строках 15—18 определяется конструктор без аргументов, устанавливающий время на полночь. В строках 22—25 определяется конструктор Time2, принимающий один аргумент int, обозначающий hour, и устанавливающий время с помощью указанного значения hour и о для minute и second. В строках 29—32 определяется конструктор Time2, принимающий два аргумента int, обозна- чающих hour и minute, и устанавливающий время, используя эти значения, и о для second. В строках 35—38 определяется конструктор Time2, принимающий три аргумента int, представляющих hour, minute и second, и использующий эти значения для установки времени. В строках 41—44 определяется конструктор Time2, при-
136 Глава 5 нимаюший ссылку на другой объект Time2. После вызова последнего конструктора значения из аргумента Time2 используются для инициализации значений hour, minute и second нового объекта Time2. Несмотря на то, что класс Time2 объявляет hour, minute и second как private (строки 9—11), конструктор Time2 может получить доступ к этим-значениям напрямую в своем аргументе Time2 с помощью выражений time.hour, time.minute и time.second, соответственно. Замечание по технологии программирования________________________________________________ Когда один объект класса имеет ссылку на другой объект того же класса, тогда первый объект имеет доступ к данным и методам второго объекта (включая private). Обратите внимание, что второй, третий и четвертый конструкторы (строки 22,29 и 35) имеют некоторые общие аргументы, и что эти аргументы поддерживаются в одном и том же порядке. Например, конструктор, начинаю- щийся в строке 29, имеет в качестве двух аргументов целое число, обозначающее часы, и целое число, обозна- чающее минуты. Конструктор в строке 35 имеет эти же два аргумента в том же порядке, за которыми следует последний аргумент (целое число, обозначающее секунды). Распространенная ошибка программирования________________________________________________ При определении перегруженных конструкторов старайтесь поддерживать по возможности одинаковый порядок аргументов; такой подход упрощает программирование клиента. Конструкторы не задают возвращающих типов, иначе результатом будут синтаксические ошибки. Также обра- тите внимание, что каждый конструктор принимает разное количество аргументов разных типов. Даже если только два конструктора принимают значения hour, minute и second, каждый из этих конструкторов вызывает SetTime со значениями для hour, minute и second, используя нули для отсутствующих значений для удовлетво- рения требования SetTime о трех аргументах. Класс TimeTest2 (листинг 5.5) начинает приложение, демонстрирующее использование перегруженных конст- рукторов (см. листинг 5.4). В строках 15—20 создается шесть объектов Time2, активизирующих различные кон- структоры класса. В строке 15 активизируется конструктор без аргументов вводом пустых скобок после имени класса. В строках 16-Г-20 активизируются конструкторы Time2, принимающие аргументы. Для активизации со- ответствующего конструктора в него передаются нужное количество, типы и порядки аргументов (заданные в определении конструктора). Например, строка 16 активизирует конструктор, определенный в строках 22—25 листинга 5.4. В строках 22—47 активизируются методы ToUniversalString и ToStandardString для каждого объекта Time2 для демонстрации корректной инициализации объектов конструкторами. j Листинг 5.5, Демонстрация перегруженного кднструктрра - aa,aaav<aaa«>,k,kav«akkk'a<kk«ka>„*aa>k««aia4k*a«,aa,aa.'vaaaa,a 1 // Листинг 5.5; TimeTest2.cs 2 // Использование перегруженных конструкторов 3 4 using System; 5 using System.Windows.Forms; 6 7 // TimeTest2 демонстрирует конструкторы класса Time2 8 class TimeTest2 9 { 10 // главная точка входа для приложения 11 static void Main(string[] args) 12 { 13 Time2 timel, time2, time3, time4, time5, time6; 14 15 timel = new Time2(); // 00:00:00 16 time2 = new Time2 (2) ; // 02:00:00 17 time3 = new Time2(21, 34); // 21:34:00 18 time4 = new Time2(12, 25, 42); // 12:25:42 19 time5 = new Time2(27, 74, 99); // 00:00:00 20 time 6 = new Time2(time4); // 12:25:42 21 22 String output = "Constructed with: " + 23 "\ntimel: all arguments defaulted" + 24 “\n\t" +.timel.ToUniversalString() + 25 "\n\t" + timel.ToStandardString(); 26
Объектно-ориентированное программирование 137 27 output += "Xntime2: hour specified; minute and " + 28 "second defaulted" + 29 "\n\t" + time2.ToUniversalString() + 30 "XnXt" + time2.ToStandardString(); 31 32 output += "\ntime3: hour and minute specified; " + 33 "second defaulted" + 34 "XnXt” + time3.ToUniversalString() + 35 "XnXt" + time3.ToStandardString(); 36 37 output += "\ntime4: hour, minute, and second specified " + 38 "\n\t" + time4 .ToUniversalStfingO + 39 "XnXt" + time4.ToStandardStringO ; 40 41 output += "\ntime5: all invalid values specified" + 42 "XnXt" + time5.ToUniversalString() + 43 "XnXt" + time5.ToStandardString(); 44 45 output += "Xntime6: Time2 object time4 specified" + 46 "XnXt" + time6.ToUniversalString() + 47 "XnXt" + time6.ToStandardString(); 48 4 9 MessageBox.Show(output, 50 "Demonstrating Overloaded Constructors"); 51 52 } // конец метода Main 53 54 } // конец класса TimeTest2 Рис. 5.3. Демонстрация перегруженного конструктора Каждый конструктор Time2 также можно записать для включения в него копии соответствующих операторов из метода SetTime. Такой подход может быть немного эффективнее, потому что он устраняет дополнительный вы- зов SetTime. Однако, рассмотрим, что получится, если, например, программист изменит представление времени с трех значений int (требующих 12 байтов памяти) на одно значение int, обозначающее общее количество се- кунд, истекших за день (требует 4 байта памяти). Размещение идентичных кодов в конструкторах Time2 и мето- де SetTime намного затрудняет такое изменение в определении класса, потому что тело каждого конструктора потребует модификаций для манипулирования данными как одним значением int, а не тремя. С другой сторо- ны, если конструкторы Time2 вызывают SetTime напрямую, то любые изменения в реализацию SetTime можно гнести только один раз — в тело SetTime. Такой подход снижает вероятность появления ошибки программиро- вания при изменении реализации, потому что в класс вносится только одно изменение, вместо изменений каж- дого конструктора и метода SetTime. Замечание по технологии программирования _______________________________________________ Если метод класса обеспечивает необходимую конструктору (или другому методу) функциональность класса, вызовите этот метод (или другой) из конструктора. Такая практика упрощает обслуживание кода и снижает веро- ятность появления в нем ошибок. Результат работы программы представлен на рис. 5.3. 5. 7. Свойства Методы класса могут манипулировать переменными private-экземпляра этого класса. Типичной манипуляцией может быть настройка баланса клиента банка — переменной экземпляра private класса BankAccount — мето- дом Computeinterest. Классы часто предоставляют свойства private, что позволяет клиентам устанавливать — set (т. е. присваи- вать значения) или получать — get (т. е. получать значения) переменных private-экземпляра. Например, в лис- тинге 5.6 созданы три свойства— Hour, Minute и Second, имеющие доступ к hour, minute и second соответст- венно. Каждое свойство содержит процедуру доступа get (для получения значения поля) и процедуру доступа set (для модификации значения поля). Наличие set и get похоже на получение переменных экземпляра public. Впрочем, данный аспект— это еще одна тонкость С#, делающая этот язык весьма привлекательным инструментом с точки зрения разработки про-
138 Глава 5 граммных средств. Если переменная экземпляра — public, тогда ее можно считать или записать любым мето- дом в программе. Если переменная экземпляра— private, тогда процедура доступа public get может позво- лить другим методам считывать данные по необходимости Однако процедура доступа get может контролиро- вать форматирование и отображение данных. Точно так же процедура доступа public set может отслеживать попытки изменения значения переменной экземпляра, обеспечивая полное соответствие нового значения тому или иному элементу данных. Например, попытка указать (set) день месяца под номером 37 будет отклонена, как и попытка указать (set) вес человека отрицательным значением. Поэтому процедуры доступа set и get мо- гут обеспечить доступ к private-данным, но реализация этих средств доступа управляет тем, что клиентский код может сделать с данными. Объявление переменных экземпляра как private не гарантирует целостности этих переменных. Программисты должны выполнять проверку достоверности; C# обеспечивает только структуру, в которой разработчики могут создавать более надежные и устойчивые программные продукты. Совет по тестированию и отладке_____________________________________________________________ Методы, задающие значения private-данных должны подтверждать, что новые значения будут допустимыми, если они некорректны, тогда процедуры доступа set будут помещать переменные private-экземпляра в соот- ветствующее согласованное состояние. Процедуры доступа set свойства не могут возвращать значения, указывая на неудавшуюся попытку присвоить некорректные данные объектам класса. Такие возвращаемые значения могут быть полезны для клиента класса при обработке ошибок. Клиент может предпринять соответствующие действия, если объекты занимают недо- пустимые состояния. В главе 8 описывается обработка исключительных ситуаций — механизм, применяемый для указания на попытки присвоения элементам объекта недопустимых значений. В листинге 5.6 класс Time расширен, теперь он называется Time3 и содержит свойства переменных private- экземпляров — hour, minute и second. Процедуры доступа set этих свойств строго контролируют задание до- пустимых значений переменным экземпляра. При попытке задать любой переменной экземпляра некорректное значение такая переменная устанавливается на 0 (таким образом, она остается в согласованном состоянии). Каждая процедура доступа get возвращает значение соответствующей переменной экземпляра. Данное прило- жение также представляет методики обработки событий усовершенствованного графического интерфейса поль- зователя (рис. 5.4), включающего несколько кнопок, которыми можно пользоваться при манипуляциях показа- телями времени, сохраненными в объекте Time3. 1 // Листинг 5.6: Time3.cs 2 // Класс Time2 демонстрирует свойства 3 4 using System; 5 б // определение класса Time3 7 public class Time3 8 { 9 private int hour; // 0-23 16 private int minute; // 0-59 11 private int second; // 0-59 12 13 // Конструктор Time3 обнуляет переменные экземпляра 14 11 для установки времени по умолчанию на полночь 15 public Time3() 16 { 17 SetTime (0, 0, 0); 18 } 19 20 // Конструктор Time3: представлены часы; минуты и секунды 21 //по умолчанию установлены в 0 22 public Time3(int hour) 23 { 24 SetTime(hour, 0, 0); 25 ) 26
Объектно-ориентированное программирование 139 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 // Конструктор ТлгаеЗ: представлены часы и минуты; секунды //по умолчанию установлены в 0 • public Time3(int hour, int minute) { SetTime(hour, minute, 0); } // Конструктор Time3: представлены часы, минуты и секунды public Time3(int hour, int minute, int second) ( SetTime (hour, minute, second); 11 Конструктор Time3: инициализация с помощью другого объекта Time3 public Time3(Time3 time) { SetTime(time.Hour, time .Minute, time.Second); ) // Установка нового значения времени в 24-часовом формате. // Проверка корректности данных. Обнуление недопустимых значений, public void SetTime( int hourValue, int minuteValue, int secondValue) ( Hour = hourValue; // активизация набора свойств Hour Minute = minuteValue; // активизация набора свойств Minute Second = secondValue; // активизация набора свойств Second } // свойство Hour public int Hour { get { return hour; } set { hour = ((value >= 0 && value < 24) ? value : 0); } } // конец свойства Hour // свойство Minute public int Minute { get ( return minute; ) set { minute =((value >= 0 && value < 60) ? value : 0); } } // конец свойства Minute // свойство Second public int Second { get
140 Глава 5 90 { 91 return second; 92 } 93 94 set 95 { 96 second = ((value >= 0 && value < 60) ? value : 0); 97 } 98 99 } II конец свойства Second 100 101 // преобразование времени в строку 24-часового формата 102 public string ToUniversalString() 103 { 104 return String.Format( 105 ”{0:D2}:{1:D2}:{2:D2}", Hour, Minute, Second); 106 } 107 / 108 // преобразование времени в строку 12-часового формата 109 public string ToStandardStringO 110 { 111 return String.Format("{0):(1:D2):{2:D2} {3}", 112 ((Hour = 12 || Hour =0) ? 12 : Hour % 12), 113 Minute, Second, (hour < 12 ? "AM" : "EM")); 1'14 } 115 116 } II конец класса Time3 В строках 57—69, 72—84 и 87—99 определяются свойства Hour, Minute и Second класса Time3 соответственно. Каждое свойство начинается со строки объявления, включающей в себя модификатор доступа к свойству (public), тип (int) и имя (Hour, Minute или Second). Тело каждого свойства содержит процедуры доступа get и set, объявленные с помощью зарезервированных слов get и set. Объявления процедуры доступа get представлены в строках 59—62, 74—77 и 89—92. Эти сред- ства доступа возвращают значение переменных экземпляра hour, minute и second соответственно, запрашивае- мые объектом. Процедуры доступа set объявлены в строках 64—67, 79—82 и 94—97. Тело каждой процедуры доступа set выполняет тот же условный оператор, который ранее выполнялся методом SetTime для установки hour, minute ИЛИ second. Метод SetTime (строки 48—54) теперь использует свойства Hour, Minute и Second для подтверждения, что пере- менные экземпляра hour, minute и second имеют допустимые значения. После определения свойства его можно применять так же, как и переменную. Значения присваиваются свойствам с помощью оператора присваивания (=). При появлении этого оператора выполняется код для данного свойства в процедуре доступа set. Зарезервированное слово value представляет аргумент к процедуре доступа set. Точно так же методы ToUniversalString (строки 102—106) и ToStandardString (строки 109—114) теперь используют свойства Hour, Minute и second для получения значений переменных экземпляра hour, minute и second соответственно. Ссылка на конкретное свойство выполняет процедуру доступа get для данного свойства. При использовании методов доступа set и get в конструкторах и других методах класса Time3 сводится к ми- нимуму количество изменений, которые нужно внести в определение класса на случай, если представление дан- ных из hour, minute и second будет изменено на другое (например, на общее количество истекших секунд за день). Когда вносятся такие изменения, необходимо только представить тела новых процедур доступа set и get. Использование данной методики также позволяет программистам менять реализацию класса, не затрагивая кли- ентов этого класса (до тех пор, пока public-методы класса по-прежнему вызываются тем же способом). Замечание по технологии программирования________________________________________________________ Дрступ к private-данным через процедуры set и get не только защищает переменные экземпляра от получения недопустимых значений, но и скрывает внутреннее представление переменных экземпляра от клиентов класса. Следовательно, если меняется представление данных (как правило, для сокращения объема сохранения или для повышения производительности), то изменить потребуется только реализации метода, реализации клиента менять не нужно, пока неприкосновенен имеющийся интерфейс. % Класс TimeTest3 (листинг 5.7) определяет приложение с графическим интерфейсом для манипуляций объектом класса типеЗ.
Объектно-ориентированное программирование 141 Примечание___________________________________________________________________________ Код сгенерированный Windows Form Designer из Visual Studio .NET, не показан. Вместо этого в строке 45 имеет- ся комментарий, указывающий на место появления сгенерированного кода в файле исходного кода. Этот код можно скачать со страницы Downloads/Resources на Web-сайте www.deitel.com. 1 // Листинг 5.7: TimeTest3.cs 2 // Демонстрация свойств Time3 — Hour, Minute и Second 3 4 using System; 5 using System.Drawing 6 using System.Collections; 7 using System.ComponentModel; 8 using System.Windows.Forms; 9 using System.Data; 10 11 // определение класса TimeTest3 12 public class TimeTest3 : System.Windows.Forms.Form 13 { 14 private System.Windows.Forms.Label hourLabel; 15 private System.Windows.Forms.TextBox hourTextBox; 16 private System.Windows.Forms.Button hourButton; 17 18 private System.Windows.Forms.Label minuteLabel; 19 private System.Windows.Forms.TextBox minuteTextBox; 20 private System.Windows.Forms.Button minuteButton; 21 22 private Systern.Windows.Forms.Label secondLabel; 23 private Systern.Windows.Forms.TextBox secondTextBox; 24 private System.Windows.Forms.Button secondButton; 25 26 private System.Windows.Forms.Button addButton; 27 28 private Systern.Windows.Forms.Label displayLabell; 29 private System.Windows.Forms.Label displayLabel2; 30 31 // требуется переменная проектировщика 32 private System.ComponentModel.Container components = null; 33 34 private Time3 time; 35 36 public TimeTest3() 37 { 38 // необходимо для поддержки Windows Form Designer 39 InitializeComponent(); 40 41 time = new Time3(); 4 2 UpdateDisplay(J; 43 ) 44 45 // код, сгенерированный Visual Studio .NET 46 47 // основная точка входа для приложения 48 [STAThread] 49 static void Main() 50 { 51 Application.Run(new TimeTest3() >; 52 } 53 54 // обновление меток отображения 55 public void UpdateDisplay()
142 Гпава 5 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 { displayLabell.Text = "Hour: " + time.Hour + Minute: " + time.Minute + ’*; Second: " + time.Second + displayLabe!2.Text = "Standard time: " + time.ToStandardStringO + "\nUniversal time: " + time.ToUniversalstring(); } // установка свойства Hour при нажатии hourButton private void hourButton_Click( object sender, System.EventArgs e) { time.Hour = Int32.Parse(hourTextBox.Text); hourTextBox.Text = UpdateDisplay(); } // установка свойства Minute при нажатии minuteButton private void minuteButton_Click( object sender, System.Event Args e) { time.Minute = Int32.Parse(minuteTextBox.Text); minuteTextBox.Text = UpdateDisplay(); ) // установка свойства Second при нажатии secondButton private void secondButton_Click( object sender, System.EventArgs e) , { time.Second = Int32.Parse(secondTextBox.Text); secondTextBox.Text = UpdateDisplay(); } // добавить единицу Second при нажатии addButton private void addButton_Click( object sender, System.EventArgs e) { time.Second = (time.Second +1) % 60; if (time.Second = 0) { time.Minute = (time.Minute +1) % 60; if (time.Minute — 0) time.Hour = (time.Hour +1) % 24; ) UpdateDisplay(); } // конец метода addButton_Click } // конец класса TimeTest3 Результат работы программы представлен на рис. 5.4. В строке 34 объявляется ссылка time класса Time3. В строке 41 в конструкторе создается объект класса Time3 и присваивается его значение time. GUI содержит три текстовых поля, и которые пользователь может ввести зна- чения для переменных hour, minute и second, соответственно, объекта Time3. Рядом с каждым текстовым полем расположена кнопка, которую пользователь может нажать для установки значения конкретного свойства Time3. В строках 66—90 объявляются три метода обработки для событий Click кнопок. Каждый обработчик событий предупреждает значение свойства Time3 (Hour, Minute или Second). GUI также имеет кнопку, позволяющую уве-
Объектно-ориентированное программирование 143 личивать значение second на 1. Используя свойства объекта Time3, метод addButton_ciick (строки 93—108) определяет и устанавливает новое время. Например, когда пользователь нажимает эту кнопку, 23:59:59 пре- вращается в 00:00:00. Результатом каждого изменения значения времени становится вызов updateDisplay, ис- пользующего свойства Time3 для отображения значений hour, minute и second, а также представление времени в универсальном и стандартном форматах. б д ж Рис. 5.4. Демонстрация свойств класса Time3: а — ввод часов; б — установка часов; в — ввод минут; а — установка минут; д — ввод секунд; е — установка секунд; ж — нажатие кнопки Add 1 to Second; з — результат после добавления 1 секунды Свойства не ограничиваются оценкой данных типа private; свойства также можно использовать для расчета значений, относящихся к объекту. Например, рассмотрим объект student со свойством, представляющим GPA (grade point average, средний балл) студента (свойство называется gpa). Программист может представить код, рассчитывающий GPA студента в процедуре доступа get, чтобы это свойство просто возвратило переменную рх ivate, содержащую рассчитанный GPA, называемый gpa. (Значение р этой переменной потребуется, рассчи- тать каким-либо другим способом, например, с помощью метода CalculateGPA.) Программисты могут пользо- ваться любой методикой, но мы рекомендуем применение свойства, рассчитывающего GPA. Помните, что код клиента не нужен для сообщения объекту student, когда рассчитывать GPA. Код клиента просто должен ис- пользовать свойство gpa. Базовая реализация должна быть скрыта от клиента. 5.8. Композиция: ссылки на объекты как переменные экземпляра других классов Во многих ситуациях гораздо удобнее ссылаться на существующие объекты, нежели переписывать код объектов для новых классов в каждом новом проекте. Предположим, что следует выполнить реализацию объекта Alarmclock (будильник), которому нужно знать, когда ему подавать сигнал. В этом случае будет проще сделать
144 Гпава 5 ссылку на существующий объект Time (подобный упоминавшимся в примерах данной главы выше), чем писать новый объект Time. Использование ссылок на объекты уже существующих классов в качестве элементов новых объектов называется композицией (или агрегированием). < Замечание по технологии программирования___________________________________________________ Одной из форм многократного использования программных средств является агрегирование, где класс имеет члены, делающие ссылки на объекты других классов. В приложениях листингов 5.8—5.10 демонстрируется принцип композиции. Программа содержит три класса. Класс Date (листинг 5.8) инкапсулирует информацию, относящуюся к конкретной дате. Класс Employee (лис- тинг 5.9) инкапсулирует имя служащего и два объекта Date, обозначающие день рождения служащего и дату его приема на работу. Класс CompbsitionTest (листинг 5.10) создает объект класса Employee для демонстрации со- ставлений. Класс Date объявляет переменные экземпляра int — month, day и year (строки 9—11). В строках 16—32 опре- деляется конструктор, принимающий значения month, day и year в качестве аргументов и присваивает эти зна- чения переменным экземпляра, убедившись, что эти значения находятся в согласованном состоянии. Обратите внимание, что строки 25—26 выводят сообщение об ошибке, если конструктор получает недопустимое значе- ние month. Как правило, вместо вывода сообщений об ошибках конструкторы "выбрасывают исключение". Ис- ключения рассматриваются в главе 8. Методом ToDateString (строки 58—61) возвращается строковое пред- ставление Date. 1 . // Листинг 5.8: Date.cs 2 // Определение класса Date инкапсулирует месяц, день и год 3 4 using System; 5 б // определение класса Date 7 public class Date 8 { 9 private int month; // 1-12 10 private int day; // 1-31 (в зависимости от месяца) 11 private int year; // любой год 12 13 // Конструктор подтверждает допустимое значение для месяца; 14 // вызов метода CheckDay для подтверждения корректности 15 // -значения дня 16 public Date(int theMonth, int theDay, int theYear) j 17 { 18 // подтверждение корректности месяца 19 if (theMonth > 0 && theMonth <= 12) 20 month = theMonth; 21 22 else 23 { 24 month = 1; 25 Console.WriteLine( 26 "Month {0} invalid. Set to month 1.", theMonth); 27 } 28 29 year = theYear; // можно подтвердить корректность года 30 day = CheckDay(theDay); // подтверждение корректности дня 31 32 } // конец конструктора Date 33 34 // утилитный метод подтверждает корректность значения дня 35 // на основании месяца и года 36 private int CheckDay(int testDay) 37 { 38 int[] dayPerMonth - 39 { 0, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 }; 40
145 Объектно-ориентированное программирование 41 // проверка дня в диапазоне месяца 42 if (testDay > 0 && testDay <= daysPerMonth[mont]) 43 return testDay; 44 45 // проверка високосного года 46 if (month == 2 && testDay == 29 && 47 (year % 400 == 0 I I 48 (year % 4 == 0 && year % 100 != 0))) 49 return testDay; 50 51 Console.WriteLine( 52 "Day {0} invalid. Set to day 1. ", testDay); 53 54 return 1; // оставить объект в согласованном состоянии 55 } 56 57 // возвратить строку даты как месяц/день/год 58 public string ToDateString() 59 { 60 return month + "/" + day + "/" + year; 61 } 62 63 } // конец класса Date В классе Employee (листинг 5.9) инкапсулируется информация, относящаяся ко дню рождения служащего и дате его приема на работу (строки 10—13) с помощью переменных экземпляра firstName, lastName, birthDate и hireDate. Элементы birthDate и hireDate являются ссылками на объекты Date, каждый из которых содержит переменные экземпляра month, day и year. В этом случае класс Employee составлен из двух ссылок типа string и двух ссылок класса Date. Конструктор Employee (строки 16—27) принимает восемь аргументов (first, last, birthMonth, birthDay, birthYear, hireMonth, hireDay и hireYear). В строке 24 аргументы birthMonth, birthDay и birthYear передаются в конструктор Date для создания объекта birthDate. Подобным же образом, в строке 25 аргументы hireMonth, hireDay и hireYear передаются в конструктор Date для создания объекта hireDate. Мето- дом ToEmployeestring (строки 30—35) возвращается строка, содержащая имя Employee и строковое представ- ление birthDate И hireDate Employee. -----“»Г"•gif-p-ir-........- - — - “ -----* "Листинг 5.9. Класс Employee инкапсулирует имя, дату рождения служащего и дату его приема на работу 1 // Листинг 5.9: Enployee.cs 2 // Определение класса Employee инкапсулирует имя, фамилию, 3 // дату рождения служащего и дату его приема на работу 4 5 using System; 6 7 // Определение класса Employee 8 public class Employee 9 { 10 private string firstName; 11 private string lastName; 12 private Date birthDate; // ссылка на объект Date 13 private Date hireDate; ' // ссылка на. объект Date 14 v 15 // конструктор инициирует имя, даты рождения и приема на работу 16 public Employee(string first, string last, 17 int birthMonth, int birthDay, int birthYear, 18 int hireMonth, int hjireDay, int hireYear) 19 < 20 firstName = first; 21 lastName = last; 22 10 Зак. 3333
146 Глава 5 23 // создание новых объектов Date 24 birthDate = new Date(birthMonth, birthDay, birtbYear); 25 hireDate = new Date(hireMonth, hireDay, hireYear); 26 27 } // конец конструктора Employee 28 29 // преобразование Employee в строковый формат 30 public string ToEmployeeString() 31 { 32 return lastName + + firstName + 33 " Hired: " + hireDate.ToDateStringO + 34 " Birthday: " + birthDate.ToDateString() 35 ) 36 37 ) // конец класса Employee Класс CompositionTest (листинг 5.10) выполняет приложение с методом Main. В строках 13 и 14 создается объ- ект Employee, а в строках 16—17 отображается строковое представление Employee. --Т----- [ Листинг 5.10. Демонстрация композиции 1 1 // Листинг 5.10: ConpositionTest.cs 2 // Демонстрация объекта с ссылкой на элемент объекта 3 4 using System; 5 using System.Windows.Forms; 6 7 // определение класса Composition 8 class CompositionTest 9 { 10 // главная точка входа для приложения 11 static void Main(string[] args) 12 { 13 Employee e = 14 new Employee("Bob”, "Jones", 7, 24, 1949, 3, 12, 1988); 15 16 MessageBox.Show(e.ToEmployeeString(), 17 "Testing Class Employee"); 18 19 } // конец метода Main 20 21 } // конец класса CompositionTest Рис. 5.5. Демонстрация композиции Результат работы приложения представлен на рис. 5.5. 5.9. Использование ссылки this Каждый объект может получить доступ к ссылке на самого себя, называемой ссылкой this. Ссылка this может неявно ссылаться на переменные экзем пляра, свойства и методы объекта. Ключевое слово this обычно исполь- зуется в рамках методов, где this — ссылка на объект, на котором метод выполняет операции. В Windows- приложении в листинге 4.1 представлено несколько вариантов использования this в методе InitializeComponent. Приложение использует ключевое слово this для ссылки на инициализируемую (созда- ваемую) форму. Каждая форма имеет метод InitializeComponent, поэтому ссылка this обеспечивает разработ- чика простым способом доступа к информации в текущем объекте. Дополнительные примеры this представле- ны в главах 9 и 10. Теперь продемонстрируем неявное и явное использование ссылки this для отображения private-данных объек- та Time4. Класс Time4 (листинг 5.11) определяет три переменные private-экземпляра— hour, minute и second (строки 9—11). Конструктор (строки 14—19) принимает три аргумента int для инициализации объекта Time4. Обратите внимание, что для данного примера указаны имена параметров конструктора (строка 14), идентичные именам переменных экземпляра для класса (строки 9—11). Это сделано для иллюстрации явного использования ссылки this. Если метод содержит локальную переменную с тем же именем, что и переменная экземпляра клас-
Объектно-ориентированное программирование_______. ,, ._________________________________________М7 са метода, тогда метод будет обращаться к локальной переменной, а не к переменной экземпляра (т. е. локаль- ная переменная скрывает переменную экземпляра в области действия метода). Однако метод может использо- вать ссылку this для явного обращения к скрытым переменным экземпляра (строки 16—18). Метод Buildstring (строки 22—27) возвращает строку, созданную оператором, использующим ссылку this явно и неявно. В строке 25 явно используется ссылка this для обращения к методу ToStandardString, тогда как в строке 26 неявно применяется ссылка this для обращения к тому же методу. Следует обратить внимание, что обе строки решают одну и ту же задачу. Следовательно, программисты, как правило, не используют ссылку this явно для ссылки на методы в рамках текущего объекта. Совет по тестированию и отладке_____________________________________________________________ Не, используйте имен метода-параметра (или имен локальных переменных), конфликтующих с именами пере- менных экземпляра, во избежание появления сложных для отслеживания ошибок. 1 // Листинг 5.11: Time4.cs 2 // Класс Time4 демонстрирует ссылку this 3 4 using System; 5 б // определение класса Time4 7 public class Time4 8 { 9 private int hour-; // 0-23 10 private int minute; // 0-59 11 private int second; // 0-59 12 13 // конструктор 14 public Time4(int hour, int minute, int second) 15 { 16 this.hour = hour; 17 this.minute = minute; 18 this.second = second; 19 } 20 21 // создание строки с помощью this и неявных ссылок 22 public string BuildString() 23 { 24 return "this.ToStandardString() : " + 25 this.ToStandardString() + 26 "XnToStandardStringO : " + ToStandardStringO; 27 } 28 29 // преобразовать время в строку 12-часового формата 30 public string ToStandardStringO 31 { 32 return String.Format("{0}:{1:D2}:(2:D2) {3}", 33 ((this.hour == 12 I I this.hour « 0) ? 12 : 34 this.hour % 12), this.minute, thisisecond, 35 (this.hour < 12 ? "AM" : "PM")); 36 } 37 38 ) // конец класса Time4 Распространенная ошибка программирования_____________________________________________ Для метода, в котором параметр (или локальная переменная) имеют то же имя, что и переменная экземпляра, используйте ссылку this, если необходимо получить доступ к переменной экземпляра; в противном случае ссылка будет сделана на параметр метода (или локальной переменной). Класс ThisTest (листинг 5.12) запускает приложение, демонстрирующее явное использование ссылки this. В строке 13 создается экземпляр класса Time4. В строках 15—16 активизируется метод Buildstring объекта Time4 и результаты отображаются в окне сообщения MessageBox.
148 Гпава 5 1 // Листинг 5.12: ThisTest.cs 2 // Использование ссыпки this 3 4 using System; 5 using System.Window.Forms; 6 7 // определение класса ThisTest 8 class ThisTest 9 { 10 // главная точка входа для приложения 11 static void Main(string[] args) 12 { 13 Time4 time = new Time4(12, 30, 19); 14 15 MessageBox. Show(time.Buildstring(), 16 "Demonstrating the \"this\" Referrence"); 17 } 18 } Рис. 5.6. Демонстрация ссылки this Результат работы программы представлен на рис. 5.6. О хорошем стиле программирования_____________________________________________________ Явное использование ссылки this может сделать программу более понятной в некоторых контекстах, где ис- пользование this необязательно. Проблему параметров (или локальных переменных), скрывающих переменные экземпляра, можно решить с помощью свойств. При наличии свойства Hour, имеющего доступ к переменной экземпляра hour, нет необхо- димости использования this.hour для различения параметра (или локальной переменной) hour и переменной экземпляра hour; в этом случае hour просто присваивается Hour. 5.10. "Сбор мусора" В предыдущих примерах читатели видели, как конструктор инициализирует данные в объекте класса после соз- дания объекта. Оператор new распределяет память для объекта, после чего вызывает конструктор этого объекта. Впоследствии конструктор может получить другие системные ресурсы, например, подключение к сети, базы данных или файлы. Объекты должны организованно освобождать ресурсы памяти и прочие после того, как программа заканчивает использование этих объектов. Если ресурсы не освобождаются вовремя, то имеют место так называемые утеч- ки, потенциально истощающие набор доступных ресурсов, которые могут потребоваться программе для даль- нейшего выполнения. В отличие от языков С и C++, где программисты открыто управляют памятью, C# осуществляет внутреннее управление памятью. .NET Framework осуществляет "сбор мусора" памяти, необходимости в которой уже нет, для ее возврата в системную память. При запуске "сборщик мусора" идентифицирует объекты, на которые при- ложение не имеет ссылок. Такие объекты собираются сразу или при последующей работе сборщика. Таким об- разом, утечки памяти, представляющие собой обычное явление в таких языках, как С и C++, где память не вос- станавливается во время прогона, довольно редки в С#. Распределение и освобождение других ресурсов, например, сетевых подключений, подключений к базам дан- ных и файлам, программист должен осуществлять явно. Одной из методик, применяемых для управления этими ресурсами (совместно со "сборщиком мусора"), является определение деструктора (иногда называется фина- лизатором) возвращающего ресурсы в систему. "Сборщик мусора" вызывает деструктор объекта для выполне- ния прерывающего обслуживания этого объекта непосредственно перед тем, как сборщик восстановит память объекта (этот шаг называется финализацией). Каждый класс может содержать только один деструктор. Имя деструктора образуется вводом символа - перед именем класса. Например, деструктор для класса Time будет иметь имя -Timeо. Деструкторы не принимают аргументов, поэтому они не могут перегружаться. Когда "сборщик мусора" удаляет объект из памяти, сборщик сначала активизирует деструктор этого объекта и освобождает ресурсы, использованные классом. Однако нель-
Объектно-ориентированное программирование 149 зя с точностью определить, когда вызывается деструктор, потому что нельзя точно сказать, когда^ начнется "сбор мусора". При прекращении работы программы все объекты, для которых ранее "сбор мусора" не прово- дился, получат вызовы деструктора. 5.11. Staf/c-члены класса Каждый объект класса имеет собственную копию всех переменных экземпляра класса. Однако в некоторых слу- чаях все объекты класса совместно используют только одну копию той или иной переменной. Такие перемен- ные называются статическими (static) переменными. Программа хранит в памяти только одну копию каждой static-переменной класса, независимо от того, сколько объектов класса создавалось. Переменная static пред- ставляет информацию класса; все объекты класса совместно используют один и тот же элемент static-данных. Объявление static-члена начинается с ключевого слова static. Переменная static может быть инициализиро- вана в ее объявлении, если за именем переменной следует оператор = и первоначальное значение. В случаях, когда static-переменная требует более сложной инициализации, программист может определить static- конструктор для инициализации только членов static. Такие конструкторы необязательны, и их нужно объяв- лять ключевым словом static, за которым должно следовать имя класса. Конструкторы static вызываются до использования любых static-членов и до создания любых объектов класса. Рассмотрим *пример видеоигры, обосновывающий необходимость static-данных класса. Предположим, что имеется видеоигра, в которой участвуют Martian (марсиане) и другие космические пришельцы. Каждый объект Martian — храбрый воин, желающий нападать на других космических существ, будучи уверенным в том, что поблизости находятся еще, как минимум, четверо Martian. Если присутствует менее пяти Martian, то каждый из них автоматически становится трусом. По этой причине каждый объект Martian должен знать martianCount. Класс Martian можно снабдить martianCount в качестве данных экземпляра. Однако если это сделать, тогда каждый Martian получит отдельную копию данных экземпляра, и при каждом новом создании Martian придется обновлять переменную экземпляра martianCount в каждом Martian. Лишние копии занимают место, и обновле- ние этих копий требует времени. Вместо этого объявляется, что martianCount — static с тем, чтобы перемен- ная martianCount стала общими данными класса. Каждый объект Martian может рассматривать martianCount так, будто это — данные экземпляра этого Martian, но C# поддерживает только одну копию static-переменной martianCount, что сэкономит свободное пространство. Такая методика также экономит время; благодаря нали- чию только одной копии, отпадает необходимость увеличения отдельных копий martianCount для каждого объ- екта Martian. Совет по повышению производительности___________________________________________________ Если достаточно одной копии части данных, используйте static-переменные для экономии памяти. Несмотря на то, что static-переменные в других языках программирования могут показаться глобальными пе- ременными (переменными, ссылки на которые Moiyr быть сделаны в любой части программы), в глобальном доступе к static-переменным необходимости нет; переменные static имеют область действия класса. Доступ к public static-членам класса осуществляется через имя класса с помощью точечного оператора (на- пример, Math.pi). Доступ к private static-членам производится только через методы или свойства класса. static-члены доступны сразу, как только класс за!ружается в память во время выполнения программы, и они существуют в течение всего времени выполнения, даже в отсутствие каких бы то ни было объектов этого клас- са. Для того чтобы программа могла получить доступ к члену private static ъ отсутствие объектов класса, по- следний должен обеспечить метод public static или свойство. Метод static не может получить доступ к членам экземпляра (нестатическим). В отличие от метода экземпля- ра, static-метод не имеет ссылки this, потому что static-переменные и static-методы существуют независи- мо от каких бы то ни было объектов классов, даже когда класс не имеет объектов. Распространенная ошибка программирования________________________________________________ Использование ссылки this в static-методе или static-свойстве является ошибкой компиляции. Распространенная ошибка программирования__________________________________________ Обращение к методу экземпляра или попытка доступа к переменной экземпляра из static-метода является ошибкой компиляции. Класс Employee (листинг 5.13) демонстрирует свойство public static, позволяющее программе получить зна- чение переменной private static, static-переменная count (строка 11) не инициализируется явно, поэтому по умолчанию она получает значение 0. Переменная класса count поддерживает подсчет количества объектов
150 Гпава 5 класса Employee, включая объекты, уже отмеченные как "кандидаты" на выброс, но еще не обработанные "сборщиком мусора". Если существуют объекты класса Employee, элемент count типа static можно использовать в любом методе объекта Employee; в данном примере конструктор (строки 14—23) увеличивает count, а деструктор (строки 26— 32) уменьшает count. Если в классе Employee объектов не существует, тогда значение элемента count можно получить через static-свойство Count (строки 53—39); эта методика также работает при наличии в памяти объ- ектов Employee. 1 // Листинг 5.13: Employee.cs 2 // Класс Employee содержит static-данные и static-метод 3 4 using System; 5 6 // определение класса Employee 7 public class Employee 8 { 9 private string firstName; 10 private string lastName; 11 private string int count; // объекты Employee в памяти 12 13 // конструктор наращивает счетчик count static Employee 14 public Employee(string fname, string IName) 15 { 16 firstName = fName; 17 lastName = IName; .18 19 ++ count 20 21 Console.WriteLine("Employee object constructor: " + 22 firstName + + lastName + "; count = " + Count); 23 } 24 25 // деструктор уменьшает счетчик count static Employee 26 -Employee() 27 { 28 —count; 29 30 Console.WriteLine("Employee bbject destructor: " + 31 firstName + " " + lastName,+ "; count = " + Count); 32 ' } 33 34 // свойство firstName 35 public string FirstName 36 ( 37 get 38 { 39 return firstName; 40 } 41 } 42 43 // свойство lastName 44 public string LastName 45 { 46 get 47 { 48 return LastName; 49 ) 50 }
Объектно-ориентированное программирование 151 51 52 // свойство static Count 53 public static int Count 54 { 55 get 56 { 57 return count; 58 } 59 } 60 61 } // конец класса Employee Класс StaticTest (листинг 5.14) запускает приложение, демонстрирующее static-члены класса Employee (лис- тинг 5.13). В строках 12 и 13 используется static-свойство Count класса Employee для получения текущего зна- чения count до того, как программа создаст объекты Employee. Обратите внимание на синтаксис, используемый для доступа к static-члену: ClassName. Sta ticMember В строке 13 ClassName— Employee, a staticMember— Count. Вспомните, что такой синтаксис использовался в примерах ранее для вызова static-методов класса Math (например, Math.Pow и Math.Abs) и других методов, таких как Int32.Parse и MessageBox.Show. Затем, строки 16 и 17 создают два объекта Employee и присваивают их ссылкам employeel и employee2. Каждый вызов конструктора Employee наращивает значение count на единицу. В строках 19—26 отображается значение Count, а также имена двух служащих. В строках 30 и 31 устанавливаются ссылки employeel и епр1оуее2 на null, поэтому они больше не относятся (не ссылаются) к объектам Employee. Поскольку эти ссылки были единствен- ными в программе ссылками на объекты Employee, теперь к ним можно применить "сборщик мусора". г Листинг 5.14. Демонстрация 1 // Листинг 5.14: StaticTest.cs 2 // Демонстрация static-членов класса 3 4 using System; 5 6 // определение класса StaticTest 7 class StaticTest 8 { 9 // главная точка входа для приложения 10 static void Main(string[] args) 11 { 12 Console.WriteLine("Employees before instantiation: " + 13 Employee.Count + "\n"); 14 15 // создание двух классов Employee 16 Employee employeel = new Employee("Susan", "Baker"); 17 Employee employee2 = new Employee("Bob", "Jones"); 18 19 Console. WriteLine ("\nErtployees after instantiation: " + 20 Employee.Count ® " + Employee.Count + "\n"); 21 22 // отображение классов Employees 23 Console.WriteLine("Employee 1: " + 24 employeel.FirstName + " " + employeel.LastName + 25 "\nEmployee 2: ” + employee2.FirstName + 26 " " + employee2.LastName + "\n"); 27 28 // удаление ссылок на объекты для указания, что 29 // объекты можно удалить . "сборщиком мусора" 30 employeel » null; 31 employee2 « null; 32
152 Глава 5 33 // принудительный сбор мусора 34 System.GC.Collect(); 35 36 // ожидание завершения сбора 37 System.GC.WaitForPendingFinalizers(); 3& 3 9 Console.WriteLine( 40 "\nEmployees after garbage collection: " + 41 Employee.Count); 42 } 43 } Результат работы программы: Employees before instantiation: 0 Employee object constructor: Susan Baker; count = 1 Employee object constructor: Bob Jones; count = 2 Employees after instantiation: Employee.Count = 2 Employee 1: Susan Baker Employee 2: Bob Jones Employee object destructor: Bob Jones; count = 1 Employee object constructor: Susan Baker; count = 0 Employees after garbage collection: 0 "Сборщик мусора” не активизируется непосредственно программой. Сборщик освобождает память для объек- тов, когда во время прогона принимается решение о его использовании, либо когда операционная система вос- станавливает память при прекращении работы программы. Впрочем, существует возможность запроса "сбор- щика мусора" о попытке собрать все доступные объекты. В строке 34 используется public static-метод Collect из класса GC (пространство имен System) для такого запроса. Сборщик не гарантированно соберет все доступные иля сбора объекты. Если сборщик принимает решение о начале своей деятельности, он сперва акти- визирует деструктор каждого объекта. Важно понимать, что сборщик выполняется как независимая единица, называемая потоком. (Потоки рассматриваются в главе 11.) Множественные потоки возможно выполнять па- раллельно на многопроцессорной системе, либо совместно использовать процессор — на системе с одним про- цессором. Таким образом, программа будет выполняться параллельно с процессом "сбора мусора". По этой причине вызывается static-метод WaitForPendingFinalizers класса GC (строка 37), который вынуждает про- грамму дождаться вызова сборщиком деструкторов всех объектов, готовых для уничтожения и восстановления этих объектов. Когда программа достигает строки 41, можно быть уверенным, что вызовы деструкторов завер- шены, и значение count было соответствующим образом уменьшено на единицу. В данном примере вывод показывает, что для каждого Employee был вызван деструктор, после чего значение count уменьшилось на 2 (по единице на каждого собираемого Employee). В строках 39—41 используется свой- ство count для получения значения count после активизации сборщика мусора. Если объекты не были собраны, тогда значение count было бы больше нуля. Ближе к концу выходных данных обратите внимание, что объект Employee для Bob Jones был закончен (финали- зирован) до объекта Employee для Susan Baker. Впрочем, выходные данные этой программы при запуске на дру- гой системе могут отличаться от полученного в примере, потому что нельзя гарантировать, что сборщик собе- рет объекты в заданном порядке. 5.12. Члены класса const и readonly Язык C# дает программистам возможность создавать константы, значения которых не могут изменяться во время выполнения программы. Совет по тестированию и отладке________________________________________________ Если значение переменной никогда не должно изменяться, сделайте ее константной. Такая практика помогает избежать ошибок, которые могут иметь место, если значение переменной действительно нужно было изменить.
Объектно-ориентированное программирование 153 Для создания постоянного элемента (объекта) данных объявите этот элемент либо ключевым словом const, ли- бо readonly. Объекты данных, объявленные как const, неявно являются static, и при объявлении их необходи- мо инициализировать. Объекты данных, объявленные как readonly, могут быть инициализированы в их объяв- лении или в конструкторе их класса. Как только значение const или readonly инициализированы, изменять их нельзя, хотя объектам данных readonly можно присваивать значения в нескольких конструкторах (когда объект инициализирован, вызван будет только один конструктор).’ Распространенная ошибка программирования_______________________________________________ Объявление члена класса как const, без инициализации его в объявлении этого класса является синтаксиче- ской ошибкой. Распространенная ошибка программирования______________________________________________ Присвоение значения const-члену после того, как этот элемент инициализирован, является ошибкой компи- ляции. Распространенная ошибка программирования______________________________________________ Объявление const-члена как static является синтаксической ошибкой, потому что элемент const неявно все- гда static. Распространенная ошибка программирования________________________________________________ Объявление члена класса как readonly и попытка использовать его до инициализации является логической ошибкой. Членам класса, объявленным как const, значения должны присваиваться во время компиляции. Следовательно, элементы const могут быть инициализированы только с другими постоянными значениями, например, целыми числами, строковыми литералами, символами и другими элементами const. Постоянные элементы со значения- ми, которые не могут быть определены во время компиляции, должны объявляться с ключевым словом readonly. Ранее отмечалось, что элементу readpnly значение может быть присвоено только один раз, либо при его объявлении, либо в рамках конструктора класса. При инициализации элемента readonly типа static в кон- структоре необходимо использовать конструктор static. В листинге 5.15 представлены константы. Программа состоит из двух классов: класса constants (строки 8—22), определяющего две константы, и класса UsingConstAndReadOnly (строки 25—43), демонстрирующего константы в классе constants. Листинг 5.15. Демонстрация членов класса const и readonly .а*. 1 // Листинг 5.15: UsingConstAndReadOnly.cs '2 // Демонстрация постоянных значений с const и readonly 3 4 using System; 5 using System.Windows.Forms; 6 7 // определение класса Constants 8 public class Constants 9 { 10 // создание константы PI 11 public const double PI = 3.14159 12 13 // радиус — константу, 14 //то есть не инициализирован 15 public readonly int radius; 16 17 public Constants(int radiusValue) 18 { 19 radius = radiusValue; 20 ) 21 22 } // конец класса Constants 23 24 // определение класса UsingConstAndReadOnly 25 public class UsingConstAndReadOnly
154 Глава 5 26 { 27 // метод Main создает объект Constants 28 //и отображает его значение 29 static void Main(string[] args) 30 { 31 Random random = new RandomO; 32 33 Constants constantvalues = 34 new Constants(random.Next(1, 20)); 35 36 MessageBox.Show("Radius = " + constantvalues.radius + 37 "\nCircumference * ’’ + 38 2 * Constants.PI * constantvalues.radius, 39 "Circumference"); 40 41 } // конец метода Main 42 43 } // конец класса UsingConstAndReadOnly В строке 11 в классе Constants создается константа pi с помощью ключевого слова const и инициализирует pi значением double — 3.14159 — приблизительным значением числа л, которое программа использует для расче- та длин окружностей. Заметьте, что в качестве значения можно было воспользоваться заранее заданной кон- стантой pi класса Math (Math.pi), однако мы хотели продемонстрировать процесс явного определения элемента данных const. Компилятор должен быть способен определить значение элемента const во время компиляции; в противном случае будет иметь место ошибка компиляции. Например, если строка 11 инициализировала pi выражением Double.Parse("3.14159") то компилятор сгенерировал бы ошибку. Несмотря на то, что данном выражении в качестве аргумента исполь- зуется строковый литерал "3.14159" (постоянное значение), компилятор не может оценить во время компили- рования вызов метода Double.Parse. Переменные, объявленные как readonly, можно инициализировать во время выполнения программы. В строке 15 объявляется readonly переменную radius, но не инициализирует ее. Конструктор Constants (строки 17—20) принимает значение int и присваивает его radius, когда программа создает объект Constants. Обратите внима- ние, что радиус также можно инициализировать и более сложным выражением, например, вызовом метода, воз- вращающим int. Класс UsingConstAndReadOnly (строки 25—43) использует элементы данных const и readonly класса Constants. В строках 33 и 34 используется объект Random для генерирования произвольного int между 1 и 20 (с помощью метода next класса Random), соответствующего radius окружности, и передает это значение в конструктор constants для инициализации readonly-элемента radius. В строках 36—39 выдается радиус и длина окружно- сти в MessageBox. В строке 36 используется ссылка Constants — constantvalues для доступа к readonly- элементу radius. В строке 38 рассчитывается длина окружности с помощью const-элемента Constants.PI и readonly-элемента radius. Обратите внимание, что для доступа к PI используется синтаксис static, потому что элементы данных const неявно всегда имеют тип static. Результат работы программы представлен на рис. 5.7. Рис. 5.7. Демонстрация членов класса const и readonly а — для радиуса 2; б—для радиуса 6 5.13. Индексаторы Иногда класс инкапсулирует данные, которыми программа может манипулировать как списком элементов. Та- кой класс может определить особые свойства, называемые индексаторами, обеспечивающие индексированный доступ к списку элементов в стиле массива. При работе с "традиционными" массивами C# индекс должен быть
Объектно-ориентированное программирование 155 целым числом. Преимуществом индексаторов является то, что программист может задавать как целые, так и дробные индексаторы. Например, разработчик может позволить коду клиента манипулировать данными, ис- пользуя в качестве индексов строки, представляющие имена или описания элементов данных. Также при работе с элементами ’’традиционных" массивов в C# оператор индекса массива всегда возвращает данные одного типа, т. е. типа массива. Индексаторы более гибкие: они могут возвращать данные любого типа, даже отличающегося от типа данных в списке элементов. Распространенная ошибка программирования_____________________________________________________ Объявление static-индексаторов является синтаксической ошибкой. Несмотря на то, что оператор индексатора используется как оператор-индекс массива, индексаторы определя- ются в классе как свойства. В отличие от обычных свойств, для которых разработчик может выбрать подходя- щее имя, индексаторы должны определяться ключевым словом this. Индексаторы имеют общую форму accessModifier returnType this[IndexTypel namel, IndexType2 name2, —] { get { // использование namel, name2, ___ получение данных } set { // использование namel, name2, — для установки данных } 1 Параметры indexType, указанные в квадратных скобках, доступны для процедур доступа get и set. Эти средства доступа определяют использование индекса (или индексов) для выбора или модификации соответствующего элемента данных. Как и в случае со свойствами, get должен возвратить значение типа return Type, a set может использовать ключевое слово value Для ссылки на значение, которое должно быть присвоено элементу данных. Программа из листинга 5.16 содержит два класса: класс Box (строки 14—74) представляет собой окно с длиной, шириной и высотой, а класс indexerTest (строки 77—177) демонстрирует индексаторы класса Box. Результат работы программы представлен на рис. 5.8. ---—....у....... ... ... .. . ........... ... Листинг 5.16. Индексаторы, обеспечивающие доступ к элементам объекта 1 // Листинг 5.16: Indexer.Test.cs 2 // Индексаторы обеспечивают доступ к элементам объекта через 3 // оператор индексации 4 5 using System; б using System.Drawing; 7 using System.Collections; 8 using System.ComponentModel; 9 using System.windows.Forms; 10 using System.Data; 11 12 // Определение класса Box представляет "окно" с длиной, 13 // шириной и высотой 14 public class Box 15 { ... 16 private string!] names = { "length", "width", "height" }; 17 private doublet] dimensions = new double[3]; 18 19 // конструктор 20"- private Box (double length, double width, double height) 21 { 22 dimensions[0] = length; 23 dimensions[1] = width; 24 dimensions[2] = height; 25 }
156 Глава 5 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 30 81 82 83 84 85 86 87 88 89 // доступ к размерам по целому числу индекса public double this[int index] { get { return (index < 0 11 index >= dimensions.Length) ? -1 : dimensions[index]; set { if (index >= 0 && index < dimensions.Length) dimensions[index| - value; } } // конец числового индексатора // измерения доступа по строковым именам public double this[string name] { get { // расположение Элемента на get int i = 0; while (i < names.Length && name.ToLower () !” names[i]) i++; return (i “= names.Length) ? -1.0 : dimensions[i]; } set { // расположение элемента на set int i = 0; while (i < names.Length && name.ToLower() ! = names[i]) if (i != names.Length) dimensions[i] = value; ) ] // конец индексатора } // конец класса Box // Класс IndexerTest public class IndexerTest : System.Windows.Forms.Form { private System.Windows.Forms.Label indexLabel; private System.Windows.Forms.Label nameLabel; private System.Windows.Forms.TextBox indexTextBox; private System.Windows.Forms.TextBox valueTextBox; private System.Windows.Forms.Button nameSetButtoh; private System.Windows.Forms.Button nameGetButton; private System. Windows.Forms.Button intSetButton; private System.Windows. Forms. Button nameGetButton;
Объектно-ориентированное программирование 157 90 91 92 93 94 95 96 97 , 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140. 141 142 143 144 145 146 147 148 149 150 151 152 153 private System.Windows.Forms.TextBox resultTextBox; // переменная, требуемая для поддержки Windows Form Designer private System.ComponentModel.Container Components = null; private Box box; // конструктор public IndexerTest() { // требуется для поддержки Windows Form Designer InitializeComponent(); // создание блока box “ new Box(0.0; 0.0, 0.0); } // Код, сгенерированный Visual Studio .NET // главная точка входа для приложения (STAThread] static void Main() { Application.Run(new IndexerTest()); } // отображение значения на заданном номере индекса private void ShowValueAtIndex(string prefix, int index) ( resultTextBox.Text = prefix + "box [ " + index + •']="+ box [ index ]; } // отображение значения с указанным именем private void ShowValueAtIndex(string prefix, string name) ( resultTextBox.Text = prefix + "box [ " + index + "]« + box [ name ]; ) // очистить indexTextBox и valueTextBox private void ClearTextBoxes { indexTextBox.Text = valueTextBox. Text = } // получение значения в указанном индексе private void intGetButton_Click( object sender, System.EventArgs e) ( ShowValueAtlndex( "get: ", Int32.Parse(indexTextBox.Text)); , ClearTextBoxes(); } // задание значения в указанном индексе private void intSetButton_Click( object sender, System.EventArgs e) { int index = Int32.Parse(indexTextBox.Text); box(index] - Double.Parse(valueTextBox.text);
158 Гпава 5 154 ShowValueAtIndex("set: ", index); 155 ClearTextBoxes(); 156 } 157 158 // получение значения с указанным именем 159 private void nameGetButton_Click( 160 object sender, System.EventArgs e) 161 { 162 ShowValueAtlndexfget: ", indexTextBox.Text); 163 ClearTextBoxes(); 164 } 165 166 // задание значения с указанным именем 167 private void nameSetButton_Click( 168 object sender, System.EventArgs e) 169 { 170 box[indexTextBox.Text] = 171 Double.Parse(valueTextBox.Text); 172 173 ShowValueAtIndex("set: ”, indexTextBox.Text); 174 ClearTextBoxes(); 175 } 176 177 } // конец класса IndexerTest в г d e ж з Рис. 5.8. Демонстрация работы индексатора: а — до установки значения индекса; б— после установки значения индекса; в — до получения значения имени массива; г — после получения значения имени массива; д — до установки значения имени массива; е — после установки значения имени массива; ж — до получения значения индекса; з — после получения значения индекса Члены данных private класса Box— это строковые имена массивов (строка 16), содержащие имена (т. е. "length", "width" и "height'*) для размеров Box, и двойные размеры массива (строка 17), содержащие значения
Объектно-ориентированное программирование 159 каждого размера. Каждый элемент в массиве names соответствует элементу в массиве dimensions (например, dimensions [2] содержит значение высоты Box). > Объект Box определяет два индексатора (строки 28—42 и строки 45—72), каждый из которых возвращает двой- ное значение типа double, представляющее размер, указанный параметром индексатора. Подобно методам, ин- дексаторы могут быть перегруженными. Первый индексатор использует индекс int для манипуляций элемен- том в массиве dimensions. Второй индексатор использует индекс string, обозначающий имя размера для мани- пуляций элементом в массиве dimensions. Каждый индексатор возвращает значение -1.0, если процедура доступа get сталкивается с недопустимым индексом. Каждая процедура доступа set индексатора присваивает value соответствующему элементу dimensions, только если индекс допустимый. Обычно индексатор выдает исключительную ситуацию при получении некорректного индекса. Выбрасывание исключений рассматривается в главе 8. Обратите внимание, что индексатор string использует оператор while для поиска совпадающей строки в мас- сиве names (строки 64—66). Если совпадение найдено, индексатор выполняет обработку соответствующего эле- мента в массиве dimensions (строка 69). Класс IndexerTest— это System.Windows.Forms.Form, ВЫПОЛНЯЮЩИЙ манипуляции private-членами класса Box, через индексаторы Box. Переменная экземпляра box объявлена в строке 69 и инициализирует конструктор на строке 105 значениями размеров 0.0. Обработчик событий для кнопки Get Value by Index (строки 139—445) активизирует метод ShowValueAtindex (строки 118—122) для получения значения индекса, указанного в indexTextBox. Обработчик событий для кнопки Set Value by Index (строки 148—156) присваивает значение в valueTextBox в местоположение, указанное в indexTextBox. Обработчик событий для кнопки Get Value by Name (строки 159—164) активизирует перегруженный метод ShowValueAtindex (строки 125—129) для получе- ния значения с именем, указанным в valueTextBox. Обработчик событий для кнопки Set Value by Name (стро- ки 167—175) присваивает значение т valueTextBox в местоположение с именем, указанным в indexTextBox. 5.14. Абстракция данных и сокрытие информации Как отмечалось в начале главы, классы, как правило, скрывают от клиентов подробности своей реализации. Та- кая практика называется сокрытием информации (information hiding). В качестве примера сокрытия информа- ции рассмотрим структуру данных, называемую стеком (stack). Представьте себе, что стек — это нечто, напоминающее стопку тарелок. Каждая очередная тарелка всегда ста- вится сверху (такой процесс называется проталкиванием тарелки в стопку). Точно так же снимается со стопки всегда верхняя тарелка (этот процесс называется выталкиванием). Стеками называются структуры данных, по- строенные по принципу "последним пришел — первым обслужен" (Last Input First Output, LIFO), т. e. последний поступивший в стек элемент удаляется из него первым. Стеки можно реализовывать массивами и другими структурами данных, например, связанными списками. (Сте- ки и связанные списки подробно рассматриваются в главе 20.) Клиенту стекового класса не нужно принимать во внимание реализацию стека. Клиент знает только то, что когда элементы данных помещаются в стек, они будут обрабатываться по принципу LIFO. Клиенту интересно, какую функциональность предлагает стек, а не то, как эта функциональность реализована. Такая концепция называется абстракцией данных. Несмотря на то, что раз- работчики могут знать подробности реализации того или иного класса, им не следует писать коды, зависящие от этих подробностей. Такая практика позволяет заменить конкретный класс (например, класс, реализующий стек и его операции — проталкивание и выталкивание) на другую версию, не затрагивая другие части системы. До тех пор, пока public-службы класса остаются неизменными (т. е. каждый метод по-прежнему имеет одно и то же имя, возвращаемый тип и список параметров в определении нового класса), прочие части системы не затра- гиваются. В большинстве языков программирования основное внимание уделено операциям. В таких языках данные су- ществуют для поддержки операций, которые должна совершать программа; данные здесь "менее интересны", чем операции, и считаются "необработанными". В таких языках существует всего несколько встроенных типов данных, и создать собственные типы данных программистам достаточно сложно. C# и объектно-ориен- тированный принцип программирования значительно повышают важность данных. Первоначальной целью объ- ектно-ориентированного программирования в C# является создание типов данных (т. е. классов) и выражение взаимодействия между объектами этих типов данных. Для создания языков, делающих особый упор на данные, сообществу создателей языков программирования потребовалось формализовать некоторые понятия, связанные с данными. Рассматриваемая в книге формализация— это абстрактные типы данных (Abstract Data Type, ADT). Сегодня ADT уделяется внимания не меньше, чем структурному программированию десять лет назад.
160 Гпава 5 Впрочем, ADT не заменяют структурного программирования, а, скорее, обеспечивают дополнительную форма- лизацию, направленную на усовершенствование процесса разработки программных средств. Рассмотрим встроенный тип int, по поводу которого у многих могут возникнуть ассоциации с целыми числами в математике. Тип int— это, скорее, абстрактное представление целого числа. В отличие от математических целых чисел, int имеют фиксированный размер. Например, тип int в .NET ограничен примерным диапазоном от -2 х Ю9 до +2 х 109. Если результат вычислений выходит за рамки этого диапазона, то имеет место ошибка, и компьютер реагирует на нее машинозависимым способом. К примеру, машина может "тихонько” выдать непра- вильный результат. Математические целые числа не сталкиваются с этой проблемой. Именно поэтому понятие компьютерного int— это всего лишь приближенное понятие обычного целого числа. То же самое верно для float и других встроенных типов. До сих пор мы принимали понятие int, как нечто само собой разумеющееся, однако теперь на него стоит взгля- нуть с другой точки зрения. Такие типы, как int, float и char — это все примеры абстрактных типов данных. Эти типы являются представлениями понятий повседневной жизни с некоторым удовлетворительным уровнем точности в рамках компьютерной системы. Фактически, ADT включает два понятия: представление данных и операции, которые можно выполнять с дан- ными. Например, в C# int содержит целочисленное значение (данные) и выполняет сложение, вычитание, ум- ножение, деление и модульные операции; деление на ноль, впрочем, не определено. Для реализации абстракт- ных типов данных программисты на C# пользуются классами. Замечание по технологии программирования_____________________________________________ Разработчики могут создавать типы с помощью механизмов классов. Вновь создаваемые типы могут быть на- столько же удобны в работе, как и встроенные. Эта особенность ставит C# в ряд так называемых расширяемых языков. Несмотря на то, что язык легко расширить с помощью новых типов, программист не может изменить са- мую основу языка. Другой рассматриваемый абстрактный тип данных — это очередь (queue), напоминающая обычную очередь из людей. Внутри компьютерных систем используется много очередей. Очередь предлагает клиентам прозрачное и понятное поведение системы: клиенты помещают элементы данных по одному в очередь операцией постановки в очередь (enqueue) и получают их уже обработанными операцией снятия с очереди (dequeue). Очередь возвра- щает элементы по принципу First Input First Output (FIFO) — "первым пришел — первым обслужен”, т. е. пер- вый элемент, поставленный в очередь, будет первым кандидатом "на выход". Концептуально очередь может быть бесконечно длинной, но в реальности очереди конечны. Очередь скрывает представление внутренних данных, прослеживающих находящиеся в ней элементы, и предла- гает клиентам набор операций (постановка в очередь и снятие с очереди). Клиентам не обязательно знать реали- зацию очереди: они просто зависят от того, что очередь будет работать "как предписано". Когда клиент отправ- ляет в очередь элемент данных, она должна его принять и поместить в некую внутреннюю структуру данных, работающую по принципу FIFO. Точно так же, когда клиенту требуется очередной элемент из начала очереди, последняя должна извлечь его из своего внутреннего представления и выдать в порядке FIFO (т. е. элемент, на- ходившийся в очереди самое продолжительное время, должен быть возвращенным первым следующей опера- цией снятия с очереди). Абстрактный тип данных очередности гарантирует целостность внутренней структуры данных. Клиенты не мо- гут манипулировать этой структурой данных напрямую: к внутренним данным доступ имеет только ADT оче- редности. Клиенты могут выполнять лишь допустимые операции на представлении данных; ADT игнорирует операции, не предоставляемые общим интерфейсом. 5.15. Многократное использование программных средств Программисты, работающие с С#, уделяют особое внимание как созданию новых классов, так и повторному использованию классов из FCL (библиотека классов .NET Framework), содержащей тысячи уже определенных классов. Разработчики строят программные средства объединением классов, созданных программистами, с тщательно проверенными, документированными, портативными и повсеместно доступными классами FCL. Такой тип многократного использования программных средств ускоряет процесс разработки мощного и высо- кокачественного программного обеспечения. Сегодня огромный интерес представляет быстрая разработка приложений (Rapid Application Development, RAD). Библиотека FCL позволяет программистам добиваться многократного использования программных средств на платформах, поддерживающих .NET и принцип быстрой разработки приложений. Разработчики на C# делают упор на высокоуровневые аспекты программирования, оставляя подробности реализаций низкого уровня клас-
Объектно-ориентированное программирование 161 сам в FCL. Например, программисту, пишущему программу обработки графики на С#, не нужно знать подроб- ности каждой возможности обработки графики платформы .NET. Он просто сосредотачивается на изучении и использовании графических классов FCL. Библиотека FCL позволяет разработчикам на C# создавать программные приложения более оперативно путем использования уже существующих и многократно проверенных классов. Помимо сокращения общего времени на разработку, классы FCL развивают возможности программистов при отладке и технической поддержке про- граммных приложений благодаря тому, что используются проверенные компоненты. Для того чтобы разработ- чики могли пользоваться преимуществами классов FCL, они должны познакомиться с расширенным набором возможностей FCL. Многократное использование программных средств не ограничивается разработкой Windows-приложений. Биб- лиотека FCL содержит классы для создания Web-служб (или Web-cepeucoe), являющихся программными при- ложениями, пакетированными в виде служб, которыми клиенты могут пользоваться через Интернет. Любое приложение C# потенциально является Web-службой, поэтому программисты, работающие с С#, могут много- кратно использовать существующие приложения в качестве компоновочных блоков для создания более круп- ных и "замысловатых" приложений с возможностью работы в Web. Многие полагают, что Web-службы представляют следующую фазу эволюции проектирования программных средств, где Web предоставляет библиотеку функциональности, с помощью которой проектировщики могут создавать приложения, не зависимые от конкретной системной платформы. Будучи первым .NET-языкрм Micro- soft, C# обеспечивает все функции, необходимые для создания масштабируемых и надежных Web-служб. Фор- мально Web-службы представлены в главе 18. 5.16. Пространства имен и компоновочные блоки Практически в каждом приводимом в книге примере видно, что классы из существующих библиотек, скажем, из .NET Framework, должны импортироваться в программу C# путем добавления ссылки на соответствующие биб- лиотеки (процесс, продемонстрированный в разд. 2.7). Помните о том, что каждый класс в библиотеке классов FCL принадлежит конкретному пространству имен. Существующий в FCL код упрощает повторное использова- ние программных средств. Разработчики должны стремиться к тому, чтобы создаваемые ими программные компоненты обладали возмож- ностями многократного использования. Однако это часто приводит к так называемому конфликту имен. На- пример, два класса, определенные разными программистами, могут иметь одно имя. Если программе нужны оба класса, то она должна как-то различать их в коде. Распространенная ошибка программирования_______________________________________________ Попытка компилирования кода, содержащего конфликты имен, приводит к ошибкам компиляции. Пространства имен (namespaces) помогают свести к минимуму данную проблему обеспечением правила для уникальных имен классов. Два класса в отдельно взятом пространстве имен не могут иметь одинакового имени, однако разные пространства имен могут содержать классы с одинаковыми именами. При том, что программы на C# пишут сотни тысяч разработчиков, вполне вероятно, что имена, которые один программист выбирает Для описания классов, будут конфликтовать с именами классов, выбранными другим программистом. Начнем обсуждение с повторного использования существующих определений классов (листинг 5.17), предос- тавляющих код для класса Time3 (первоначально определенного в листинге 5.6). При повторном использовании определений классов между программами разработчики создают библиотеки классов, которые можно импорти- ровать в программу посредством оператора using. Из библиотек классов повторно можно использовать только public-классы. Классы, не принадлежащие к типу public, могут использоваться только другими классами в од- ном компоновочном блоке. Единственным различием между классом Time3 в данном примере и версией в листинге 5.6 является то, что по- казано пространство имен (namespace) — т. е. TimeLibrary, где определен класс Time3. Каждая библиотека клас- сов определена в пространстве имен namespace, содержащем все классы библиотеки. Авторы продемонстриру- ют процесс пакетирования Time3 в TimeLibrary.dll — динамически подключаемую библиотеку, создаваемую для повторного применения в других программах. Программы способны загружать динамически подключаемые библиотеки во время выполнения для доступа к общим функциям, которые могут совместно использовать мно- го программ. Динамически подключаемая библиотека представляет собой компоновочный блок. Когда в проек- те используется библиотека классов, то он должен содержать ссылку на компоновочный блок, определяющий библиотеку классов. 11 Зак. 3333
162 Гпава 5 • .' ............ ” "Г.-;.- V --------- £ Листинг 5,17, Компоновочный блок з теЬхЬгагу со-;ерж it . ласс Т±щеЗ Ш 4 1 2 3 4 5 б 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 // Листинг 5.17: TimeLibrary.cs // Размещение Time3 в компоновочном блоке для многократного использования using System; namespace ’fimeLibrary // указывает пространство имен для Time3 { // определение класса Time3 public class Time3 { private int hour; // 0-23 private int minute; // 0-59 private int second; // 0-59 // Конструктор Time3 инициализирует переменные экземпляра /7 к нулю для установки времени по умолчанию на полночь public Time3() { SetTime(0, 0, 0); } // Конструктор Time3: вводятся часы, минуты и секунды // по умолчанию — 0 public Time3(int hour) { SetTime(hour, 0, 0); } // Конструктор Time3: вводятся часы и минуты; секунды // по умолчанию — 0 public Time3(int hour, int minute) { SetTime(hour, minute, 0); J // Конструктор Time3: вводятся часы и минуты и секунды public Time3(int hour, int minute, int second) { SetTime(hour, minute, second); ) 11 Конструктор Time3: инициализация с помощью другого объекта Time3 public Time3(Time3 time) { SetTime(time.Hour, time.Minute, time.Second); ) // Установка нового значения времени в 24-часовом формате. // Проверка достоверности данных. Сброс некорректных значений в 0 public void SetTime( int hourValue, int minuteValue, int secondValue) { Hour = hourValue; Minute = minuteValue; Second = secondValue; ) // свойство Hour public int Hour { get
Объектно-ориентированное программирование 163 62 { .63 return hour 64 } 65 66 set 67 { 68 hour = ((value >= 0 && value < 24) ? value : 0); 69 } 70 71 ) // конец свойства Hour 72 73 // свойство Minute 74 public int Minute 75 { 76 get 77 { 78 return minute; 79 } 80 81 set 82 { 83 minute = ((value >= 0 && value < 60) ? value : 0); 84 } 8Й 8j ) конец свойства Minute 87 88 // свойство Second 89 public int Second 90 { 91 get 92 { 93 return second; 94 } 95 96 set 97 { 98 second = ((value >= 0 && value < 60) ? value : 0); 99 } 100 , 101 } конец свойства Second 102 103 // преобразование времени в строку 24-часового формата 104 . public string ToUniversalString() 105 { 106 return String.Format( 107 ”{0:D2}:{1:D2}:{2:D2}", Hour, Minute, Second); 108 } 109 110 // преобразование времени в строку 12-часового формата 111 public string- ToStandardString() 112 { 113 return String.Format("{0}:{1:D2}:{2:D2} {3}", 114 ((Hour — 12 |I Hour — 0) ? 12 : Hour % 12) 115 Minute, Second, (Hour < 12 ? "AM” : "PM”)); 116 } 117 } // конец класса Time3 118 } Теперь опишем по шагам процесс создания библиотеки классов TimeLibrary, содержащей класс Time3: 1. Создание проекта "библиотека классов". Выберите в меню File команду New | Project. В диалоговом окне New Project убедитесь в том, что в разделе Project Туре выбрана строка C# Projects и щелкните мышью на команде Class Library. Присвойте проекту имя TimeLibrary и выберите каталог, в котором проект нужно со- хранить. Будет создана простейшая библиотека классов (рис. 5.9).
164 Гпава 5 По поводу сгенерированного кода стоит сделать два важных замечания. Во-первых, этот класс не содержит метода Main. Это условие указывает на то, что класс в библиотеке классов не может быть использован дру- гими программами. Также обратите внимание, что Classi создан, как public-класс. Если эта библиотека бу- дет использоваться в другом проекте, то доступными окажутся только public-классы библиотеки. Для этой цели был создан класс Time3 — public (строка 9 в листинге 5.17). Класс тйпеЗ был создан просто переиме- нованием Classi (сгенерированного Visual Studio .NET как часть проекта) в Time3. В окне Solution Explorer файл Classl.cs также был переименован в Time3.cs. Рис. 5.9. Простая библиотека классов 2. Добавление кода для класса Time3. Удалите код для конструктора Classi. Затем скопируйте оставшуюся часть кода Time3 (строки 11—116) в листинге 5.17 (этот файл можно найти в наборе примеров на Web-сайте www.deitel.com) и вставьте код в тело определения класса, показанного на рис. 5.9. 3. Компиляция кода. В меню Build выберите команду Build Solution для построения решения. Код должен скомпилироваться без проблем. Помните о том, что этот код— неисполняемый, т. е. точки входа для про- граммы здесь нет. Фактически, если вы пытаетесь запустить программу выбором команды Debug | Start, то Visual Studio .NET выдаст сообщение об ошибке. При компилировании проекта будет создан компоновочный блок (динамически подключаемая библиотека), представляющий библиотеку нового класса. Этот компоновочный блок размещен в каталоге bin\Debug проекта. По умолчанию имя компоновочного блока будет включать в себя имя пространства имен. (В этом случае имя компоновочного блока будет TimeLibrary.dll.) Файл компоновочного блока содержит класс Time3, который мо- жет использоваться в других проектах. Файлы компоновочных блоков, имеющих расширение dll и ехе, являют- ся неотъемлемой частью С#. В операционной системе Windows запускаются исполняемые файлы (с расширени- ем ехе) для выполнения приложений, где используются файлы библиотеки (с расширением dll, обозначающее динамически подключаемые библиотеки) для представления библиотек кодов, которые могут динамически за- гружаться и совместно использоваться многими приложениями. Затем определяется проект консольного приложения, содержащего класс AssemblyTest (листинг 5.18), исполь- зующий класс Time3 в компоновочном блоке TimeLibrary.dll для создания объекта Time3 и отображения его строк времени в стандартном и универсальном форматах. До того, как класс AssemblyTest сможет использовать класс Time3, проект, содержащий AssemblyTest, должен получить ссылку на компоновочный блок TimeLibrary. Для добавления ссылки выберите в меню Project коман- ду Add Reference. С помощью кнопки Browse выберите TimeLibrary.dll (библиотека размещена в каталоге bin\Debug проекта TimeLibrary) и нажмите кнопку ОК для добавления ресурса в проект. После добавления
Объектно-ориентированное программирование 165 ссылки воспользуйтесь ключевым словом using для информирования программы-компилятора о том, что будут использоваться классы из пространства имен TimeLibrary (строка 5 в листинге 5.18). ?...'.......- ................:............................. -у -ууу -.............-... = Листинг 5.18. Компоновочный блок TimeLibrary, вызываемый из класса AssemblyTest | ил». 1 // Листинг 5.18: AsserriblyTest.es 2 // Использование класса Time3 из компоновочного блока TimeLibrary 3 4 using System; 5 using TimeLibrary; 6 7 // определение класса AssemblyTest 8 class AssemblyTest 9 { 10 // главная точка входа для приложения 11 static void Main(string[] args) 12 { 13 Time3 time = new Tjne3(13, 27, 6); 14 15 Console.WriteLine( 16 "Standard time: {0} \nUniversal time: {1} \n”, 17 time.ToStandardString(), time.ToUniversalString()); 18 } 19 } Результат работы программы: Standard time: 1:27:06 PM Universal time: 13:27:06 5.17. Окна Class View и Object Browser Теперь, после изложения основных концепций объектно-ориентированного программирования, представим два средства, предоставляемых Visual Studio .NET для упрощения проектирования объектно-ориентированных при- ложений — Class View и Object Browser. Окно Class View отображает переменные и методы для всех классов проекта. Для доступа к этой функции вы- берите в меню View команду Class View. На рис. 5.10 показано окно Class View для проекта TimeTestl из лис- тингов 5.1 и 5.2 (классы Timel и TimeTestl).'IIpH просмотре прослеживается иерархическая структура, позицио- нирующая имя проекта (TimeTestl) в качестве корня и включающая в себя серию так называемых узлов (напри- мер, классов, переменных и методов). Если слева от узла стоит символ +, это означает, что узел можно развернуть для просмотра других, включенных в него, узлов. Если же слева от узла стоит символ -, это означа- ет, что узел уже развернут, и при необходимости его можно свернуть. В соответствии с содержимым окна Class View проект TimeTestl имеет классы Timel и TimeTestl в качестве потомков. Класс Timel включает в себя ме- тоды SetTime, Timel, ToStandardString и ToUniversalString, а также переменные экземпляра hour, minute И second. Пиктограммы блокирования, размещенные слева от голубых маркеров для переменных экземпляра, ука- зывают на то, что эти переменные принадлежат к типу private. Класс TimeTestl содержит метод Main. Обрати- те внимание, что классы Timel и TimeTestl содержат узлы Bases and Interfaces. Если развернуть этот узел, то в каждом случае появится класс Object, потому что каждый класс наследует из класса System.Object (см. главу 6). В окне Object Browser Visual Studio .NET перечислены все входящие в проект классы. Разработчики пользуют- ся окном Object Browser для изучения функциональности, предоставляемой тем или иным классом. Для откры- тия окна Object Browser щелкните правой кнопкой мыши на любом встроенном классе или методе .NET в ре- дакторе кода и выберите команду Go То Definition. На рис. 5.11 показано окно Object Browser, когда пользова- тель щелкает правой кнопкой мыши на имени класса Object в редакторе кодов. Обратите внимание, что в окне Object Browser перечислены все методы, имеющиеся в классе object в окне Members of ’Object’; это окно обеспечивает разработчикам мгновенный доступ к информации, касающейся функциональности разных объек- тов. Также, отметьте, что в списке Objects окна Object Browser приводится перечень всех классов в библиотеке FCL (рис. 5.12). Окно Object Browser можно использовать для оперативного получения информации о классе или методе класса. Помните, что полное описание класса или метода можно просмотреть в документации, дос- тупной через меню Help в Visual Studio .NET.
166 Глава 5 Рис. 5.10. Окно Class View, отображающее классы Timei (из листинга 5.1) и TimeTestl (из листинга 5.2) Рис. 5.11. Окно Object Browser, когда пользователь щелкает правой кнопкой мыши на имени класса Object в редакторе кодов Рис. 5.12. Окно Object Browser после выбора команды Go То Definition Данная глава является первой в серии из трех глав, посвященных основам объектно-ориентированного про- граммирования. Здесь рассматриваются создание корректных определений классов, управление доступом к элементам классов и несколько функций, используемых обычно для создания полезных классов с возмож- ностью их применения другими разработчиками. В главе 6 подробно обсуждается понятие наследования. Чита- тель научится строить классы, наследующие данные и функциональность из уже существующих определений классов. Также в главе 6 представлена информация о других особенностях С#, имеющих непосредственное от- ношение к наследованию между классами. Эти функции служат основой концепции объектно- ориентированного программирования, называемой полиморфизмом, описываемой в главе 7.
Объектно-ориентированное программирование 167 5.18. Резюме К переменным экземпляра и методам, объявляемым с модификатором доступа public к члену класса, доступ осуществляется всякий раз, когда программа имеет ссылку на объект класса, в котором определены public- члены. Переменные экземпляра и методы, объявленные с модификатором доступа private к члену класса, дос- тупны только для методов класса, в котором определены private-члены. Конструктор класса вызывается при обработке объекта этого класса. Конструкторы инициализируют перемен- ные экземпляра объекта и могут перегружаться. Если конструкторы для класса не определены, тогда програм- ма-компилятор предоставляет конструктор по умолчанию. Последний не имеет параметров, но содержит пустое тело Для того чтобы клиенты могли выполнять манипуляции значениями private-данных, класс предоставляет оп- ределение свойства. Определения свойств содержат в себе методы средств доступа, управляющие подробностя- ми изменений и возвращения данных. Определение свойства может содержать процедуры доступа set, get или то и другое. Процедура доступа get позволяет клиенту считать поле значения, а процедура доступа set — изме- нять значение поля. Класс также может определить индексаторы для обеспечения индексированного доступа к данным в объекте этого класса. Индексаторы можно определять для использования любого типа данных в качестве индекса. Каж- дый индексатор может определить процедуры доступа get и set. .NET Framework выполняет автоматический "сбор мусора". Каждый класс в C# может иметь деструктор, кото- рый, как правило, возвращает системе ее ресурсы. Деструктор для объекта гарантированно активизируется при завершении обработки объекта перед тем, как "сборщик мусора" повторно запросит объем памяти для объекта Каждый класс в .NET Framework принадлежит к тому или иному пространству имен (или к библиотеке), содер- жащему группу относящихся к нему классов и интерфейсов. Программисты могут создавать собственные про- странства имен, во избежание конфликта имен с идентификаторами в других библиотеках, заданных другими разработчиками.
ГЛАВА 6 (ш Объектно-ориентированное программирование: наследование Не говорите, что вы знаете другого человека, до тех пор, пока не разделите с ним наследство. Йохан Каспер Лаватер Метод заключается в том, чтобы определить как число класса класс всех классов, сходных с отдельно взятым классом. Бертран Расселл Получить в наследство библиотеку хорошо; еше лучше собрать ее самому. Огастин Биррелл Темы данной главы: □ наследование и многократное использование программных средств; □ концепция базовых и производных классов; О модификаторы доступа к элементам классов protected и internal; О использование ссылки base для доступа к элементам базового класса; О использование конструкторов и деструкторов в базовых и производных классах; □ учебный пример, демонстрирующий механизм наследования. 6.1. Введение В данной главе рассмотрение объектно-ориентированного программирования (Object-Oriented Programming, OOP) начинается с представления одной из главных его особенностей — наследования. Наследование — это форма многократного использования программных средств, при которой классы создаются поглощением суще- ствующих данных и поведений класса с приданием им новых возможностей. Возможность многократного ис- пользования программных средств экономит время разработки. Она также поощряет использование проверен- ного программного обеспечения, что повышает вероятность эффективной реализации системы. При создании класса, вместо написания абсолютно новых переменных и методов экземпляра, программист мо- жет обозначить факт того, что новый класс должен наследовать переменные, свойства и методы другого клас- са. Определенный ранее класс называется базовым, а новый — производным. (В других языках программирова- ния, например в Java, базовый класс называется надклассом, а производный — подклассом.) После создания каждый производный класс может стать базовым для будущих производных классов. Производный класс, к ко- торому обычно добавляются уникальные переменные, свойства и методы класса, часто по размеру больше сво- его базового класса. Именно поэтому производный класс более специфичен, нежели его базовый класс, и пред- ставляет более специализированную группу объектов. Обычно производный класс содержит поведения своего базового класса и дополнительные. Прямой базовый класс — это базовый класс, из которого совершает явное наследование производный класс. Косвенный базовый класс наследуется из двух или более уровней выше по классовой иерархии. В случае единичного наследования класс производится из одного базового класса. В отли- чие от языка C++, C# не поддерживает множественного наследования (имеющего место, когда один класс про- изводится более чем из одного прямого базового класса). В главе 7 рассматривается использование интерфейсов в C# для реализации многих преимуществ множественного наследования без проблем ассоциирования. Каждый объект производного класса также является объектом базового класса этого производного класса. Од- нако объекты базового класса не являются объектами их производных классов. К примеру, все автомобили яв-
Объектно-ориентированное программирование: наследование 169 ляются транспортными средствами, но не все транспортные средства— автомобили. При углубленном рас- смотрении вопросов объектно-ориентированного программирования в главах 6 и 7 мы воспользуемся этим от- ношением для выполнения некоторых интересных манипуляций. Опыт создания программных систем указывает на то, что большая часть любого, кода имеет весьма близкое от- ношение к контрольным примерам. Когда программисты чересчур увлекаются контрольными (особыми) при- мерами, то обилие подробностей может "затереть" общую картину. При объектно-ориентированном програм- мировании разработчики делают упор на общность объектов в системе, а не на контрольные случаи. Данный процесс называется абстракцией. Различаются отношения "является" и "имеет" ("is-a" и "has-a"). Отношение "является" представляет наследо- вание. В отношении "является" объект производного класса также может рассматриваться как объект его базо- вого класса. Например, автомобиль является транспортным средством. А "имеет" обозначает составление (рас- сматривается в главе 5). В отношении "имеет" объект класса содержит одну или более ссылок на объект в каче- стве элементов. Например, автомобиль имеет рулевое колесо. Методы производных классов могут требовать доступа к переменным экземпляра, свойствам, методам их базо- вого класса. Производный класс может иметь доступ к He-private-элементам его базового класса. Элементы базового класса, которые не должны быть доступными для свойств или методов класса, производного от этого базового класса через наследование, объявляются в базовом классе как private. Производный класс может при- вести в действие изменения состояния в private-членах базового класса, но только посредством не-private- методов и свойств, представленных в базовом классе и унаследованных в производный класс. Замечание по технологии программирования_________________________________________________ Свойства и методы производного класса не могут получить прямой доступ к private-членам их базового класса Замечание по технологии программирования_________________________________________________ Сокрытие элементов типа private помогает разработчикам корректно тестировать, отлаживать и модифициро- вать системы Если производный класс имел бы доступ к private-членам его базового класса, тогда классы, наследующие из этого производного класса, также могут получить доступ к этим данным. При этом доступ рас- ширился бы до того, что должно считаться private-данными, и преимущества сокрытия информации были бы потеряны. Одна проблема наследования заключается в том, что производный класс может наследовать свойства и методы, которые ему либо не нужны, либо не должны наследоваться. На разработчика класса возлагается ответствен- ность за то, что возможности, предоставляемые классом, будут соответствовать будущим производным классам. Даже когда свойство или метод базового класса соответствуют производному классу, этот производный класс часто требует свойство или метод для выполнения задачи так, как это сделает производный класс. В таком слу- чае свойство или метод базового класса можно подменить (переопределить) в производном классе соответст- вующей реализацией. Новые классы могут наследовать из обширных библиотек классов. Организации разрабатывают собственные библиотеки классов и пользуются другими повсеместно доступными библиотеками. Настанет время, когда большая часть нового программного обеспечения будет создаваться из стандартизированных компонентов многократного использования, как происходит в настоящее время с аппаратными средствами. Все это упрощает разработку более мощного и расширенного программного обеспечения. 6.2. Базовые и производные классы Часто объект одного класса также "является" объектом другого класса. Например, прямоугольник есть четы- рехугольник (как квадраты, параллелограммы и трапеции). Следовательно, можно сказать, что класс Rectangle (прямоугольник) наследует от класса Quadrilateral (четырехугольник) В данном контексте класс Quadrilateral является базовым, а класс Rectangle — производным. Прямоугольник есть специфический тип четырехугольника, но некорректно заявлять, что любой четырехугольник является прямоугольником: четырех- угольник может быть параллелограммом или любым другим типом класса Quadrilateral. В табл. 6.1 перечис- лено несколько простых примеров базовых и производных классов. Таблица 6.1. Примеры наследования Базовый класс Производные классы Student (студент) Graduatestudent (студент выпускного курса), Undergraduatestudent (студент, еще не получивший диплом)
170 Гпава 6 Таблица 6.1 (окончание) Базовый класс Производные классы Shape (форма) Circle (окружность), Triangle (треугольник), Rectangle (прямоугольник) Loan (аренда, ссуда) CarLoan (прокат автомобилей), Home Improvement Loan (ссуда на улучшение жилищных условий), MortgageLoan (заем под залог недвижимости) Employee (служащий) FacultyMember (преподаватель учебного заведения), Staf fMember (штатный сотрудник) Account (счет) CheckingAccount (лицевой счет до востребования), SavingsAccount (депозитный счет) Каждый объект производного класса "является" объектом базового класса, и один базовый класс может иметь много производных классов; следовательно, набор объектов, представленный базовым классом, как правило, больше, чем набор объектов, представленных любым из его производных классов. Например, базовый класс vehicle (транспортное средство) представляет все транспортные средства, включая легковые автомобили, гру- зовики, паромы, велосипеды и т. д. Производный же класс Саг (легковой автомобиль) представляет только не- большое множество всех Vehicles. Отношения наследования образуют иерархические структуры, подобные деревьям. Класс существует в иерар- хическом взаимоотношении со своими производными классами. Несмотря на то, что классы могут существо- вать независимо, как только они вовлекаются в наследование, то сразу становятся "привязанными" к другим классам. Класс становится либо базовым (обеспечивает другие классы данными и поведениями), либо произ- водным, наследующим данные и поведения от других классов. Попробуем'построить простую иерархию наследования Университетское сообщество состоит из тысяч членов. Это служащие, студенты и выпускники. Служащие являются либо сотрудниками факультетов, либо прочими сотрудниками. Сотрудники факультетов — это либо административные работники (деканы, заведующие кафед- рами), либо преподаватели. Такая организационная структура соответствует иерархии наследования, представ- ленной на рис. 6.1. Обратите внимание, что иерархия наследования может включать в себя много других клас- сов. Например, студенты могут быть выпускниками и новичками. "Новички" — это первокурсники, второкурс- ники и т. д. Каждая стрелка в схеме иерархии представляет отношение "является". Например, при рассмотрении стрелок на схеме можно сказать, что "Employee — это CommunityMember" (служащий — это член сообщества) и "Teacher— это FacultyMember" (преподаватель— это сотрудник факультета). CommunityMember— это прямой базовый класс для Employee (сотрудник), Student (студент) и Alumnus (выпускник). Помимо этого CommunityMember — это косвенный базовый класс для всех прочих классов на схеме иерархии. Рис. 6.1. Иерархия наследования для университетских классов CommunityMember Начиная с нижней части диаграммы, по стрелкам можно применить отношение "является" до самого верхнего базового класса. Например, Administrator является FacultyMember, Employee и CommunityMember. В C# Administrator также является Object, потому что все классы в C# имеют Object в качестве прямого или кос- венного базового класса. Таким образом, все классы в C# связаны иерархическими отношениями, в которых они совместно используют восемь методов, определенных классом object. Далее в тексте будут рассмотрены некоторые из этих методов, унаследованные из Object.
Объектно-ориентированное программирование: наследование 171 Другая иерархия наследования — иерархия Shape (рис. 6.2). Для обозначения того, что класс TwoDimensionalShape является производным (или наследует из) класса Shape, класс TwoDimensionalShape можно определить в C# сле- дующим образом: class TwoDimensionalShape : Shape Рис. 6.2. Часть иерархии класса Shape В главе 5 кратко рассматривались отношения "имеет" (has-a), где классы в качестве членов имеют ссылки на объекты других классов. Такие отношения создают классы по построению существующих классов. Например, при наличии классов Employee, BirthDate и TelephoneNumber будет некорректно сказать, что Employee является BirthDate или что Employee — ЭТО TelephoneNumber. Однако будет правильным сказать, что Employee имеет BirthDate и имеет TelephoneNumber. При наследовании private-члены базового класса не доступны напрямую из производных классов этого класса, но private-члены базового класса по-прежнему наследуются. Все прочие члены базового класса сохраняют первоначальный доступ к членам, когда они становятся членами производного класса (например, public-члены базового класса становятся public-членами производного класса, и, как скоро станет понятно, protected-члены базового класса становятся protected-членами производного класса). Посредством этих унаследованных членов базового класса производный класс может манипулировать private-членами базового класса (если эти унасле- дованные члены обеспечивают такую функциональность в базовом классе). Объекты базовых и производных классов можно рассматривать сходно; их общность выражена в переменных, свойствах и методах членов базового класса. Объекты всех классов, производных от базового класса, можно рассматривать как объекты этого базового класса. В главе 7 приводится много примеров, в которых использу- ются преимущества этого отношения. Замечание по технологии программирования________________________________________________ Конструкторы никогда не наследуются; они относятся только к конкретному классу, в котором определены. 6.3. Члены protected и internal В главе 5 рассматривались модификаторы доступа к public- и private-членам, public-члены базового класса, не принадлежащие к типу static, доступны везде, где программа имеет ссылку на объект этого базового класса или на один из его производных классов. Доступ к private-членам базового класса осуществляется только в рамках этого базового класса. В данном разделе представляются два дополнительных модификатора доступа к элементам: protected и internal. Использование доступа protected (защищенный) предлагает промежуточный уровень защиты между доступами public и private. Доступ к protected-членам базового класса может быть осуществлен только в этом базовом классе или в любых классах, производных от этого базового класса. Другой промежуточный уровень доступа называется internal (внутренний). Доступ к internal-членам базово- го класса может осуществляться только объектами, объявленными в том же компоновочном блоке. Обратите внимание, что internal-член доступен в любой части компоновочного блока; в котором этот internal-член объявлен. Методы производных классов, как правило, могут обращаться к членам типа public, protected и internal ба- зового класса просто использованием имен членов. Когда метод производного класса подменяет член базового класса, тогда доступ к члену базового класса может быть осуществлен из производного класса, если ввести пе- ред именем члена базового класса ключевое слово base, за которым следует оператор . (точка). Ключевое сло- во base рассматривается в разд. 6.4.
172 Глава 6 6.4. Взаимоотношения между базовыми и производными классами В данном разделе для рассмотрения отношений между базовым и производным классами используется иерар- хия точечной окружности1. Обсуждение отношения точечной окружности будет разделено на несколько частей. Во-первых, создадим класс Point, непосредственно наследующий из класса System.Object и содержащий в ка- честве private-данных пару координат (г, у). Затем создадим класс Circle, который также непосредственно наследует из класса System.Object и содержит в качестве private-данных пару координат (х,у), представляю- щую местоположение центра окружности, и радиус. Для создания класса Circle наследование не используется; класс строится написанием каждой строки кода, требуемого классом, Затем создается отдельный класс Circle2, напрямую наследующий из класса Point (т. е. класс Circle2 является Point, но также содержит радиус) и де- лающий попытку использования private-членов класса Point; результатом этого становятся ошибки компиля- ции, потому что производный класс не имеет доступа к private-данным базового класса. Затем демонстрирует- ся, что если данные класса Point объявлены как protected, тогда класс Circles, наследующий из класса Point, может получить доступ к этим данным. Наследуемый и ненаследуемый классы Circle содержат идентичную функциональность, но мы покажем, насколько проще создать и управлять наследованным классом Circles. За- кончив обсуждение преимуществ использования protected-данных, установим данные Point на private (для стимулирования корректного процесса проектирования), после чего продемонстрируем, как отдельный класс Circle4 (также наследующий из класса Point) может использовать методы Point для манипуляций private- данными класса Point. Для начала рассмотрим определение класса Point (листинг 6.1). Службы public класса Point включают в себя два конструктора Point (строки 13—24), свойства х и у (строки 27—54) и метод Tostring (строки 57—60). Пе- ременные экземпляра х и у класса Point указаны как private (строка 10), поэтому объекты других классов не могут напрямую осуществлять доступ к х и у. Технически, даже если бы переменные х и у в Point- были обозна- чены как public, класс Point никогда не сможет поддерживать несогласованное состояние, потому что плос- кость координат ху бесконечна в обоих направлениях, и, следовательно, свойства х и у могут содержать любое значение int. Вообще, объявление данных как private с представлением He-private-свойств для манипулиро- вания и выполнения проверки достоверности этих данных является хорошим стилем программирования. Г... -..... • '• .............. . • -;•••••.---............................................ —............. Листинг 6.1. Класс Poxnt представляет пару координат (х. у) ........ *..... 1 // Листинг 6.1: Point.cs 2 // Класс Point представляет пару координат (х, у) 3 4 using System; 5 6 // Определение класса Point неявно наследует из -Object 7 public class Point 8 { 9 // координаты точки 10 private int x, у; 11 12 // конструктор по умолчанию (без аргументов) 13 public Point() 14 { 15 // здесь происходит неявное обращение к конструктору Object 16 } 17 18 // конструктор 19 public Point(int xValue, int yValue) 20 { 21 // здесь происходит неявное обращение к конструктору Object 22 X = xValue; 23 Y = yValue; 24 } 25 1 Отношение точечной окружности может показаться неестественным, когда оно рассматривается в контексте того, что окружность "является" точкой. Данный пример демонстрирует то, что иногда называется структурным наследованием; в примере упор сделан на "механику" наследования и на то, как соотносятся между собой базовый и производный классы. В главе 7 представлены более понятные примеры наследования.
Объектно-ориентированное программирование наследование 173 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 // свойство X public int X { get { return x; } set { x = value; // нет необходимости контроля } } 11 конец свойства X // свойство Y public int Y; { get { return y; } set { у = value; // нет необходимости контроля } } 11 конец свойства Y // возвращение строкового представления Point public override string ToString() { return "[" + x + ", " + у + } } // конец класса Point В разд. 6.2 отмечалось, что конструкторы не наследуются. Следовательно, класс Point не наследует конструк- тор класса Object. Однако конструкторы класса Point (сгроки 13—24) неявно вызывают конструктор класса Object. Фактически, первой задачей конструктора любого производного класса является явный или неявный вызов конструктора прямого базового класса. (Синтаксис вызова конструктора базового класса рассматривается далее.) Если код не включает явного обращения к конструктору базового класса, тогда делается неявный вызов конструктора по умолчанию (без аргументов) базового класса. Комментарии в строках 15 и 21 указывают на то, где имеют место неявные обращения к конструктору по умолчанию базового класса Object. Обратите внимание, что метод Tostring (строки 57—60) содержит в своем объявлении ключевое слово override. Каждый класс в C# (например, класс Point) наследует прямо или косвенно из класса System.Object, являющегося корнем классовой иерархии. Как упоминалось выше, это означает, что каждый класс наследует восемь методов, определенных классом object. Одним из этих методов является Tostring, возвращающий строку и содержащий тип объекта, перед которым введено пространство имен. Этот метод принимает строковое представление объекта и иногда неявно вызывается программой (например, когда объект связывается со стро- кой). Метод Tostring класса Point подменяет первоначальный Tostring из класса Object; при активизации ме- тод Tostring класса Point возвращает строку, содержащую упорядоченную пару х и у (строка 59), вместо воз- вращения строки, содержащей класс и пространство имен объекта. Для подмены определения метода базового класса производный класс должен указать, что метод производного класса подменяет метод базового класса ключевым словом override в заголовке метода.' Замечание по технологии программирования_________________________________________________ Компилятор C# устанавливает базовый класс производного класса на Object, если программа не задает базо- вый класс явно. В языке C# метод базового класса должен быть объявлен как virtual, если этот метод должен быть подменен в производном классе. Метод Tostring класса Object фактически объявлен virtual, что позволяет производному
174 Гпава 6 классу Point подменить этот метод. Для просмотра заголовка метода Tostring выберите команду Help | Index... и введите в поле поиска объект object.Tostringmethod (фильтрованный .NET Framework SDK). Отображаемая страница содержит описание метода Tostring, включающего в себя следующий заголовок: public virtual string ToString(); Ключевое слово virtual позволяет программистам задавать методы, которые производный класс может подме- нить; метод, не объявленный как virtual, не может быть подменен. Он будет использован далее в этом разделе для активизации определенных методов с целью подмены в базовых классах. Распространенная ошибка программирования___________________________________________________ Попытка подмены производным классом (с помощью ключевого слова override) метода, не объявленного как virtual, является синтаксической ошибкой. В классе PointTest (листинг 6.2) тестируется класс Point. В строке 14 создается объект класса Point, присваи- вается 72 в качестве значения координаты х и 115 — как значение координаты у. В строках 17 и 18 использова- ны свойства х и Y для получения этих значений, после чего значения добавляются к string output. В строках 20 и 21 изменяются значения свойств х и Y (неявно активизируя средства доступа set), а в строке 24 неявно вызы- вается метод Tostring для получения строкового представления класса Point. ;.........- ? ........ ............ . " .................... . ..."7 .......................................— ...........—........ Листинг 6.2. Кларе PointTest демонстрирует функциональность класса Point 4. .. . .ai. .............-------------------------------------------_____________..___.............................................. 1 /J Листинг 6.2: PointTest.es •2 // Тестирование класса Point 3 4 using System; 5 using System.Windows.Forms; 6 7 // определение класса PointTest 8 class PointTest 9 { 10 // главная точка входа для приложения 11 static void Main(string[] args) 12 { 13 // обработка объекта Point 14 Point point = new Point(72, 115); 15 16 // отображение координат точки через свойства X и Y 17 string output = "X coordinate is " + point.X + 18 "\n" + y coordinate is " + point.Y; 19 20 point.X =10; // установка координаты x через свойство X 21 point.Y = 10; // установка координаты у через свойство Y 22 23 // отображение нового значения точки 24 output += "\n\nThe new location of point is " + point; 25 26 MessageBox.Show(output, "Demonstrating Class Point"); 27 28 } // конец метода Main 29 30 } // конец класса PointTest Рис. 6.3. Демонстрация классом PointTest функциональности класса Point Результат работы программы представлен на рис. 6.3. Теперь рассмотрим вторую часть введения в наследование путем создания и тестирования (абсолютно нового) класса Circle (листинг 6.3), напрямую наследующего из класса System.Object и представляет пару координат (х, у), обозначающую центр окружности, и радиус. В строках 9 и 10 объявляются переменные экземпляров х, у и radius как private-данные. В службы public класса Circle входят два конструктора Circle (строки 13—25), свойства X, Y и Radius (строки 28—71), методы Diameter (строки 74—77), Circumference (строки 80—83), Area (строки 86—89) и Tostring (строки 92—96). Эти свойства и методы инкапсулируют все необходимые особен- ности (т. е. "аналитическую геометрию") окружности; в следующем разделе демонстрируется, как инкапсуляция позволяет повторно использовать и расширять этот класс.
Объектно-ориентированное программирование: наследование 175 I™. 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 // Листинг 6.3: Circle.cs // Класс Circle содержит пару координат (х, у) и радиус using System; // Определение класса Circle неявно наследует из Object public class Circle { private int x, у; // координаты центра Circle private double radius; // радиус Circle 11 конструктор по умолчанию public Circle() { // здесь происходит неявное обращение к конструктору Object ' } // конструктор public Circle(int xValue, int yValue, double radiusValue) { 11 здесь происходит неявное обращение к конструктору Object х « xValue; у = yValue; radius = radiusValue; \ } // свойство X public int X < get { return x; } set { x = value; // нет необходимости контроля ) } // конец свойства X // свойство Y public int Y { get { return у; } set { у = value; // нет необходимости контрол0 } } // конец свойства Y // свойство Radius public double Radius { get
176 Гпава 6 61 { 62 return radius; 63 } 64 65 set 66 { 67 if (value >= 0) // необходим контроль 68 radius value; 69 } 70 71 } // конец свойства Radius 72 73 // расчет диаметра Circle 74 public double Diameter() 75 { . 76 return radius * 2; 77 } 78 79 // расчет длины окружности Circle 80 public double Circumference() 81 { 82 return Math.PI * Diameter(); 83 } 84 85 // расчет площади Circle 86 public double Area() 87 { 88 return Math.PI * Math.Row(radius, 2); 89 } 90 91 // возвращение строкового представления Circle 92 public override string ToStringO 93 { 94 return "Center = [" + x + ", " + у + "]" + 95 "; Radius = " + radius; 96 } 97 98 } // конец класса Circle В классе CircleTest (листинг 6.4) тестируется класс circle. В строке 14 создается объект класса Circle с при- своением значения 37 координате х, 43 — координате у и 2.5.— радиусу. В строках 17—19 используются свой- ства х, Y и Radius для получения этих значений, после чего значения дописываются в string output. В строках 22—24 используются свойстват1гс1е — х, Y и Radius для изменения координат х и у и радиуса, соответствен- но. Свойство Radius обеспечивает, что переменной элемента radius не может быть присвоено отрицательное значение. В строке 28 осуществляется явное обращение к методу Tostring класса Circle для получения строко- вого представления класса Circle, а в строках 32—40 вызываются методы класса Circle— Diameter, Circumference и Area. 1 // Листинг 6.4: CircleTest.cs 2 // Тестирование класса Circle 3 4 using System; 5 using System.Windows.Forms; 6 7 // определение класса CircleTest 8 class CircleTest 9 { 10 // главная точка входа для приложения 11 static void Main(string!] args)
Объектно-ориентированное программирование: наследование 177 12 { 13 // создание (обработка) Circle 14 Circle circle = new Circle(37, 43, 2.5); 15 16 // получение первоначальных координат х и у и радиуса Circle 17 string output = "X coordinate is " + circle.X + 18 "\nY coordinate is " + circle.Y + "\nRadius is " + 19 circle.Radius; 20 21 // задание новых значений координатам х и у и радиуса Circle 22 circle.X = 2; 23 circle.Y =2; 24 circle.Radius = 4.25; 25 26 // отображение строкового представления Circle 27 output += "\n\nThe new location and radius of " + 28 "circle are \n" + circle + "\n"; 29 30 // отображение диаметра Circle 31 output += "Diameter is " + 32 String.Format(”{0:F)", circle.Diameter()) + "\n"; 33 34 // отображение длины окружности Circle 35 output += "Circumference is " + 36 String.Format("(0:F)", circle.Circumference()) + "\n"; 37 38 // отображение площади Circle 39 output 4-= "Area is " + 40 String.Format("{0:F)", circle.Area()); 41 42 MessageBox.Show(output, "Demonstrating Class Circle"); 43 44 } // конец метода Main 45 Рис. 6.4. Демонстрация классом CircleTest функциональности класса Circle 46 } // конец класса CircleTest Результат работы программы представлен на рис. 6.4. После написания полного кода для класса Circle (см. листинг 6.3) видно, что большая часть кода в этом классе сходна, если не идентична, с большей частью кода для класса Point. Например, объявление в классе Circle private-переменных х и у и свойств х и Y идентично их объявлению в классе Point. Кроме этого, конструкторы класса circlе и метод Tostring практически идентичны конструкторам класса Point, за исключением того, что они предоставляют информацию и о radius. Единственными добавлениями к классу Circle являются перемен- ная radius private-члена, СВОЙСТВО Radius и методы Diameter, Circumference И Area. Возникает впечатление, что код Point был просто скопирован, вставлен в код класса Circle, после чего послед- ний был модифицирован с включением радиуса. Подобный принцип "копирования и вставки" не защищен от ошибок, требует много времени и, что еще хуже, результатом его может стать чрезмерное количество физиче- ских копий существующего в системе кода, обслуживание и поддержка которых превращается в "кошмар" для разработчиков. Существует ли способ "поглощения" атрибутов и поведений одного класса так, чтобы они ста- новились частью других классов без необходимости дублирования кода? В следующем примере дается ответ на поставленный выше вопрос, используется более продуманный подход к построению классов с упором на преимуществах наследования. Теперь создадим и протестируем класс Circle2 (листинг 6.5), наследующий переменные х и у и свойства х и Y класса Point (см. листинг 6.1). Данный класс Circle2 "является" Point (потому что наследование поглощает возможности класса Point), но и содержит radius (строка 9). Символ двоеточия в определении класса (строка 7) указывает на наследование. Как произ- водный, класс Circle2 наследует все элемента класса Point, за исключением конструкторов. Таким образом, public-службы класса Circle2 включают в себя два конструктора Circle2 (строки 12—24); public-методы, унаследованные из класса Point; свойство Radius (строки 27—40) и методы Circle2 — Diameter, Circumference, Area и Tostring (строки 43—65). Метод Area объявляется как virtual так, что производные классы (например, класс cylinder, как будет понятно из разд. 6.5) могут подменить этот метод для обеспечения более корректной реализации. 12 Зак. 3333
178 Глава 6 Листинг 6.5. Класс Circle2, наследующий из класса Point .« ч ....• . «..™...< 5 ’ ’ 1 2 3 4 5 б 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 // Листинг 6.5: Circle2.cs // Класс Circle2, наследующий из класса Point using System; // определение класса'Circle2 наследует из Point class Circle2 : Point { ' private double radius; // радиус Circle2 // конструктор по умолчанию public Circle2() { // здесь имеет место неявное обращение к конструктору Point ) // конструктор public Circle2(int xValue, int yValue, double radiusValue) { // здесь имеет место неявное обращение к конструктору Point х = xValue; у = yValue; Radius = radiusValue; ) // свойство Radius public double Radius { get { return radius; \ } set { if (value >= 0) radius value; ) } // конец свойства Radius // расчет диаметра Circle public double Diameter() { return radius * 2; ) // расчет длины окружности Circle public double Circumference() { return Math.PI ♦ Diameter(); ) // расчет площади Circle public virtual double AreaO { return Math.Pow(radius, 2); )
Объектно-ориентированное программирование: наследование 179 60 // возвращение строкового представления Circle 61 public override string ToStringO 62 { 63 return Center = [" + x + ", " + у + "J" + 64 Radius = " + radius; 65 } 66 67 } // конец класса Circle2 В строках 14 и 20 в конструкторах' Circle2 (строки 12—24) неявно активизируется конструктор по умолчанию Point для инициализации части базового класса (переменные х и у, унаследованные от класса Point) объекта Circle2 к 0. Однако из-за того, что параметризованный конструктор (строки 18—24) должен присваивать коор- динатам (х,у) специфические значения, строки 21 и 22 делают попытку присвоения значений аргумента напря- мую х и у. Несмотря на то, что в строках 21 и 22 делается попытка явного задания значений х и у, в строке 20 сначала вызывается конструктор по умолчанию Point для инициализации этих переменных по их значениям по умолчанию. Компилятор генерирует синтаксические ошибки для строк 21 и 22 (и для строки 63, где метод ToString класса Circle2 делает попытку прямого использования значений х и у), потому что производному классу Circle2 не разрешен доступ к private-элементам х и у базового класса Point. Примечание_____________________________________________________________________________ Номера строк на рис. 6.5 немного отличаются от упоминавшихся номеров строк, потому что представленный в книге код немного отличается от фактического кода программы. Cfck tee to add -э new Task List - 3 BuM Error ta*ks shown (filtered) Clrcle2.Pointx' Is inaccessble due to its protection level C:\..\Circle2.cs 23 *Cfrcle2.Potityl 11 Is Inaccessble due to Its protection level C:\..\Clrcte2.cs 24- 'Circle2.Pointx‘ is inaccessble due to its protection level C:\...\Circle2.cs 65 l1L . - Рис. 6.5. Синтаксические ошибки Язык C# накладывает строгое ограничение на доступ к элементам private-данных так, что даже производный класс (т. е. тесно связанный с базовым классом) не может получить доступ к private-данным базового класса. Для активизации класса Circle2 на прямой доступ к переменным элементов х и у класса Point эти переменные можно объявить как protected. Как обсуждалось в разд. 6.3, доступ к protected-членам базового класса может быть осуществлен только в этом базовом классе или в любых классах, являющихся производными для этого базового класса. Класс Point2 (листинг 6.6) модифицирует класс Point (см. листинг 6.1) для объявления пере- менных х и у как protected (строка 10), а не как private. 1 // Листинг 6.6: Point2.cs 2 // Класс Point2 содержит координаты (х, у) как protected-данные 3 4 using System; 5 6 // определение класса Point2 наследует из Object 7 public class Point2 8 { 9 // координаты точки 10 protected int x, у; 11 12 // конструктор по умолчанию 13 public Point2() 14 { 15 // здесь имеет место неявное обращение к конструктору Object 16 ) 17 18 // конструктор 19 public Point2(int xValue, int yValue)
180 Гпава 6 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 { И здесь имеет место неявное обращение к конструктору Object х = xValue; у = yValue; ) II свойство X public int X { get { return x; } set { x = value; / / нет необходимости аттестации } } // конец свойства X // свойство Y public int Y { get ( return у; } set { у = value; // нет необходимости аттестации ) } // конец свойства у // возвращение строкового представления Point2 public override string ToStringО { return ”[" + x + ", " + у + "]"; } // конец класса Point2 Класс Circles (листинг 6.7) модифицирует класс Circle2 (см. листинг 6.5) для наследования из класса Point2, а не из класса Point. По причине того, что класс Circles является производным классом от класса Point2, класс Circles может получить прямой доступ к переменным элементам х и у типа protected класса Point2, и компи- лятор не генерирует ошибки при компилировании программы, представленной в листинге 6.7. Это демонстри- рует особые привилегии, гарантированные производному классу для доступа к protected-членам данных базо- вого класса. Производный класс также может получить доступ к методам protected в любом из базовых клас- сов этого производного класса. 1 // Листинг 6.7: Circle3.cs 2 // Класс Sircle2, наследующий из класса Point2 3 4 using System; 5 б // определение класса Circle3 наследует из Point2 7 public class Circle3 : Point2
Объектно-ориентированное программирование: наследование 181 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 private double radius; // радиус Circle3 // конструктор по умолчанию public Circle3() I // здесь имеет место неявное обращение к конструктору Point2 } // конструктор public Circled(int «Value, int yValue, double radiusValue) { // здесь имеет место неявное обращение к конструктору Point2 х «Value; у = yValue; Radius = radiusValue; J // свойство Radius public double Radius get { return radius; } set { if (value >= 0) radius value; } } // конец свойства Radius // расчет диаметра Circle public double Diameter() { return radius * 2; } // расчет длины окружности Circle public double Circumference() { return Math.PI ♦ Diameter(); } // расчет площади Circle public virtual double Area() { return Math.PI * Math.Pow(radius, 2); } // возвращение строкового представления CircleS public override string ToString*() • { return "Center =["+«+", " + у + + "; Radius = " + radius; } } // конец класса Circle3
182 Гпава 6 Класс CircleTestS (листинг 6.8) выполняет идентичные проверки класса Circles, как класс CircleTest (см. листинг 6.4) выполнял на классе Circle (см. листинг 6.3). Обратите внимание, что выходные данные двух программ идентичны. Класс Circle3 был создан без учета наследования, а класс circles — с использованием наследования; не смотря на это, оба класса обеспечивают одну и ту же^функциональность. Однако листинг кода для класса Circles (68 строк) значительно короче, чем листинг кода для класса Circle (98 строк), потому что класс Circle3 поглощает часть функциональности из Point2, тогда как класс Circle — нет. К тому же, теперь осталась только одна копия точечной функциональности. .......... ... ^Листинг 6.8. Класс CircleTest3 демонстрирует функциональность класса Circle3 1 // Листинг 6.8: CircleTest3.cs 2 // Тестирование класса Circle3 3 4 using System; 5 using System.Windows.Forms; 6 7 // определение класса CircleTest3 8 class CircleTest3 9 { 1*0 // основная точка входа для приложения 11 static void Main(string[] args) 12 { 13 /7 создание (обработка) Circle3 14 Circle3 circle = new Circle3(37, 43, 2.5); 15 16 // получение первоначальных координат x и у и радиуса Circle3 17 string output = ”Х coordinate is ” + circle.X + "\n” + 18 ”Y coordinate is " + circle.Y + "\nRadius is " + 19 circle.Radius; 20 21 // задание новых значений координатам х и у и радиуса Circle3 22 circle.X = 2; 23 circle.Y = 2; 24 circle.Radius = 4.25; 25 26 // отображение строкового представления Circle3 27 output += "\n\n" + 28 "The new location and radius of circle are " + 29 "\n" + circle + "\n"; 30 31 // отображение диаметра Circle3 32 output += "Diameter is " + 33 String.Format("{0:F}", circle.Diameter()) + "\n"; 34 35 // отображение длины окружности Circle3 36 output += "Circumference is " + 37 String.Format(”{0:F)", circle.Circumference()) + "\n"; 38 39 // отображение площади Circle3 40 output += "Area is " + 41 String.Format("{0:F}”, circle.Area()); 42 43 MessageBox.Show(output, "Demonstrating Class Circle3"); 44 45 } // конец метода Main 46 47 } // конец класса CircleTest3 Рис. 6.6. Демонстрация классом CircleTest3 функциональности класса Circle3 Результат работы программы представлен на рис. 6.6. В предыдущем примере переменные экземпляра базового класса были объявлены как protected, так что произ- водный класс мог напрямую модифицировать их значения. Использование переменных типа protected позволя-
Объектно-ориентированное программирование: наследование 183 ет слегка повысить производительность, потому что мы избегаем непроизводительных издержек вызова метода в процедурах доступа set или get свойства. Однако в большинстве приложений С#, где взаимодействие с поль- зователем занимает большую часть времени исполнения, оптимизация, предлагаемая использованием перемен- ных типа protected, незначительна. Использование переменных экземпляра protected создает две фундаментальные проблемы. Во-первых, объекту производного класса не нужно использовать свойство для установки значения данных типа protected базового класса Следовательно, объект производного класса может легко присвоить недопустимое значение данным типа protected, оставляя объект в несогласованном состоянии. Например, если бы было нужно объявить radius переменной класса Circles как protected, то объект производного класса (например, Cylinder) мог бы присво- ить radius отрицательное значение. Второй проблемой использования данных типа protected является то, что методы производного класса, вероятнее всего, будут написаны в зависимости от реализации базового класса. На практике производные классы должны зависеть только от служб базового класса (т. е. методов и свойств, не принадлежащих к типу private), а не от реализации базового класса. При данных protected в базовом классе, если изменяется реализация базового класса, может потребоваться модернизация всех производных классов этого базового класса. Например, если по какой-либо причине потребовалось бы изменить имена переменных х и у на xCoordinate и yCoordinate, тогда это пришлось бы делать во всех случаях, когда производный класс ссы- лается напрямую на эти переменные базового класса. В этом случае программное средство называется хрупким (или ломким). Программист должен иметь возможность свободного изменения реализации базового класса с предоставлением тех же служб производным классам. (Разумеется, при внесении изменения в службы базового класса необходимо повторно реализовывать производные классы, однако качественное объектно-ориенти- рованное проектирование старается этого избегать.) Замечание по технологии программирования_______________________________________________ Самое подходящее время для использования модификатора доступа protected — когда базовый класс должен предоставить ту или иную службу своим производным классам (т. е. базовый класс не должен предоставлять эту службу другим клиентам). Замечание по технологии программирования_______________________________________________ Объявление переменных экземпляра базового класса типа private (вместо объявления их protected) позво- ляет программистам изменять реализацию базового класса без необходимости изменения реализации произ- водного класса. Совет по тестированию и отладке____________________________________________________________ По возможности избегайте включения данных типа protected в базовый класс. Лучше включите свойства и ме- тоды, не принадлежащие типу private, имеющие доступ к данным типа private, что обеспечит поддержание согласованного состояния объекта. Еще раз рассмотрим пример иерархии точечной окружности; на этот раз попробуем воспользоваться самой со- вершенной методикой программного проектирования. Используем класс Points (листинг 6.9), объявляющий переменные х и у как private и использующий свойства в методе Tostring для доступа к этим значениям. По- кажем, как производный класс Circle4 (листинг 6.10) может активизировать методы и свойства базового клас- са, не принадлежащие типу private, для манипулирования этими переменными. —-имутИЯС ----------------------————-— — • - <> ewwj Листинг 6.9. Класс РоалЪз использует свойства для манипулирования данными типа private ...... .....Сиоо*» * i.’ '• - б;**м4»*«* •«< 1 // Листинг 6.9: Point3.cs 2 // Класс Point3 содержит координаты (х, у) как protected-данные 3 4 using System; 5 6 // определение класса Point3 неявно наследует из Object 7 public class Point3 8 { * 9 // координаты точки 10 private int x, у; 11 12 // конструктор по умолчанию 13 public Point3() 14 { 15 // здесь имеет место неявное обращение к конструктору Object 16 } 17
184 Гпава 6 18 // конструктор 19 public Point3(int xValue, int yValue) 20 { 21 // здесь имеет место неявное обращение к конструктору Object 22 х = xValue; // использование свойства X 23 у = yValue; // использование свойства Y 24 ) 25 26 // свойство X 27 public int X. 28 { 29 get 30 { 31 return x; 32 ) 33 34 set 35 { 36 x = value; // нет необходимости аттестации 37 } 38 39 ) // конец свойства X 40 41 // свойство Y 42 public int Y 43 { 44 get 45 { 46 return у; 47 } 48 49 set 50 { . , 51 у = value; // нет необходимости аттестации 52 } 53 54 } // конец свойства у 55 56 // возвращение строкового представления Point3 57 public override string ToString() 58 { 59 return "[" + X + ”, ” + Y + "]"; 60 } 61 62 ) // конец класса Point3 ....- - .....:-y"- -•-..........—gcfr: Листинг 6.10. Класс Circle4, наследующий из класса Points, не предоставляющего данных типа protected i .UVMKW* . « в I ...>А'мм1 ----------....... ................................... •.чЪпооЫ». .„if-i* wmM xfeoteKffiiv > ИС'..- Г&- 0do&« -»Л b- f 'I >Mh ‘J • H-etel 1 // Листинг 6.10: Circle4.cs 2 // Класс Circle4, наследующий из класса Point3 3 4 using System; 5 6 // определение класса Circle4 наследует из Point3 7 public class Circle4 : Point3 8 { 9 private double radius; 10 11 // конструктор no умолчанию 12 public Circle4() 13 { 14 // здесь имеет место неявное обращение к конструктору Point3 15 } 16
Объектно-ориентированное программирование: наследование 185 17 // конструктор 18 public Circle4(int xValue, int yValue, double radiusValue) 19 : base(xValue, yValue) 20 { 21 Radius = radiusValue; 22 } 23 24 // свойство Radius 25 public double Radius 26 ( 27 get 28 ( 29 return radius; 30 } 31 32 set 33 { 34 if (value >= 0) // необходима аттестация действительности 35 radius = value; 36 } 37 38 • } // конец свойства Radius 39 40 11 расчет диаметра Circle 41 public double Diameter() 42 { 43 return Radius * 2; /./ использование свойства Radius 44 } 45 46 // расчет длины окружности Circle 47 public double Circumference() 48 { 4 9 return Math.PI * Diameter(); 50 } 51 52 // расчет площади Circle 53 public virtual double AreaO 54 { 55 return Math.PI * Math.Pow(Radius, 2);// использование 55a // свойства 56 } 57 58 // возвращение строкового представления Circle4 59 public override string'ToString() 60 { 61 // использование базовой ссылки для возвращения строкового 61а . // представления Point 62 return "Center= ’’ + base.ToStringO + 63 "; Radius = " + radius; // использование свойства Radius 64 } 65 66 } // конец класса Circle4 Замечание по технологии программирования___________________. ♦_____________________ По возможности пользуйтесь свойствами для изменения и получения значений переменных чл5йов, даже если эти значения могут быть изменены напрямую. Процедура доступа set свойства может предотвратить попытки присвоения недопустимого значения переменной этого члена, а процедура доступа get свойства может помочь в управлении представлением данных клиентам. Совет по повышению производительности______________________________________________ Использование для доступа к значению переменной свойства немного замедляет работу программы, нежели прямой доступ к данным. Однако часто оптимизация программ прямыми ссылками на данные не требуется, по- тому что компилятор оптимизирует программу неявно. (В настоящее время для неявного выполнения многих оп-
186 Глава 6 химизаций тщательно разрабатываются так называемые "оптимизирующие компиляторы", даже если програм- мист не пишет того, что кажется наиболее оптимальным кодом. Хорошим правилом является: "Не пытайтесь предугадать компилятор".) С целью демонстрации явного и неявного вызова конструкторов базового класса здесь включен второй конст- руктор, явно обращающийся к конструктору базового класса. В строках 18—22 объявляется конструктор Circle4, явно активизирующий второй конструктор Point3 (строка 19), с помощью синтаксиса вызова базового класса (т. е. ссылки base, за которой следует набор круглых скобок, содержащих аргументы конструктора базо- вого класса). В этом случае передаются xValue и yValue для инициализации элементов х и у типа private базо- вого класса. Символ двоеточия, за которым следует ключевое слово base, осуществляет явный доступ к версии базового класса этого метода (строка 19). Этим вызовом можно инициализировать х и у в базовом классе до особых значений, а не до о. Распространенная ошибка программирования________________________________________________ Если производный класс использует base для вызова конструктора базового класса с аргументами, не соответ- ствующими точно числу и типу параметров, указанных в одном из определений конструктора базового класса, то это является синтаксической ошибкой. Метод Tostring класса Circle4 (строки 59—64) подменяет метод ToString класса Points (строки 57—60 в лис- тинге 6.9). Как рассматривалось выше, подмена этого метода возможна, потому что метод Tostring класса System.Object (базовый класс класса Point3) объявлен virtual. Метод Tostring класса Circle4 отображает переменные private-экземпляра х и у класса Point3 вызовом метода Tostring базового класса (в этом случае метод Tostring класса Point3). Вызов делается в строке 62 через выражение base.Tostring о и вынуждает х и у стать частью строкового представления Circle4. Использование такого подхода является хорошим стилем про- граммирования: помните, что если метод объекта выполняет операции, необходимые другому объекту, то про- граммисту следует вызвать этот метод, а не копировать тело его кода. Дублирование кода создает проблемы его поддержки. Использование методом Tostring класса Circle4 форматирования, предоставленного методом Tostring класса Points, помогает избежать дублирования кода. Также метод Tostring класса Point3 выполняет часть задачи метода Tostring класса Circie4, поэтому из данного класса вызывается метод Tostring класса Points с выражением base.Tostring (). Распространенная ошибка программирования________________________________________________ Когда метод базового класса в производном классе подменяется, тогда версия производного класса часто вы- зывает версию базового класса для выполнения дополнительной работы. Неудачная попытка использования ссылки base при ссылке на метод базового класса вызывает бесконечную рекурсию, потому что в этом случае метод производного класса будет вызывать сам себя. Распространенная ошибка программирования____________________________-___________________ Использование "цепных" ссылок base для ссылок на элемент класса (метод, свойство и переменную) на не- сколько уровней вверх по иерархии (как в base.base.mx) является синтаксической ошибкой. Замечание по технологии программирования__________________________________________ Переопределение в производном классе метода базового класса, в котором используется иная сигнатура, неже- ли та, что применяется в методе базового класса, является перегрузкой, а не подменой метода. Замечание по технологии программирования___________________________________________ Несмотря на то, что метод Tostring можно подменить для выполнения произвольных операций, в сообществе С#.NET принято, что метод Tostring должен быть подменен для получения строкового представления объекта. Класс CircleTest4 (листинг 6.11) выполняет идентичные манипуляции классом Circle4, что и классы CircleTest (см. листинг 6.4) и circleTest3 (см. листинг 6.8). Обратите внимание, что выход всех трех модулей идентичен. Следовательно, несмотря на то, что каждый класс "окружности" ведет себя одинаково, класс Circle4 спроектирован корректнее всех. С помощью принципа наследования нам удалось эффективно спроек- тировать новый класс. 1 // Листинг 6.11: CircleTest4.cs 2 // Тестирование класса Circle4 3 4 using System; 5 using System.Windows.Forms; 6
Объектно-ориентированное программирование: наследование 187 7 // определение класса CircleTest4 8 class CircleTest4 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 { // главная точка входа для приложения static void Main(string[] args) { // создание (обработка) Circle4 Circle4“circle = new Circle4(37, 43, 2.5); // получение первоначальных координат x и у и радиуса Circle4 string output = "X coordinate is " + circle.X + "\n” + "Y coordinate is " + circle.Y + "\n" + "Radius is " + circle.Radius; // задание новых значений координатам х и у и радиуса Circle4 circle.X =2; circle.Y = 2; circle.Radius = 4.25; // отображение строкового представления Circle4 output += "\n\n" + "The new location and radius of circle are " + "\n" + circle + "\n"; // отображение диаметра Circle4 output += "Diameter is " + String.Format(”{0:F)", circle.Diameter()) + "\n"; // отображение длины окружности Circle4 output += "Circumference is " + String.Format(”{0:F)", circle.CircumferenceO) + "\n"; // отображение площади Circle4 output += "Area is " + String.Format("{0:F)", circle .Area()); MessageBox.Show(output, "Demonstrating Class Circle4"); } // конец метода Main } // конец класса CircleTest4 Рис. 6.7. Демонстрация классом CircleTest4 функциональности класса Circle4 Результат работы программы представлен на рис. 6.7. 6.5. Учебный пример: трехуровневая иерархия наследования Рассмотрим более значительный пример наследования, включающий в себя трехуровневую иерархию "точка — окружность— цилиндр". В разд. 6.4 созданы классы Points’(см. листинг 6.9) и Circle4 (см. листинг 6.10). Те- перь приведем пример, где из класса Circle4 выводится производный класс Cylinder. Первый используемый в контрольном примере класс— Point3 (см. листинг 6.9). Переменные экземпляра Points объявлены как private. Класс Points содержит свойства х и Y для доступа х и у, а также метод Tostring (который класс1 Points подменяет из класса object) для получения строкового представления пары координат Также был создан класс Circie4 (см. листинг 6.10), наследующий из класса Point3. Класс Circle4 содержит функциональность Point3, в дополнение к предоставлению свойства Radius, запрещающий переменной radius члена класса содержать отрицательное значение, а также методы Diameter, Circumference, Area и Tostring. Помните, что метод Area объявлен как virtual (строка 53). Как обсуждалось в разд. 6.4, данное ключевое слово позволяет подменить производным классам метод базового класса. Производные классы от Circie4 (например, класс Cylinder, который будет вскоре представлен) могут подменить эти методы и обеспечить особые реализа-
188 Глава 6 ции. Круг имеет площадь, рассчитываемую по формуле л/, где г — радиус окружности. Однако цилиндр имеет площадь поверхности, рассчитываемую по формуле (2лг2) + (2лгй), где г — радиус цилиндра, ah — высота ци- линдра. Таким образом, класс Cylinder должен подменить метод Area для включения этого расчета; поэтому метод Area класса Circle4 объявляется как virtual. В листинге 6.12 представлен класс Cylinder, наследующий из класса Circle4 (строка 7). Службы public класса Cylinder включают унаследованные методы Circle4 — Diameter, Circumference, Area и Tostring; унаследо- ванное свойство Radius класса Circie4; косвенно унаследованные свойства х и Y класса P*int3; конструктор класса Cylinder, свойство Height и метод Volume. Метод Area (строки 41—44) подменяет метод Area класса Circle4. Обратите внимание, что если класс Cylinder делает попытку подмены методов Diameter и Circumference класса Circle4, то имеют место синтаксические ошибки, потому что класс Circle4 не объявлял эти методы virtual. Метод Tostring (строки 53—56) подменяет метод Tostring класса Circie4 для получения строкового представления цилиндра. Класс Cylinder также включает в себя метод Volume (строки 47—50) для расчета объема цилиндра. По причине того, что метод Volume не объявляется как virtual, ни один производный класс класса Cylinder не может подменить этот метод. 1 // Листинг 6.12: Cylinder.cs 2 // Класс Cylinder наследует из класса Circle4 3 4 using System; 5 6 // определение класса Cylinder наследует из Circle4 7 public class Cylinder : Circle4 8 { 9 private double height; x 10 11 // конструктор по умолчанию 12 public Cylinder() 13 { 14 // здесь происходит неявное обращение к конструктору Circle4 15 } 16 17 // конструктор с четырьмя аргументами 18 public Cylinder(int xValue, int yValue, double radiusValue, 19 double heightvalue) : base(xValue, yValue, radiusValue) 20 { 21 Height = heightvalue; // установка высоты Cylinder 22 } 23 24 // свойство Height 25 public double Height 26 { 27 get 28 { 29 return height 30 } 31 , 32 set 33 { 34 if (value >= 0) 11 подтверждение корректности высоты 35 height = value; 36 } 37 38 } // конец свойства Height 39 40 // подмена метода Area класса Circle4 для расчета площади 40а // Cylinder 41 public override double AreaO 42 { 43 return 2 * base.AreaO + base.Circumference() * Height; 44 )
Объектно-ориентированное программирование: наследование 189 45 46 // расчет объема Cylinder 47 public double Volume() 48 { 49 return base. Area() * Height; 50 ) 51 52 // преобразование Cylinder в string 53 public override string ToString() 54 { 55 return base.ToString() + "; Height = " + Height; 56 } 57 58 } // конец класса Cylinder В листинге 6.13 представлено программное приложение CylinderTest, проверяющее класс Cylinder. В строках 17—19 используются свойства X, Y, Radius и Height для получения информации об объекте Cylinder, потому что CylinderTest не может сделать прямые ссылки на private-данные класса Cylinder. В строках 22—25 ис- пользуются свойства х, Y, Radius и Height для сброса координат (х,у) класса Cylinder (предполагается, что ко- ординаты (г, у) цилиндра задают его положение на плоскости ху), радиуса и высоты. Класс Cylinder может ис- пользовать свойства х и Y класса Point3, потому что класс Cylinder косвенно их наследует из класса Points — класса Cylinder прямо наследует свойства х и y из класса Circle4, который, в свою очередь, прямо наследует их из класса Points. В строке 29 неявно активизируется метод Tostring для получения строкового представления объекта Cylinder. В строках 33—37 активизируются методы Diameter и Circumference объекта Cylinder, пото- му что класс cylinder наследует эти методы из класса Circle4 и не может подменить эти методы, поскольку они активизированы, как показано в Circle4. В строках 41—45 активизируются методы Area и volume. С помощью примера "точка — окружность — цилиндр" продемонстрировано использование преимуществ на- следования. С его помощью классы Circie4 и Cylinder были созданы гораздо быстрее, чем если бы они проек- тировались "с нуля". Наследование избегает дублирования кода и связанных с ним проблем поддержки. 1 // Листинг 6.13: CylinderTest.cs 2 .11 Тестирование класса Cylinder 3 4 using System; 5 us ing System. Windows. Forms; 6 7 11 определение класса CylinderTest 8 class CylinderTest 9 { 10 // главная точка входа для приложения 11 static void Main(string[] args) 12 { 13 // создание объекта класса Cylinder 14 Cylinder cylinder = new Cylinder(12, 23, 2.5, 5.7); 15 16 // свойства принимают координаты x и у, радиус и высоту 17 string output = "X coordinate is " + cylinder.X + *\n" + 18 "Y coordinate is " + cylinder.Y + "\nRadius is " + 19 cylinder.Radius + "\n" + "Height is " + cylinder.Height; 20 21 // свойства задают новые координаты х и у, радиус и высоту 22 cylinder.X = 2; 23 cylinder.Y = 2; 24 cylinder.Radius = 4.25; 25 су Under. Height = 10; 26 ' 27 // получение новых координат х, у и радиуса 28 output += "\n\nThe new location, radius and height of " + 29 "cylinder are\n" + cylinder + "\n\n"; 30
190 Глава 6 31 // отображение диаметра Cylinder 32 output += "Diameter is " + 33 String.Format("{0:F}", cylinder.Diameter()) + "\n"; 34 35 // отображение длины окружности Cylinder 36 output += "Circumference is " + 37 String.Format(”{0:F}", cylinder.Circumference()) + "\n"; 38 39 // отображение площади Cylinder 40 output += "Area is " + 41 String.Format("{0:F}", cylinder.AreaO ); 42 43 // отображение объема Cylinder 44 output += "Volume is " + 45 String.Format(”{0:F}", cylinder.Volume()); 46 47 MessageBox.Show(output, "Demonstrating Class Cylinder"); 48 49 } // конец метода Main 50 51 } // конец класса CylinderTest w The new locator), redko endbeight of tymder-sre Ceoter-[2,21 Radus ^4.25; Г 1 ‘ ' Diameter is 8.50 fCjrcumfererce ts 26,70 Areais390.53 Volume is 567 45 Ycoordnate<?23 Г Radus ts 2.5 , Demonstrating (Joss Cylinder Рис. 6.8. Тестирование класса Cylinder Результат работы программы представлен на рис. 6.8. 6.6. Конструкторы и деструкторы в производных классах Как описывалось в предыдущем разделе, создание объекта производного класса начинает цепь обращений к конструктору, в которой конструктор производного класса, до выполнения поставленных перед ним задач, ак- тивизирует (явно или неявно) конструктор базового класса. Точно так же, если базовый класс является произ- водным другого класса, тогда конструктор базового класса должен активизировать конструктор следующего класса вверх по иерархии и т. д. Последний вызываемый конструктор в цепи является конструктором класса object, тело которого фактически завершает выполнение первым; тело первоначального производного класса заканчивает выполнение последним. Конструктор каждого базового класса инициализирует переменные экзем- пляра базового класса, которые наследует объект производного класса. Например, рассмотрим иерархию Point3/circle4 в листингах 6.9 и 6.10. Когда программа создает объект Circle4, вызывается один из конструк- торов Circle4. Этот конструктор вызывает конструктор класса Point3, который, в свою очередь, вызывает кон- структор класса Object. Когда конструктор класса object завершает выполнение, он возвращает управление конструктору класса Point3, который инициализирует координаты (x,j) класса Circle4. Когда конструктор класса Points завершает выполнение, он возвращает управление конструктору класса Circle4, который ини- циализирует радиус Circle4. Замечание по технологии программирования__________________________________________ Когда программа создает объект производного класса, конструктор этого производного класса сразу вызывает конструктор базового класса и выполняется тело этого конструктора, после чего выполняется тело конструктора производного класса Когда "сборщик мусора" удаляет из памяти объект производного класса, он вызывает деструктор этого объекта. Этим открывается цепочка обращений к деструкторам, где деструктор производного класса и деструкторы пря- мого и непрямого базовых классов выполняются в порядке, обратном порядку выполнения конструкторов. Вы- полнение деструкторов должно освободить все ресурсы, полученные объектом, до запроса "сборщиком мусора" памяти для этого объекта. Когда "сборщик мусора" вызывает деструктор объекта производного класса, деструк- тор выполняет свою задачу, после чего активизирует деструктор базового класса. Этот процесс повторяется до тех пор, пока не будет вызван деструктор класса object. Фактически, C# реализует деструкторы с помощью метода Finalize класса object (одного из восьми методов, наследуемых каждым классом в С#). При компиляции определения класса, содержащего деструктор, програм- ма-компилятор переводит определение деструктора в метод Finalize, выполняющий задачи деструктора, после чего активизирует метод Finalize базового класса как последний оператор в методе Finalize производного класса Как отмечалось в главе 5, нельзя с точностью определить, когда будет иметь место вызов деструктора, потому что нельзя с точностью выяснить момент "сбора мусора". Однако определением деструктора можно задать выполнение кода до того, как "сборщик мусора" удалит объект из памяти.
Объектно-ориентированное программирование: наследование 191 В следующем примере еще раз рассматривается иерархия "точка — окружность", определение класса Point4 (листинг 6.14) и класса Circles (листинг 6.15), содержащие конструкторы и деструкторы, каждый из которых при выполнении выводит сообщение. Класс Point4 (листинг 6.14) содержит свойства, показанные в листинге 6.1. Конструкторы были модифицирова- ны (строки 13—17 и 20—26) для вывода строки текста при их вызове, и добавлен деструктор (строки 29—32), также выдающий при его вызове строку текста. Каждый выходной оператор (строки 16, 25 и 31) добавляет в строку вывода ссылку this. При этом неявно активизируется метод Tostring класса для получения строкового представления координат Point4. 1 // Листинг 6.14: Point4.cs 2 // Класс Point4 представляет пару координат (х, у) 3 4 using System; 5 6 // определение класса Point4 7 public class Point4 8 { 9 // координаты точки 10 private int x, у; 11 12 // конструктор по умолчанию 13 public Point4() 14 { 15 // здесь имеет место неявное обращение к конструктору Object 16 Console.WriteLine("Point4 constructor: {0}", this); 17 ) 18 19 // конструктор 20 public Point4(int xValue, int yValue) 21 ( > 22 // здесь имеет место неявное обращение к конструктору Object 23 х = xValue; 24 у = yValue; 25 Console.WriteLine("Point4 constructor: {0}", this); 26 ) 27 28 // деструктор 29 - ~Point4() 30 ( 31 Console.WriteLine("Point4 destructor: {0}", this); 32 } 33 34 // свойство X 35 public int X 36 { 37 get 38 { 39 return x; 40 } 41 42 set 43 { 44 x = value; // нет необходимости контроля 45 } 46 47 } // конец свойства X 48 49 // свойство Y 50 public int Y
192 Глава 6 51 { 52 get 53 { 54 return у; 55 ) 56 57 set 58 { 59 у = value; // нет необходимости контроля 60 } 61 62 } // конец свойства у 63 64 // возвращение строкового представления Point4 . 65 public override string Tostring() 66 { 67 return "[" + x + ", " + у + "]"; 68 } 69 70 ) // конец класса Point4 Класс Circles (листинг 6.15) содержит особенности, приведенные в листинге 6.10; при этом для вывода строки текста два конструктора при их вызове были модифицированы (строки 12—16 и 19—24). Также был добавлен деструктор (строки 27—30), который выводит строку текста при его вызове. Каждый выходной оператор (стро- ки 15, 23 и 29) добавляет в строку вывода ссылку this. При этом неявно активизируется метод Tostring класса Circles для получения строкового представления координат и радиуса Circles. ................................ Листинг 6.15. Класс Circles наследует из класса Point4 и подменяет метод деструктора ........ •s’.ta'a" .^...ааа^ааа -tvatawa'd. .. -• -a .HaiatltniM.a.i - X.'.Ma aa . ... " 1 // Листинг 6.15: Circles.cs 2< // Класс Circles, наследующий из класса Point4 3 4 using System; 5 6 // определение класса Circles наследует из Point4 7 public class Circles : Point4 8 { 9 private double radius; 10 11 // конструктор по умолчанию 12 public Circles() 13 { 14 // здесь имеет место неявное обращение к конструктору Point4 15 Console.Writeline("Circles constructor: (0)", this); 16 ) 17 18 // конструктор 19 public Circles(int xValue, int yValue, double radiusValue) 20 : base(xValue, yValue) 21 { 22 Radius = radiusValue; 23 Console.Writeline("Circles constructor: {0}”, this); 24 } 25 26 // деструктор подменяет версию в классе Point4 27 -Circles() 28 { 29 Console.Writeline("Circles destructor: {0)", this); 30 } 31 32 // свойство Radius 33 public double Radius
Объектно-ориентированное программирование: наследование 193 34 { 35 get 36 { 37 return radius; 38 } 39 40 set 41 { 42 if (value >= 0) 43 radius = value; 44 } 45 46 } // конец свойства Radius 47 48 // расчет диаметра Circles 49 public double Diameter() 50 { 51 return Radius * 2; 52 } 53 54 // расчет длины дуги окружности Circles 55 public double Circumference() 56 . { 57 return Math.PI * Diameter(); 58 } 59 60 // расчет площади Circle5 61 public virtual double AreaO 62 { 63 return Math.PI * Math.Pow(Radius, 2); 64 } 65 66 // возвращение строкового представления Circle5 67 public override string Tostring() 68 { 69 11 использование базовой ссыпки для возвращения строкового 69 // представления Point3 70 return "Center® " + base.Tostring() + 71 "; Radius = " + Radius; 72 } 73 74 } // конец класса Circles Класс ConstructorAndFinalizer (листинг 6.16) демонстрирует порядок, в котором вызываются конструкторы и деструкторы для объектов классов, являющихся частью иерархии наследования классов. Метод Main (стро- ки 11—28) начинается созданием объекта класса Circles, после чего этот объект присваивается ссылке circlel (строка 16). При этом активизируется конструктор класса Circles, который сразу же активизирует конструктор класса Point4. Затем конструктор класса Point4 активизирует конструктор класса object. Когда конструктор класса Object (который ничего не распечатывает) возвращает управление конструктору Point4, тогда конструк- тор класса Point4 инициализирует координаты (х, у), после чего выдает строку, указывающую, что был вызван конструктор класса Point4. Выходной оператор также неявно вызывает метод Tostring (с помощью ссылки this) для получения строкового представления строящегося объекта. Далее управление возвращается конструк- тору класса Circles, инициализирующему радиус, и выдает координаты и радиус класса Circles неявным вызо- вом метода Tostring. Обратите внимание, что первые две строки вывода программы содержат значения для координат (х,у) и радиу- са объекта circlel класса Circles. При построении объекта Circles ссылка this, использованная в теле конст- рукторов Circles и Point4, относится к строящемуся объекту Circles. Когда программа активизирует на объек- те метод Tostring, то исполняемая версия Tostring всегда является версией, определенной в классе этого объ- екта. По причине того, что ссылка this относится к текущему строящемуся объекту Circles, метод Tostring класса Circles выполняется даже тогда, когда Tostring активизируется из тела конструктора класса Point4. 13 Зак. 3333
194 Гпава 6 Примечание__________________________________________________________________________ Не важно, что конструктор класса Point4 вызывается для инициализации объекта, фактически являвшегося но- вым объектом Point4. Когда конструктор класса Point4 активизирует метод Tostring для строящегося Circles, то программа отобра- жает значение радиуса — о, потому что тело конструктора Circles еще не инициализировало radius. Помните, что 0 — значение по умолчанию переменной double. Во второй строке выхода показано корректное значение radius (4.5), потому что эта строка выводится после инициализации radius. В строке 17 создается другой объект класса Circles, после чего присваивается ссылке circle2. При этом опять начинается цепочка вызовов конструкторов классов Circles, Point4 и Object. Обратите внимание, что на выхо- де конструктор класса Point4 выполняется до тела конструктора класса Circles. Этим демонстрируется, что объекты построены в обратном внутреннем порядке (т. е. конструктор базового класса вызывается первым). В строках 22 и 23 ссылкам circlel и circle2 задается значение null. При этом удаляются ссылки на эти объек- ты Circles в программе. Следовательно, "сборщик мусора" может освободить память, занимаемую этими объ- ектами. Помните, что нельзя точно сказать, ни когда сработает "сборщик мусора", ни то, соберет ли он все дос- тупные объекты при срабатывании. Для демонстрации вызовов деструктора для двух объектов Circles в стро- ке 26 активизируется метод Collect класса gc для запроса запуска "сборщика мусора". Обратите внимание, что деструктор каждого объекта Circles выдает информацию до вызова деструктора класса Point4. Объекты унич- тожаются в обратном внешнем порядке (т. е. деструктор производного класса завершает выполнение своих за- дач до активизации деструктора базового класса). Листинг 6.16 Порядок вызова конструкторов и деструкторов 1 // Листинг 6.16: ConstructorAndDestructor.es 2 // Отображение порядка, в котором вызываются конструкторы 3 4 //и деструкторы базового и производного классов 5 c using System; О 7 // определение класса ConstructorAndDestructor 8 class ConstructorAndDestructor 9 { 10 // главная точка входа для приложения 11 static void Main(string[] args) 12 { 13 14 Circles circlel, circle2; 15 // создание объектов 16 circlel = Circle5(72, 29, 4.5); 17 18 19 20 circle2 = Circles(5, 5, 10); Console.WriteLine(); 21 // пометить объекты для "сборщика мусора" 22 23 24 circlel = null; circle2 = null; 25 // сигнал "сборщику мусора" на исполнение 26 27 System.GC.Collect(); 28 29 } // конец метода Main 30 } // конец класса ConstructorAndDestructor Результат работы программы: Point4 constructor: Center = [72, 29]; Radius = О Circles constructor: Center = [72, 29]; Radius =4.5 Point4 constructor: Center = [5, 5]; Radius = 0 Circles constructor: Center = [5, 5]; Radius = 10
Объектно-ориентированное программирование: наследование У95 Circles destructor: Center = [5, 5]; Radius = 10 Point4 destructor: Center = [5, 5]; Radius = 10 Circles destructor: Center = [72, 29]; Radius = 4.5 Point4 destructor: Center = [72, 29]; Radius =4.5 6.7. Проектирование программ с наследованием В данном разделе рассматривается использование принципа наследования для "подгонки” существующих про- граммных средств. При использовании наследования для создания нового класса из существующего новый класс наследует переменные, свойства и методы членов существующего класса. Новый класс можно настроить под соответствие нужным требованиям путем включения дополнительных переменных, свойств и методов чле- нов класса, а также путем подмены членов базового класса. Иногда сложно оценить масштаб проблем, с которыми сталкиваются разработчики крупных промышленных систем. Те, кто имеют опыт работы над подобными проектами, говорят, что эффективное повторное использо- вание программных компонентов совершенствует общий процесс разработки. Объектно-ориентированное про- граммирование упрощает возможность многократного использования программных средств, что сокращает время на разработку. Язык C# стимулирует многократное использование программных компонентов предоставлением библиотеки классов .NET Framework (FCL), обладающей максимальными преимуществами многократного (повторного) использования программных средств через наследование По мере роста интереса к C# повышается и интерес к библиотекам классов FCL. Во всем мире признана необходимость непрерывного совершенствования библиотек классов FCL д ля большого разнообразия программных приложений. По мере развития .NET будет расширяться и FCL. Замечание по технологии программирования_______________________________________________ На этапе проектирования объектно-ориентированной системы разработчик часто определяет, что конкретные классы тесно связаны между собой. Проектировщик должен "факторизовать" общие атрибуты и поведения и размещать их в базовом классе. После этого следует воспользоваться принципом наследования для образова- ния производных классов с проектированием их возможностей, выходящих за рамки унаследованных от базово- го класса Замечание по технологии программирования_______________________________________________ Создание производного класса не влияет на исходный код базового класса. Наследование сохраняет целост- ность базового класса. Замечание по технологии программирования_______________________________________________ Так же, как разработчики необъектно-ориентированных систем должны избегать чрезмерного обилия функций, так и разработчики объектно-ориентированных систем должны избегать большого количества классов, потому что это часто приводит к появлению проблем управления ими и может помешать повторному использованию программных компонентов, т к. клиенту становится затруднительно находить самые необходимые классы в ог- ромных библиотеках. Альтернатива: создание меньшего числа классов, каждый из которых обладает значитель- ной функциональностью, а функциональность эта может быть и чрезмерной. Совет по повышению производительности__________________________________________________ Если созданные посредством наследования классы объемнее, чем следует (т е. содержат слишком большой объем функциональности), то это может привести к нецелесообразному расходу памяти и ресурсов обработки Наследовать нужно из класса, функциональность которого максимально "приближена" к необходимой. Прочтение определений производных классов может быть запутанным, потому что унаследованные члены классов физически не показаны в производном классе, но, тем не менее, присутствуют в нем. Аналогичная про- блема возникает при документировании элементов производных классов. В данной главе представлен принцип наследования — возможности создания классов путем поглощения эле- ментов данных и поведений существующего класса и присвоения им новых возможностей. Глава 7 построена на рассмотренном принципе наследования, и в ней вводится понятие полиморфизма — методики объектно-ориен- тированного программирования, позволяющая создавать приложения, управляющие, так сказать, большим раз- нообразием классов, связанных наследованием. В следующей главе читатели более подробно познакомятся с инкапсуляцией, наследованием и полиморфизмом — наиболее важными аспектами объектно-ориентированного программирования.
196 Гпава 6 6.8. Резюме Наследование — это процесс, при котором один класс "поглощает" возможности существующего класса. Но- вый класс, создаваемый наследованием возможностей существующего класса, называется производным клас- сом. Класс, из которого осуществляется наследование, называется базовым. По причине того, что производные классы могут включать в себя собственные переменные, свойства и методы, они часто имеют больший размер, нежели их базовые классы. Производный класс более специфичен, нежели его базовый класс, и представляет меньшую группу объектов. Наследование — это отношение типа "является”, т. е. объект производного класса так же можно рассматривать, как объект его базового класса. Однако объекты базового класса не являются объектами его производных клас- сов. Композиция — это отношение типа "имеет", т. е. объект класса ссылается на один или более объектов дру- гих классов как на их элементы. Нестатические public-члены базового класса доступны везде, где программа делает ссылку на объект этого ба- зового класса, или на объект одного из производных классов от этого базового класса. Доступ к private-членам базового класса осуществляется только в рамках определения этого базового класса. Члены типа protected ба- зового класса имеют промежуточный уровень защиты между доступом public и private. Доступ к protected- членам базового класса может быть осуществлен только в этом базовом классе или в любых классах, производ- ных от этого базового класса. Доступ к internal-членам базового класса может быть осуществлен только объ- ектами того же компоновочного блока. Когда метод базового класса не подходит производному классу, тогда этот член базового класса можно подме- нить (переопределить) в производном классе с ‘помощью соответствующей реализации. Когда метод, подменен- ный в производном классе, вызывается к объекту производного класса, тогда вызывается версия производного, а не базового класса. При создании производного класса сразу вызывается конструктор базового класса (явно или неявно) для осуще- ствления необходимой инициализации переменных экземпляра базового класса в объекте производного класса (до инициализации переменной экземпляра производного класса). Конструкторы и деструкторы базового класса не наследуются производными классами.
ГЛАВА 7 (ш Объектно-ориентированное программирование: полиморфизм Одно кольцо, чтобы повелевать ими, одно кольцо, чтобы отыскать их, одно кольцо, чтобы собрать их и связать в темноте. Джон Рональд Рейел Толкиен Общие суждения не решают конкретных дел. Оливер Венделл Холмс Выдающийся философ не мыслит в вакууме. Даже самые абстрактные его идеи в определенной степени обусловлены тем, что известно или не известно в эпоху его существования. Альфред Норт Уайтхед Темы данной главы: □ понятие концепции полиморфизма; □ механизмы расширения и поддержки компьютерных систем с помощью полиморфизма; О различия между абстрактными и конкретными классами; □ создание классов, интерфейсов и делегатов типа sealed. 7.1. Введение При обсуждении объектно-ориентированного программирования (ООП) в предыдущей главе особое внимание было уделено одной из ключевых технологий ООП — наследованию. В данной главе будет рассмотрена кон- цепция полиморфизма ООП. При разработке сложных программных средств наследование и полиморфизм яв- ляются важнейшими технологиями. Полиморфизм позволяет создавать программы, управляющие большим раз- нообразием связанных классов родовыми отношениями, а также упрощает добавление в систему новых классов и возможностей. С помощью полиморфизма становятся возможными проектирование и реализация легко расширяемых систем. Программы могут обрабатывать объекты всех классов в иерархии классов "в общем виде", как объекты общего базового класса. Более того, новые классы можно добавлять с минимальными изменениями (или вообще без изменений) общих частей программы до тех пор, пока эти классы являются частью иерархии наследования, ко- торую программа обрабатывает в общем виде. Для ввода в программу новых классов изменениям должны под- вергаться только те ее компоненты, что требуют непосредственного знания новых классов, которые програм- мист добавляет в иерархию. В данной главе демонстрируются две значительные иерархии классов и осуществ- ляются полиморфные манипуляции объектами этих иерархий. 7.2. Преобразование объекта производного класса в объект базового класса В разд. 6.4 была создана иерархия "точка— окружность", в которой класс circle наследовал из класса Point. Программы, манипулирующие объектами этих классов, всегда использовали ссылки на объекты класса Point и ссылки circle для ссылок на объекты класса circle. В данном разделе рассматриваются взаимоотношения ме- жду классами в иерархии, позволяющие программам присваивать объекты производных классов ссылкам на базовый класс — фундаментальной части программ, обрабатывающих объекты полиморфно. В данном разделе также изучается явное преобразование между типами в иерархии классов.
198 Гпава 7 Объект производного класса можно рассматривать как объект его базового класса. Это дает возможность осу- ществления интересных действий. Например, программа может создать массив ссылок базового класса, отно- сящихся к объектам многих типов производных классов. Это обеспечивается, несмотря на тот факт, что объек- ты производного класса принадлежат к разным типам данных. Впрочем, обратное утверждение не верно: объект базового класса не является объектом какого бы то ни было его производного класса. Например, Point не явля- ется Circle в иерархии, определенной в главе 6. Если ссылка базового класса сделана на объект производного класса, то ссылку базового класса можно преобразова гь в фактический тип данных этого объекта и манипули- ровать им, как объектом заданного типа. Пример в листингах 7.1—7.3 демонстрирует присвоение объектов производного класса ссылкам базового клас- са и преобразование ссылок базового класса в ссылки производного класса. Класс Point (листинг 7.1), рассмат- ривавшийся в главе б, представляет пару координат (х,у). Класс circle (листинг 7.2), также рассмотренный в главе 6, представляет окружность и наследует от класса Point. Каждый объект класса Circle "является" объек- том класса Point и также имеет радиус (представленный с помощью свойства Radius). Метод Area объявлен как virtual, так что производный класс (например, класс Cylinder) может подменить метод Area для расчета пло- щади объекта производного класса. Класс PointCircleTest (листинг 7.3) демонстрирует операции присвоения и преобразования. Листинг 7,1 Класс Pnxnt представляет пару координат (х, у) ;‘ , 'V; у •/”] 1 // Листинг 7.1: Point.cs 2 // Класс Point представляет пару координат (х, у) 3 4 using System; 5 6 // Определение класса Point неявно наследует из Object 7 public class Point 8 { 9 // координаты точки 10 private int x, у; 11 12 // конструктор по умолчанию (без аргументов) 13 public Point() 14 { 15 // здесь происходит неявное обращение к конструктору Object 16 } 17 18 // конструктор 19 public Point(int xValue, int yValue) 20 { 21 // здесь происходит неявное обращение к конструктору Object 22 X = xValue ; 23 Y = yValue; 24 ) 25 26 // свойство X 27 public int X 28 { 29 get 30 { 31 return x; 32 } 33 34 set 35 { 36 x = value; // нет необходимости контроля 37 } 38 39 } // конец свойства X 40 41 // свойство Y 42 public int Y;
Объектно-ориентированное программирование полиморфизм 199 43 { 44 get 45 { 4 6 return у; 47 } 48 49 set 50 { 51 у = value; // нет необходимости контроля 52 } 53 54 ) // конец свойства Y 55 56 // возвращение строкового представления Point 57 public override string ToString() 58 { 59 return "[" + x + ", " + у + •]"; 60 ) 61 62 } // конец класса Point «”• • ч ч • -*йр • • jgs'.'/g Листинг 7.2. Класс Circle, Hacnej /юЩий из класса Point ...................................................................____________.‘..ju.,;.! 1 // Листинг 7.2: Circle.cs 2 // Класс Circle, наследуй ций из класса Point 3 '4 using System; 5 6 11 определение класса Circle наследует из Point 7 class Circle : Point 8 { 9 private double radius; // радиус окружности 10 11 // конструктор по умолчанию 12 public Circlet) 13 { 14 // здесь имеет место неявное обращение к конструктору Point 15 ) 16 17 // конструктор 18 public Circle(int xValue, int yValue, double radiusValue) 19 : base(xValue, yValue) 20 { 21 Radius = radiusValue; 22 } 23 24 // свойство Radius 25 public double Radius 26 { 27 get 28 { 29 return radius; 30 } 31 32 set 33 { 34 if (value >e 0) // подтвердить корректность значения радиуса 35 radius value; 36 ) 37 38 } // конец свойства Radius 39
200 Гпава 7 40 // расчет диаметра Circle 41 public double Diameter() 42 { 43 return Radius * 2; 44 } 45 46 // расчет длины окружности Circle 47 public double Circumference() 48 { 49 return Math.PI * Diameter(); 50 } 51 52 // расчет площади Circle 53 public virtual double Area() 54 { 55 return Math.PI * Math.Pow(Radius, 2); 56 } 57 58 // возвращение строкового представления Circle 59 public override spring Tostring() 60 { 61 return "Center = " + base.ToStringO + 62 "; Radius = " + Radius; 63 } 64 65 } // конец класса Circle В классе PointcircieTest (листинг 7.3) демонстрируется присвоение ссылок производного класса ссылкам ба- зового класса и преобразование ссылок базового класса в ссылки производного. В строках 13 и 14 объявляется ссылка класса Point (pointl) и ссылка класса Circle (circlel). В строках 16 и 17 прибавляются строковые представления каждого объекта string output для показа значений, использованных для инициализации этих объектов. По причине того, что pointl является ссылкой на объект класса Point, метод Tostring ссылки pointl распечатывает объект как Point. Точно так же, что circlel является ссылкой на объект класса circle, метод Tostring ссылки circlel распечатывает объект как circle. 1 // Листинг 7.3: PointCircleTest.cs 2 // Демонстрация наследования и полиморфизма 3 4 using System; 5 using System.Windows.Forms; 6 7 // определение класса PointCircleTest 8 class PointcircieTest 9 { 10 // главная точка входа для приложения 11 static void Main(string[] args) 12 { ' 13 Point pointl = new Point(30, 50); 14 Circle circlel = new Circle(120, 89, 2.7); 15 16 string output = "Point pointl: " + pointl.ToString() + 17 "\nCircle circlel: " + circle.ToString(); 18 19 // использование отношения "является" ("is а") для присвоения 20 // Circle circlel ссылке Point 21 Point point2 = circlel; 22 23 output += "\n\nCircle circlel (via point2) : " + 24 point2.ToString(); 25 /
Объектно-ориентированное программирование: полиморфизм 201 26 27 27а 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 // нисходящее преобразование (преобразование ссылки базового // класса в тип данных производного класса) point2 в Circle // circle2 Circle circle2 = (Circle) point2> output += "\n\nCircle circlel (via circle2) : " + circle2.ToString(); output += "\nArea of circlel (via circle2) : ” + circle2.Area().ToString("F"); // попытка присвоения объекта pointl ссылке Circle if (pointl is Circle) { circle2 = (Circle) pointl; output += ”\n\ncast successful"; ) else { output += "\n\npointl does not refer to a Circle”; | OeRontUating the ‘w a* lelaborrehip | Point pointl:{30.50J I Circle drctel: Center» [120. OStRednj»» 27 • - * ’ Circle circlel (vie port2) Leiter • [120.69]' R j >-r > Circle arclel (via ™‘^e?1 Center • [120 b9[ Radio» • Area el circlel [via Mde2) 22 90 peril does ru refer to a Circle Рис. 7.1. Демонстрация присвоения ссылок производного класса ссылкам базового класса MessageBox.Show(output, * "Demonstrating the 'is a' relationship"); } // конец метода Main } // конец класса PointCircleTest В строке 21 присваивается circlel (ссылка на объект производного класса) pointl (ссылке базового класса) В C# приемлемо присвоение объекта производного класса ссылке базового класса, потому что наследование принадлежит к типу "является" (is а"). Класс Circle наследует от класса Point, потому что Circle является Point (по крайней мере, в структурном смысле). Однако, как будет показано далее, присвоение ссылки базового класса ссылке производного класса потенциально опасно. В строках 23 и 24 активизируется класс point2. Tostring, и результат добавляется к output (выводимый резуль- тат), Когда C# сталкивается с активизацией метода типа virtual (например, метода Tostring), C# определяет, какую версию метода вызвать из типа объекта, на котором вызывается метод, а не тип ссылки, относящейся к объекту. В этом случае point2 относится к объекту circle, поэтому C# вызывает метод Tostring класса Circle, а не метод Tostring класса Point (как можно ожидать от ссылки point2, которая была объявлена как тип Point). Решение о том, какой метод вызвать, представляет собой пример полиморфизма — концепции, подробно рас- сматриваемой в данной главе. Обратите внимание, что если pointl сделала ссылку на объект Point, а не объект Circle, C# активизирует метод Tostring Point. Результат работы программы представлен на рис. 7.1. В предыдущих главах для преобразования разных встроенных типов C# использовались такие методы, как int32.Parse и Double.Parse. Теперь осуществим преобразование между ссылками на объекты типов, опреде- ленных программистом. Для этого применяется явное преобразование. Если преобразование допустимо, то про- грамма может рассматривать ссылку базового класса как ссылку производного класса. Если преобразование недопустимо, тогда C# выдает сообщение invalidCastException, что указывает на запрет операции преобразо- вания. Подробно исключения рассматриваются в главе 8. Распространенная ошибка программирования_____________________________________________ Присвоение объекта базового класса (или ссылки базового класса) ссылке производного класса (без явного пре- образования) является синтаксической ошибкой. Замечание по технологии программирования___________________________________________ Если объект производного класса был присвоен ссылке одного из его прямых или непрямых базовых классов, то можно преобразовать эту ссылку базового класса назад в ссылку типа производного класса На самом деле это может быть сделано для отправки сообщений об объектах, не появляющихся в базовом классе.
202 Глава 7 Примечание__________________________________________________________________________________ Для обозначения активизации методов и использования свойств объектов авторы иногда применяют термин "сообщения". В строке 28 отображается point2, которая в настоящее время относится к объекту Circle (circlel), и присваи- вает результат circie2. Очень скоро мы увидим, что такое преобразование будет опасным, если бы point2 де- лала ссылку на Point. В строках 30 и 31 активизируется метод Tostring объекта circle, на который теперь ссы- лается circle2 (обратите внимание, что четвертая строка вывода демонстрирует вызов метода Tostring класса Circle). В строках 33 и 34 выполняется расчет и выдается значение Area circle2. В строке 39 явно преобразуется ссылка pointl в Circle. Это опасная операция, потому что point ссылается на объект Point, a Point не является Circle. Объекты могут преобразовываться только в их собственный тип или в типы их базового класса. Если бы выполнилось это утверждение, то компилятор языка C# решил бы, что pointl ссылается на объект Point, счел бы преобразование в circle опасным и указал на некорректное преобразование с сообщением InvalidCastException. Однако разработчики предотвращают выполнение этого выражения от выполнения включением структуры if/eise (строки 37—45). Условие в строке 37 использует ключевое слово is для определения, находится ли объект, на который ссылается pointl, в отношении "является Circle". Клю- чевое слово is открывает тип объекта, на который ссылается левый операнд, и сравнивает этот тип с правым операндом (в данном случае Circle). В нашем примере pointl не ссылается на Circle, поэтому условие не вы- полняется, и строка 44 прибавляет к output строку, указывающую результат. Обратите внимание, что сравнение is будет true (верным), если левый операнд является ссылкой на экземпляр правого операнда или на производ- ный класс. Распространенная ошибка программирования____________________________________________________ Попытка преобразования ссылки базового класса в тип производного класса вызывает InvalidCastException, если ссылка относится к объекту базового класса, а не к объекту соответствующего производного класса. Замечание по технологии программирования____________________________________________________ Ключевое слово is позволяет программе определить, будет ли успешной операция преобразования, в которой совместимы тип ссылки и целевой тип. Если удалить проверку if и выполнить программу, то C# отобразит окно MessageBox с сообщением: An unhandled exception of type ’System.InvalidCastException' occurred in (Произошла необработанная исключительная ситуация типа System.InvalidCastException) за которым следует имя и путь исполнения программы. В главе 8 будет рассмотрена обработка таких ситуаций. Несмотря на тот факт, что объект производного класса также "является" объектом базового класса, объекты производного и базового классов различны. Как упоминалось ранее, объекты производного класса можно рас- сматривать так, будто они являются объектами базового класса. Это — логическое отношение, потому что про- изводный класс содержит члены, соответствующие всем членам в базовом классе, однако производный класс может иметь дополнительные члены. По этой причине присвоение объекта базового класса ссылке производно- го класса недопустимо без явного преобразования. Такое присвоение оставит дополнительные члены производ- ного класса неопределенными. Существует четыре способа смешения ссылок базового и производного класса с объектами базового и произ- водного класса: О ссылка на объект базового класса является прямой ссылкой базового класса; □ ссылка на объект производного класса является прямой ссылкой производного класса; О ссылка на объект производного класса ссылкой базового класса безопасна, потому что объект производного класса является объектом его базового класса. Однако такая ссылка может относиться только к членам ба- зового класса. Если код относится только к членам производного класса через ссылку базового класса, тогда компилятор выдает сообщение об ошибке; □ ссылка на объект базового класса ссылкой производного класса создает ошибку компилирования. Во избе- жание этой ошибки ссылка производного класса сначала должна быть явно преобразована в ссылку базового класса. При этом преобразовании ссылка производного класса должна ссылаться на объект производного же класса; в противном случае C# сгенерирует исключение invalidCastExpection. Распространенная ошибка программирования____________________________________________________ После присвоения объекта производного класса ссылке базового класса попытка сделать ссылку на члены толь- ко производного класса ссылкой базового класса является ошибкой компиляции.
Объектно-ориентированное программирование: полиморфизм 203 Распространенная ошибка программирования________________________________________ Рассмотрение объекта базового класса как объекта производного класса вызывает появление ошибок. Хотя рассматривать объекты производных классов как объекты базовых объектов удобно путем манипуляций объектами производного класса через ссылки базовых классов, все эти процедуры могут спровоцировать значи- тельные проблемы. Например, система начисления заработной платы должна быть способной прослеживать массив служащих и рассчитывать недельную зарплату каждого из них. Интуиция подсказывает, что использова- ние ссылок базовых классов позволит программе вызывать только рутинные операции расчетов заработной платы базового класса (если такие операции существуют в базовом классе). Используя ссылки только базового класса, можно активизировать необходимую рутинную операцию расчета заработной платы для каждого объек- та, независимо от того, является он объектом базового или производного класса. В материалах главы будет рас- смотрен процесс создания классов, демонстрирующий подобное поведение, по мере описания полиморфизма. 7.3. Типы объектов и оператор switch " / Одним из способов определения типа объекта, входящего в крупную программу, является использование опера- тора switch. Это позволяет различать типы объектов при активизации нужной операции для того или иного объекта. Например, в иерархии форм, где каждый объект формы имеет свойство shapeproperty, оператор switch может использовать Shapeproperty объекта для определения, к какому методу Print обращаться. Однако использование логики switch подвергает программы различным потенциальным опасностям. Напри- мер, программист может забыть включить стандартное испытание при наличии гарантий, либо может забыть протестировать все возможные случаи в операторе switch. При модификации системы на базе switch путем добавления новых типов программист может забыть вставить новые случаи во все актуальные операторы switch. Каждое добавление или удаление класса требует модификации каждого оператора switch в системе; прослеживание этих операторов может занимать много времени и быть подверженной ошибкам. Замечание по технологии программирования________________________________________ Полиморфное программирование может устранить необходимость в ненужной логике switch. Использованием механизма полиморфизма C# для выполнения эквивалентной логики программисты могут избежать ошибок, обычно связанных с логикой switch. Совет по тестированию и отладке_________________________________________________ Интересным последствием использования полиморфизма является более упрощенный вид программ Они со- держат менее разветвленную логику и более простой последовательный код. Такое упрощение облегчает про- цесс тестирования, отладки и технической поддержки программы. 7.4. Примеры полиморфизма В данном разделе рассматривается несколько примеров полиморфизма. Если класс Rectangle является произ- водным от класса Quadrilateral, тогда объект Rectangle представляет собой более специфическую версию объекта Quadrilateral. Любую операцию (например, расчет периметра площади), которую можно выполнить на объекте класса Quadrilateral, также можно выполнить на объекте класса Rectangle. Такие операции можно выполнять и на других типах Quadrilateral, например, на Square, Parallelogram и Trapezoid. Когда программа активизирует метод производного класса через ссылку базового класса (т. е. Quadrilateral), C# полиморфно выбирает корректный способ подмены в производном классе, из которого был получен объект. В последующих примерах данное поведение будет рассмотрено более подробно. Предположим, что нужно спроектировать видеоигру, в которой манипуляции осуществляются с объектами мно- гих типов, включая объекты классов Martian (марсиане), Venutian (венерианцы), Plutonian (плутонцы), Spaceship (космический корабль) и LaserBeam (бластер). Также, представим, что каждый из этих классов насле- дует из общего базового класса под названием Spaceobject, содержащего метод DrawYourself. Этот метод реа- лизует каждый производный класс. Программа управления экраном будет поддерживать контейнер (например, массив spaceobject) ссылок на объекты различных классов. Для обновления экрана программа управления им будет периодически направлять каждому объекту одно и то же сообщение — DrawYourself. Впрочем, каждый объект реагирует на это сообщение по-своему. Например, объект Martian будет изображен красным цветом с нужным количеством антенн. Объект Spaceobject будет изображен в виде светящейся серебристой летающей тарелки. Объект LaserBeam изобразится в виде яркого красного луча через весь экран. Таким образом, одно и то же сообщение, отправленное разным объектам, будет иметь "много форм" результатов, отсюда и термин — по- лиморфизм.
204 Гпава 7 Полиморфная программа управления экраном упрощает добавление в систему новых классов с минимальными изменениями системного кода. Предположим, что в разрабатываемую видеоигру нужно добавить класс Mercurian. Для этого необходимо создать класс Mercurian, наследующий из Spaceobject, но обеспечивающий собственное определение метода DrawYourself. Затем, когда объекты класса Mercurian появляются в контейне- ре, программисту не нужно модифицировать код для программы управления экрана. Программа управления экрана активизирует метод DrawYourself на каждом объекте в контейнере, независимо от типа объекта, поэтому новые объекты Mercurian просто "подключаются". Таким образом, без модификации системы (за исключением построения и включения в нее новых классов) программисты могут пользоваться полиморфизмом для включе- ния классов дополнительных типов, не планировавшихся при создании самой системы. С помощью полиморфизма один метод может активизировать разные операции, в зависимости от типа объекта, на котором активизирован данный метод. Это дает разработчикам колоссальные выразительные возможности. В нескольких последующих разделах представлены другие примеры, демонстрирующие полиморфизм. Замечание по технологии программирования____________________________________________ С помощью полиморфизма программист может работать с универсальными процессами, предоставляя возмож- ность программе во время исполнения самостоятельно следить за конкретными ситуациями. Разработчик может дать команду большому количеству объектов для демонстрации поведения, присущего этим объектам, даже не зная типов этих объектов. Замечание по технологии программирования____________________________________________ Полиморфизм повышает расширяемость программного приложения. Программные средства, где активизируется полиморфное поведение, создаются независимыми от типов объектов, которым отправляются сообщения (т. е вызовы методов). Следовательно, разработчики могут добавлять в систему дополнительные типы объектов, реагирующие на существующие сообщения, без изменений базовой системы. 7.5. Абстрактные классы и методы При рассмотрении класса как типа предположим, что программы будут создавать объекты этого типа. Однако бывают случаи, когда полезно определять классы, для которых программист не планирует создания никаких объектов. Такие классы называются абстрактными. По причине того, что такие классы, как правило, исполь- зуются в иерархиях наследования в качестве базовых классов, они называются абстрактными базовыми клас- сами. Их нельзя использовать для создания объектов, поскольку абстрактные классы — неполные. Производные классы должны определять "недостающие части". Обычно абстрактные классы содержат один или несколько абстрактных методов или абстрактных свойств, являющиеся методами и свойствами, не обеспечивающих реализаций. Производные классы должны подменять унаследованные абстрактные методы и свойства для того, чтобы создать объекты этих производных классов. Расширенно абстрактные классы рассматриваются в разд. 7.6 и 7.8. Целью абстрактного класса является создание соответствующего базового класса, из которого другие классы могут осуществлять наследование. Классы, из которых могут быть созданы объекты, называются конкретными. Такие классы обеспечивают реализации каждого метода и свойства, ими определенные. Можно иметь абстракт- ный базовый класс TwoDimensionalObject и получить из него такие конкретные классы, как Square, Circle и Triangle. Также, имея абстрактный базовый класс ThreeDimensionalObject, из него можно получить такие кон- кретные классы, как Cube, Sphere и Cylinder. Абстрактные базовые классы очень близки друг к другу и принад- лежат одной группе для определения реальных объектов; перед созданием объектов необходимо все определить максимально конкретно. Например, если кто-то предлагает вам "нарисовать форму", какую форму вы нарисуе- те? Конкретные классы обеспечивают специфику, обуславливающую создание объектов. Класс становится абстрактным, если он объявляется с ключевым словом abstract. Иерархии наследования не обязательно содержать абстрактные классы, но, как будет видно далее, многие качественные объектно- ориентированные системы имеют иерархии классов, возглавляемые абстрактными базовыми классами. В неко- торых случаях абстрактные классы образуют несколько верхних уровней иерархии. Хорошим в этом смысле примером является иерархия форм, представленная на рис. 6.2. Эта иерархия начинается с абстрактного базово- го класса shape. На следующем уровне иерархии расположены два других абстрактных базовых класса — TwoDimensionalShape и ThreeDimensionalShape. Следующий уровень иерархии будет определять конкретные классы для двумерных фигур, например Circle и Square, и для трехмерных, например Sphere и Cube. Замечание по технологии программирования____________________________________________ Абстрактный класс определяет общий набор методов и свойств типа public для различных членов иерархии классов Обычно абстрактный класс содержит один или несколько абстрактных методов и свойств, которые бу- дут подменены производными классами Все классы в иерархии могут использовать этот общий набор методов и свойств public.
Объектно-ориентированное программирование: полиморфизм 205 Абстрактные классы должны задавать сигнатуры для их абстрактных методов и свойств. C# предоставляет клю- чевое слово abstract для объявления метода или свойства как абстрактного. Методы и свойства, принадлежа- щие к типу abstract, не предоставляют реализаций; все попытки приводят к синтаксическим ошибкам. Каждый конкретный производный класс должен подменить все методы и свойства abstract базового класса (с помощью ключевого слова override) и обеспечить конкретные реализации этих методов или свойств. Любой класс, со- держащий метод abstract, должен быть объявлен как abstract. Разница между методом abstract и методом virtual заключается в том, что метод virtual имеет имплементацию и обеспечивает производный класс опцией подмены метода; метод abstract, наоборот, — не предоставляет реализации и вынуждает производный класс подменять метод (для того, чтобы этот производный класс стал конкретным). Распространенная ошибка программирования_________________________________________________ Результатом определения метода abstract в классе, который не объявлен как abstract, становится синтакси- ческая ошибка. Распространенная ошибка программирования________________________________________________ Результатом попытки создания объекта класса abstract является ошибка компиляции. Распространенная ошибка программирования________________________________________________ Сбой подмены метода abstract в производном классе является синтаксической ошибкой, если производный класс также находится в классе типа abstract. Замечание по технологии программирования________________________________________________ Класс abstract может иметь данные экземпляра и не абстрактные методы (включая конструкторы), которые подчиняются обычным правилам наследования производных классов. Несмотря на то, что создавать объекты абстрактных базовых классов нельзя, абстрактные базовые классы мож- но использовать для объявления ссылок, которые могут обращаться к экземплярам многих конкретных классов, производных от абстрактного класса. Программы могут полиморфно использовать такие ссылки для манипули- рования экземплярами производных классов. Рассмотрим еще одно применение полиморфизма. Программе управления экраном необходимо отобразить множество объектов, включая новые типы объектов, которые программист добавляет в систему после написа- ния этой программы. Системе может потребоваться отображение различных форм, например, Circle, Triangle или Rectangle, производных от абстрактного класса shape. Программа управления экраном использует ссылки базового класса типа shape для управления отображаемыми объектами. Для изображения любого объекта (неза- висимо от уровня, на котором класс этого объекта появляется в иерархии наследования) программа управления экраном использует ссылку базового класса на этот объект для активизации метода Draw этого объекта. Метод Draw — это метод abstract в базовом классе shape; следовательно, каждый производный класс должен реализо- вывать метод Draw. Каждый объект shape в иерархии наследования "знает", как изобразить самого себя. Про- грамме управления экраном не стоит беспокоиться о типе каждого объекта либо о том, сталкивалась программа управления экраном с объектами этого типа или нет. Полиморфизм особо эффективен для реализации многоуровневых компьютерных систем. Например, в операци- онных системах каждый тип физического устройства может функционировать отлично от всех прочих. Даже в этом случае команды считывания данных с устройств и записи на них могут обладать определенным единооб- разием. Команда на запись, отправленная на объект драйвера устройства, должна быть интерпретирована особо в контексте этого драйвера устройства и в контексте того, как драйвер этого устройства манипулирует устрой- ствами особого типа. Однако сама команда на запись ничем не отличается от записи на любое другое устройст- во в системе: достаточно отправить определенное количество байтов из памяти на это устройство. В объектно- ориентированной операционной системе для обеспечения интерфейса, подходящего для всех драйверов уст- ройств, может использоваться абстрактный базовый класс. Затем посредством наследования из этого абстракт- ного базового класса образуются производные классы, функционирующие сходным образом. Возможности (т. е. службы типа public), предлагаемые драйверами устройств, предоставляются в абстрактном базовом классе как абстрактные методы. Реализации этих абстрактных методов представлены в производных классах, соответ- ствующих особым типам драйверов устройств. В объектно-ориентированном программировании обычно определяется класс-итератор, который может про- слеживать все объекты в контейнере (например, массив). Например, программа может распечатать список объ- ектов в связанном списке путем создания объекта итератора. Итераторы часто используются в полиморфном программировании для прослеживания массива или связанного списка объектов на разных уровнях иерархии. Все ссылки в таком списке принадлежат базовому классу. (Подробно связанные списки рассматриваются в гла- ве 20.) Список объектов базового класса TwoDimensionalShape может включать объекты из классов Square,
206 Гпава 7 Circle, Triangle и т. д. Использование полиморфизма для отправки сообщения Draw каждому объекту в списке корректно изобразится каждый объект. 7.6. Пример: наследование интерфейса и реализации В следующем примере (листинги 7.4—7.8) повторно рассматривается иерархия классов Point, Circle, Cylinder, представленная в главе 6. В данном примере иерархия начинается с абстрактного базового класса shape (лис- тинг 7.4). Данная иерархия наглядно демонстрирует большие возможности полиморфизма. г ...................... .. ................................... | Листинг ТА. Абстрактный базовый класс Shape ®' ’’ЧИ 1 // Листинг 7.4: Chape.cs 2 // Демонстрация иерархии фигур с помощью абстрактного базового класса 3 using System; 4 5 public abstract class Shape 6 { 7 // возвращение площади Shape 8 public virtual double Area() 9 { 10 return 0; 11 } 12 13 // возвращение объема Shape 14 public virtual double Volume() 15 { 16 return 0; 17 ) 18 19 // возвращение имени Shape 20 public abstract string Name 21 { 22 get; 23 } 24 } Класс Shape определяет два конкретных метода и одно свойство abstract. Все фигуры имеют площадь и объем, поэтому включены виртуальные методы Area (строки 8—11) и Volume (строки 14—17), возвращающие инфор- мацию о площади и объеме фигуры, соответственно. Объем двумерной фигуры всегда будет нулевым, тогда как трехмерные формы имеют положительное, ненулевое значение объема. В классе shape методы Area и Volume по умолчанию возвращают ноль. Программисты могут подменять эти методы в производных классах, когда эти классы должны иметь различные расчеты площади (например, классы Circle2 — листинг 7.6 H-Cylinder2 — листинг 7.7) и/или различные расчеты объема (например, Cylinder2). Свойство Name— только для чтения (строки 20—23) — объявлено как abstract, поэтому производные классы должны реализовывать это свойство для превращения в конкретные классы. Обратите внимание, что методы и свойства типа abstract неявно явля- ются virtual. Класс Point2 (листинг 7.5) наследует из abstract-класса Shape и подменяет abstract-свойство Name, что пре- вращает Point2 в конкретный класс. Площадь и объем точки равны нулю, поэтому класс Point2 не подменяет методы Area и Volume базового класса. Строки 59—65 реализуют свойство Name. Если не представить эту реали- зацию, тогда класс Point2 будет классом abstract, который потребует ключевого слова abstract в первой строке определения класса. 1 // Листинг 7.5: Point2.cs 2 // Класс Point2 наследует из абстрактного класса Shape и 3 // представляет пару координат (х, у) 4 using System;
Объектно-ориентированное программирование: полиморфизм 207 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 И Point2 наследует из абстрактного класса Shape public class Point2 : Shape { private int x, у; // координаты Point2 // конструктор по умолчанию public Point2() { 11 здесь имеет место неявное обращение к конструктору Object } // конструктор public Point2(int xValue, int yValue) { X = xValue; Y = yValue; } // свойство X public int X { get < return x; ) set { x = value; // нет необходимости контроля } ) // свойство Y public int Y { get { return y; } set { у = value; // нет необходимости контроля } } // возвращение строкового представления объекта Point2 public override string ToString() { return "[" + X + " + Y + } // реализация абстрактного свойства Name класса Shape public override string Name { > get { return "Point2"; ) } ) // конец класса Point2
208 Глава 7 В листинге 7.6 определен класс Circle2, наследующий из класса Point2. Класс Circle2 содержит свойство Radius (строки 24—37) для доступа к радиусу окружности. Обратите внимание, что свойство Radius не объяв- ляется как virtual, поэтому классы, производные от этого класса, не могут подменить это свойство. Окруж- ность имеет нулевой объем, поэтому метод Volume базового класса не подменяется. Этот метод, скорее, насле- дуется классом Circle2 из класса Point2, унаследовавшего данный метод из Shape. Однако окружность не имеет площади, поэтому Circle2 подменяет метод Area класса Shape (строки 52—55). Свойство Name класса Circle2 подменяет свойство Name класса Point2. Если бы этот класс не подменял метод Name, то он бы унаследовал вер- сию свойства Name класса Point2. В этом случае свойство Name класса Circle2 ошибочно бы возвращало "Point 2". .... .........“ ------------jr -Г ” —’Г,—. Ч Листинг 7.6. Класс Cirole2, наследующий из класса Point2 1 // Листинг 7.6: Circle2.cs 2 // Circle2 наследует из класса Point2 и подменяет ключевые члены 3 using System; 4 5 // определение класса Circle2 наследует из класса Point2 6 public class Circle2 : Point2 7 { 8 private double radius; // радиус Circle2 9 10 11 конструктор по умолчанию 11 public Circle2() 12 { 13 // здесь имеет место неявное обращение к конструктору Point2 14 } 15 16 // конструктор 17 public Circle2(int xValue, int yValue, double radiusValue) 18 : base(xValue, yValue) 19 { 20 Radius = radiusValue; 21 } 22 23 // свойство Radius 24 public double Radius 25 { 26 get 27 { 28 return radius; 29 } 30 31 set 32 { 33 // установка неотрицательного значения радиуса 34 if (value >= 0) 35 radius = value; 36 } 37 } 38 39 // расчет диаметра Circle2 40 public double Diameter() 41 { 42 return Radius * 2; 43 ) 44 45 // расчет длины окружности Circle2 46 public double Circumference() 47 { 48 return Math.PI * Diameter(); 49 } 50
Объектно-ориентированное программирование: полиморфизм 209 51 // расчет площади Circle2 52 public virtual double Areaf) 53 { 54 return Math.PI * Math.Pow(Radius, 2); 55 } 56 57 // возвращение строкового представления объекта Circle2 58 public override string Tostring() 59 { 60 return "Center = ” + base.Tostring() + 61 *’; Radius = ” + Radius; 62 } 63 64 // подмена свойства Name из класса Point2 65 public override string Name 66 { 67 get 68 { 69 return ”Circle2"; 70 } 71 } 72 73 } // конец класса Circle2 В листинге 7.7 определяется класс Cylinder2, наследующий из класса Circle2. Класс Cylinder2 содержит свой- ство Height (строки 24—37) для доступа к значению высоты цилиндра. Обратите внимание, что свойство Height не объявляется как virtual, поэтому классы, производные от класса Cylinder2, не могут подменить это свойст- во. Цилиндр имеет расчеты площади и объема, отличные от расчетов площади и объема окружности, поэтому данный класс подменяет метод Area (строки 40—43) для расчета площади поверхности цилиндра (т. е. 2лг2 + 2itrh) и подменяет метод volume (строки 46—49). Свойство Name (строки 58—64) подменяет свойство Name класса Circie2. Если этот класс не подменяет свойство Name, тогда класс унаследует свойство Name класса Circle2, и это свойство ошибочно возвратит "Circle2". 1 // Листинг 7.7: Cylinder2.cs 2 // Cylinder2 наследует из класса Circle2 и подменяет ключевые члены 3 using System; 4 5 // Cylinder2 наследует из Circle2 6 public class Cylinder2 : Circle2 7 { 8 private double height; // высота- Cylinder2 9 10 // конструктор по умолчанию 11 public Cylinder2() 12 { 13 // здесь происходит неявное обращение к конструктору Circle2 14 } 15 16 // конструктор 17 public Cylinder2(int xValue, int yValue, double radiusValue, 18 double heightvalue) : base(xValue, yValue, radiusValue) 19 { 20 Height = heightvalue; 21 ) 22 23 // свойство Height 24 public double Height 25 { 26 get 14 Зак. 3333
210 Гпава 7 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 { return height } set { // установка неотрицательного значения высоты if (value >= 0) height = value; 1 } // расчет площади Cylinder2 public override double Area() { return 2 * base.AreaO + base.Circumference() * Height; } // расчет объема Cylinder2 public double Volume() { return base. AreaO * Height; } // возвращение строкового представления объекта Cylinder2 public override string ToStnng () { return base.ToStringO + "; Height = " + Height; } // подмена свойства Name из класса Circle2 public override string Name { get { return "Cylinder2"; } } } // конец класса Cylinder2 Класс AbstractShapesTest (листинг 7.8) создает объект каждого из трех конкретных классов и манипулирует этими объектами полиморфно с помощью массива ссылок shape. В строках 11—13 создается объект point класса Point2, объект circle класса Circle2 и объект cylinder класса Cylinder2 соответственно. Затем, в строке 16 распределяется массив arrayofShapes, содержащий три ссылки Shape. В строке 19 присваивается ссылка point элементу массива arrayOfShapes[0], в строке 22— ссылка circle элементу массива arrayofShapes [1], а в строке 25— ссылка cylinder элементу массива arrayofShapes [2]. Такие присвоения становятся возможными, потому что Point2 является Shape, Circle2 является Shape и Cylinder2 является Shape. Следовательно, можно присвоить экземпляры производных классов Point2, Circle2 и Cylinder2 ссылкам базо- вого класса Shape. 1 // Листинг 7.8: AbstractShapesTest.cs 2 // Демонстрация полиморфизма в иерархии "точка-окружность-цилиндр” 3 using System; 4 using System.Windows.Forms; 5 6 public class AbstractShapesTest 7 { 8 public static void Main(string[] args)
Объектно-ориентированное программирование: полиморфизм 211 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 } // создание объектов Point2, Circle2 и Cylinder2 Point2 point = new Point2(7, 11); Circle2 circle - new Circle2(22, S, 3.5); Cylinder2 cylinder = new Cylinder2(10, 10, 3.3, 10); // создание пустого массива ссылок базового класса Shape Shape[] arrayOfShapes = new Shape[3]; // arrayOfShapes[0] ссылается на объект Point2 arrayOfShapes[0] = point; // arrayOfShapes[1] ссылается на объект Circle2 arrayOfShapes[1] = circle; // arrayOfShapes[2] ссылается на объект Cylinder2 arrayOfShapes[2] = cylinder; string output = point.Name + ": " + point + "\n" + circle.Name + ": " + circle + "\n" + cylinder.Name + ": " + cylinder; Рис. 7.2. Демонстрация полиморфизма в иерархии “точка — окружность — цилиндр" // полиморфное отображение Name, Area и Volume для каждого // объекта в arrayOfShapes foreach (Shape shape in arrayOfShapes) { output += "\n\n" + shape.Name + ": " + shape + "\nArea = ” + shape.Areaf) .ToString("F") + "\nVolume = " + shape.Volume() .ToString("F"); } MessageBox.Show(output, "Demonstrating Polymorphism"); В строках 27—29 осуществляется доступ к свойству Name и активизируется (неявно) метод Tostring для объек- тов point, circle и cylinder. Свойством Name возвращается имя класса объекта, а метод Tostring возвращает строковое представление объекта (т. е. пару координат (х, у), радиус и высоту, в зависимости от типа каждого объекта). Обратите внимание, что в строках 27—29 используются ссылки производного класса для активизации методов и свойств каждого из объектов производного класса. Оператор foreach (строки 33—38), наоборот, использует ссылки базового класса shape для активизации свойств и методов каждого из объектов производного класса. Цикл foreach вызывает свойство Name и методы Tostring, Area и Volume для каждой ссылки Shape в arrayOfShapes. Свойство и методы активизируются на каждом объек- те в arrayOfShapes. Когда программа-компилятор рассматривает вызов каждого метода/свойства, то она опре- деляет, может ли осуществить эти вызовы каждая ссылка Shape (в arrayOfShapes) Это имеет значение для свой- ства Name и методов Area и Volume, потому что они определены в классе shape. Однако класс Shape не определя- ет метод Tostring. Для данного метода компилятор переходит к базовому классу shape (классу Object) и выясняет, что shape унаследовал метод без аргументов Tostring из класса object. Изображение диалогового окна на рис. 7.2 показывает, что для каждого типа объектов в arrayOfShapes были активизированы "подходящее” свойство Name и методы Tostring, Area и volume. Под словом "подходящее" име- ется в виду то, что C# отображает каждое обращение к свойству и методам на нужный объект. Например, в пер- вой итерации оператора foreach ссылка arrayOfShapes [1] (принадлежащая к типу Shape) обращается к тому же объекту, что и point (принадлежащая к типу Point2). Класс Point2.подменяет свойство Name и метод Tostring и наследует метод Area и Volume из класса Shape. Во время выполнения arrayOfShapes [1] осуществляет доступ к свойству Name и активизирует методы Tostring, Area и Volume объекта Point. Язык C# определяет корректный тип объекта, после чего использует этот тип для определения соответствующих методов для активизации. По- средством полиморфизма обращение к свойству Name возвращает строку "Point2"; обращение к методу Tostring возвращает представление string пары координат (х, у) point, а методы Area и volume возвращают значение о (как показано во второй группе выходных данных в листинге 7.8).
212 Глава 7 Полиморфизм также имеет место в следующих двух итерациях цикла foreach. Ссылка arrayOfShapes[l] обра- щается к тому же объекту, что и circle (принадлежащая к типу Circle2). Класс Circle2 обеспечивает реализа- ции для свойства Name, метода Tostring и метода Area и наследует метод volume из класса Point2 (который, в свою очередь, унаследовал метод Volume из класса Shape). Язык C# ассоциирует свойство Name и методы ToString, Area И Volume объекта Circle2 ДЛЯ ССЫЛКИ arrayOf Shapes [1]. В результате, свойство Name возвращает строку "Circle2"; метод Tostring возвращает представление string пары координат (х, у) и радиус, метод Area возвращает площадь (38.48), а метод volume — 0. Для последней итерации цикла foreach ссылка arrayOfShapes [2] обращается к тому же объекту, что и cylinder (принадлежащая к типу Cylinder2). Класс Cylinder2 предоставляет собственные реализации для свойства Name И методов Tostring, Area И Volume. Язык C# ассоциирует СВОЙСТВО Name И методы ToString, Area и Volume объ- екта Cylinder2 для ссылки на arrayOfShapes [2]. Свойство Name возвращает строку "Cylinder2:метод Tostring возвращает представление String пары координат (х,у) cylinder и радиус и высоту, метод Area — значение площади поверхности цилиндра (275.769...), а метод volume— значение объема цилиндра (342.119...). 7.7. Классы и методы типа sealed В главе 5 показано, что переменные могут быть объявлены как const и readonly для указания на то, что их нельзя изменять после их инициализации. Переменные, объявленные с const, должны инициализироваться при их объявлении; переменные, объявленные с readonly, могут быть инициализированы в конструкторе, но после инициализации модифицировать их нельзя. Ключевое слово sealed применяется к методам и классам для предотвращения подмены и наследования. Метод, объявленный как sealed, не может быть подменен в производном классе. Методы, объявленные как static, и методы, объявленные как private, неявно являются sealed. Совет по повышению производительности________________________________________________ Ключевое слово sealed обеспечивает выполнение оптимизации компилятором. Определение метода sealed никогда нельзя изменять, поэтому компилятор может оптимизировать программу удалением обращений к мето- дам sealed и заменой их расширенным кодом их определений в местоположении обращения к каждому методу. Данная методика называется вставкой кода в программы. / X Замечание по технологии программирования_____________________________________________ Если метод объявлен как sealed, его нельзя подменять в производных классах. Обращения к методам не долж- ны отправляться полиморфно на объекты этих производных классов. Обращение к методу может быть отправ- лено в производные классы, но они будут реагировать (отвечать) идентично, а не полиморфно Помните, что метод подменять нельзя (с помощью ключевого слова override), если он не объявлен либо как virtual, либо как abstract. Следовательно, для таких случаев ключевое слово sealed не требуется. Ключевое слово sealed используется для методов, которые были подменены, но которые не должны подменяться в последующих про- изводных классах. Совет по повышению производительности________________________________________________ Компилятор может принять решение о вставке обращения к методу sealed, что и делает для небольших, про- стых методов sealed. Вставки не нарушают инкапсуляции или сокрытия информации (но увеличивают произво- дительность, потому что устраняются непроизводительные издержки на осуществление обращений к методу). Совет по повышению производительности________________________________________________ Конвейерные процессоры могут повысить производительность одновременным исполнением частей следующих нескольких команд, если эти команды не придерживаются обращения к методу. Вставка (которую компилятор может выполнить на методе sealed) может повысить производительность таких процессоров, потому что она устраняет внепрограммную передачу управления, связанную с обращением к методу Замечание по технологии программирования_____________________________________________ Класс, объявленный как sealed, не может быть базовым (т. е. класс не может наследовать из класса sealed). Все методы в классе sealed неявно принадлежат к типу sealed. Использование ключевого слова sealed с классами обеспечивает возможности других оптимизации времени выполнения. Например, обращения к методам типа virtual могут преобразовываться в не-virtual обращения к методам. Класс sealed является, в определенной степени, противоположным классу abstract. Класс abstract нельзя соз- дать: из абстрактного базового класса происходят другие классы и реализуют члены abstract этого базового
Объектно-ориентированное программирование: полиморфизм 213 класса. С другой стороны, класс sealed не может иметь производных классов. Это отношение сходно в том, что касается методов. Метод abstract должен подменяться в производном классе, тогда как метод sealed не может подменяться в производном классе. 7.8. Пример: система начисления заработной платы с применением полиморфизма Воспользуемся классами abstract, методами abstract и полиморфизмом для выполнения расчетов начисления заработной платы для разных категорий служащих. Начнем с создания абстрактного базового класса Employee. Производными от Employee классами являются классы Boss (получает фиксированную зарплату каждую неде- лю, независимо от количества отработанных часов), Commissionworker (получает установленный оклад плюс процент от продаж служащего), Pieceworker (твердая ставка за каждое произведенное изделие) и HourlyWorker (почасовая оплата плюс оплата сверхурочной работы в полуторном размере). Программное приложение должно рассчитывать недельный заработок служащих всех типов, поэтому каждый производный от Employee класс требует метода Earnings. Однако в каждом производном классе используются различные вычисления дохода для каждого отдельного типа служащего. Следовательно, метод Earnings объяв- ляется в Employee как abstract, и Employee объявляется как класс abstract. Каждый производный класс подме- няет этот метод для расчета заработка рассматриваемого типа служащих. Для расчета заработка любого служащего программа может использовать ссылку базового класса на объект производного класса и активизировать метод Earnings. Реальная система расчета заработной платы должна ссылаться на разные объекты класса Employee с индивидуальными элементами в массиве ссылок Employee. Про- грамма будет прослеживать массив по каждому элементу с помощью ссылок Employee для активизации соответ- ствующего метода Earnings каждого объекта. Замечание по технологии программирования__________________________,______________ Объявление абстрактного метода предоставляет проектировщику класса значительные возможности управле- ния тем, как производные классы определяются в иерархии классов. Любой класс, напрямую наследующий из базового класса, содержащего абстрактный метод, должен подменять этот абстрактный метод. В противном случае, новый класс также будет абстрактным, и попытки создания объектов этого класса окажутся неудачными. Рассмотрим класс Employee (листинг 7.9). В число public-членов входит конструктор (строки 11—16), прини- мающий в качестве аргументов имя и фамилию служащего, свойства FirstName (строки 19—30) и LastName (строки 33 и 34), метод Tostring (строки 47—50), возвращающий имя и фамилию, разделенные пробелом, и метод abstract Earnings (строка 54). Ключевое слово abstract (строка 5) указывает на то, что класс Employee — abstract; следовательно, его нельзя использовать для создания объектов Employee. Метод Earnings объявлен abstract, поэтому данный класс не предоставляет реализации метода. Все классы, напрямую произ- водные от класса Employee, за исключением абстрактных производных классов, должны реализовывать этот метод. Метод Earnings в Employee является abstract, потому что нельзя рассчитать заработную плату для об- щих родовых служащих. Для определения заработка сначала необходимо знать категорию служащего. Объяв- лением этого метода как abstract указывается на то, что реализация будет представлена в каждом конкретном производном классе, но не в самом базовом классе. 1 /7 Листинг 7.9: Employee.cs 2 // Абстрактный базовый класс для служащих компании 3 using System; 4 5 public abstract class Employee 6 { private string firstName; 8 private string lastName; 9 10 // конструктор 11 public Employee(string firstNameValue, 12 string lastNameValue) 13 { 14 FirstName = firstNameValue; 15 LastName = lastNameValue; 16 } 17
214 Глава 7 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 // свойство FirstName public string FirstName { get { return firstName; } set { firstName = value; } // свойство LastName r public string LastName { get { return lastName; 1 set { lastName *= value; } } // возвращение строкового представления Employee public override string ToString() ( return FirstName + " " + LastName; } // абстрактный метод, который должен быть реализован для каждого // производного класса Employee для расчета конкретной зарплаты public abstract decimal Earnings(); } // конец класса Employee Класс Boss (листинг 7.10) наследует из Employee. Конструктор класса Boss (строки 10—15) получает в качестве аргументов имя, фамилию служащего и его зарплату. Конструктор передает имя и фамилию в конструктор Employee (строка 12), инициализирующий члены FirstName и LastName части базового класса объекта производ- ного класса. Другие public-члены в классе Boss включают метод Earnings (строки 34—37),. определяющий рас- чет зарплаты начальника, и метод Tostring (строки 40—43), возвращающий строку с категорией служащего (т. е. "Boss") и имя начальника. Класс Boss также включает в себя свойство weeklysalary (строки 18—31), ма- нипулирующее значением для переменной salary члена. Обратите внимание, что это свойство подтверждает факт, что salary не может иметь отрицательное значение; в реальной системе начисления заработной платы данное подтверждение было бы более расширенным и контролировалось бы тщательнее. Листинг 7.10. Класс Boss наследует из класса Employee = \ . !fk ....—£*-------------- . £1.. ..a' — 1 // Листинг 7.10: Boss.cs 2 // Класс Boss, производный от Employee 3 using System; 4 5 public class Boss : Employee 6 { 7 private decimal salary; // зарплата начальника 8 9 // конструктор 10 public Boss(string firstNameValue, string lastNameValue, 11 decimal salaryvalue)
Объектно-ориентированное программирование: полиморфизм 215 12 : base(firstNameValue, lastNameValue) 13 { 14 Weeklysalary = salaryvalue; 15 } ' 16 17 // свойство Weeklysalary 18 public decimal Weeklysalary 19 { 20 - get 21 { 22 return salary; 23 } 24 25 set 26 { 27 // обеспечение положительного значения суммы зарплаты 28 if (value >0) 29 salary = value; 30 ) 31 } 32 33 // подмена метода базового класса для расчета зарплаты начальника 34 public override decimal Earnings() 35 { 36 return Weeklysalary; 37 } 38 39 // возвращение строкового представления класса Boss 40 public override string ToString() 41 { 42 return "Boss: " + base.ToString(); 43 ) 44 } Класс Commissionworker (листинг 7.11) также наследует из класса Employee. Конструктор для этого класса (строки 12—20) принимает в качестве аргументов имя, фамилию, зарплату, сумму комиссионных и количество проданных наименований товара. В строке 15 имя и фамилия передаются в конструктор базового класса Employee. Класс Cornmissionworker также предоставляет свойства Weeklysalary (строки 23—36), Commission (строки 39—52) и Quantity (строки 55—68); метод Earnings (строки 72—75), рассчитывающий зарплату рабочего, и метод Tostring (строки 78—81), возвращающий строку с категорией служащего (т. е. "Commissionworker") и его имя. 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 // Листинг 7.11: ConmissionWorker.cs // Класс Commissionworker, производный от Employee using System; public class Commissionworker : Employee { private decimal salary; // базовая еженедельная зарплата private decimal commission; // сумма за проданную продукцию private int quantity; // всего проданных наименований // конструктор public Commissionworker(string firstNameValue, string lastNameValue, decimal salaryvalue, decimal commissionvalue, int quantityvalue) : base(firstNameValue, lastNameValue) { Weeklysalary = salaryvalue; Commission = commissionvalue;
216 Гпава 7 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 Quantity = quantityvalue } // свойство Weeklysalary public decimal Weeklysalary { get { return salary; } set { // обеспечение неотрицательного значения суммы зарплаты if (value > 0) salary = value; } } // свойство Commission public decimal Commission { get { return commission; ) set { // обеспечение неотрицательного значения суммы комиссионных if (value > 0) commission = value; } } // свойство Quantity public int Quantity { get { return quantity; } set { // обеспечение неотрицательного значения количества if (value > 0) quantity = value; } } // подмена метода базового класса для расчета // доходов Commissionworker . public override decimal Earnings() { return Weeklysalary + Commission * Quantity; // возвращение строкового представления Commissionworker public override string Tostring() ( return "Commissionworker: " + base.ToStringO; } } // конец класса CoirmissionWorker
Объектно-ориентированное программирование: полиморфизм 217 Класс Pieceworker (листинг 7.12) наследует от класса Employee. Конструктор для этого класса (строки 11—18) принимает в качестве аргументов имя, фамилию, зарплату за одно изделие и количество произведенных изде- лий. Затем в строке 14 имя и фамилия передаются в конструктор базового класса Employee. Класс Pieceworker также предоставляет свойства WagePerPiece (строки 21—33) и Quantity (строки 36—48); метод Earnings (стро- ки 52—55), рассчитывающий зарплату рабочего за единицу произведенной продукции, и метод Tostring (стро- ки 58—61), возвращающий строку с категорией служащего (т. е. "Pieceworker") и его имя. ir 7.12. Красс Pieceworker наследует от класса Employee 1 // Листинг 7.12: Pieceworker.cs 2 // Класс Pieceworker, производный от Employee 3 using System; 4 5 public class Pieceworker : Employee 6 { 7 private decimal wagePerPiece; // зарплата за единицу 7a // произведенной продукции 8 private int quantity; // к-во произведенных единиц продукции 9 10 // конструктор 11 public Pieceworker(string firstNameValue, 12 string lastNameValue, decimal wagePerPieceValue, 13 int quantityvalue) ' 14 : base(firstNameValue, lastNameValue) 15 { 16 wagePerPiece = wagePerPieceValue; 17 Quantity = quantityvalue; 18 } , 19 20 // свойство WagePerPiece 21 public decimal wagePerPiece 22 { 23 get 24 { 25 return wagePerPiece; 26 } 27 28 set 29 { 30 if (value > 0) 31 wagePerPiece = value; 32 } 33 } 34 35 // свойство Quantity 36 public int Quantity 37 { 38 get 39 { 40 return quantity; 41 } 42 43 set 44 { 45 if (value > 0) 46 quantity » value; 47 } 48 } 49 50 // подмена метода базового класса для расчета 51 // зарплаты Pieceworker > 52 public override decimal Earnings()
218 Глава 7 53 54 55 56 57 58 59 60 61 62 ( return Quantity * WagePerPiece; ) // возвращение строкового представления Pieceworker public override string ToString() { return "Pieceworker: " + base.ToString(); ) ) Класс HourlyWorker (листинг 7.13) наследует от класса Employee. Конструктор для этого класса (строки 11—17) принимает в качестве аргументов имя, фамилию, зарплату и количество отработанных часов. В строке 13 имя и фамилия передаются в конструктор базового класса Employee. Класс HourlyWorker также предоставляет свойст- ва Wage (строки 20—33) и HoursWorked (строки 36—49); метод Earnings (строки 53—70), рассчитывающий зар- плату рабочего за час работы, и метод Tostring (строки 73—76), возвращающий строку с категорией служаще- го (т. е. "HourlyWorker") и его имя. Обратите внимание, что сверхурочная работа (т. е. в случае отработки более 40 часов в неделю) служащим-почасовикам оплачивается в полуторном размере. 1 // Листинг 7.13: HourlyWorker.cs 2 // Класс HourlyWorker, производный от Employee 3 using System; 4 5 public class HourlyWorker : Employee 6 { 7 private decimal wage; 11 зарплата за час работы 8 private double hoursWorked; // к-во отработанных часов за неделю 9 10 // конструктор 11 public HourlyWorker(string firstNameValue, string lastNameValue, 12 decimal wageValue, double hoursWorkedValue) 13 : base(firstNameValue, lastNameValue) 14 { 15 Wage - salaryvalue; 16 HoursWorked = hoursWorkedValue; 17 } 18 19 // свойство Wage 20 public decimal Wage 21 { 22 get 23 { 24 return wage; 25 } 26 27 set • 28 { 29 // обеспечение неотрицательного значения суммы зарплаты 30 if (value > О) 31 wage = value;. 32 } 33 } 34 35 // свойство HoursWorked 36 public double HoursWorked 37 38 get 39 { 40 return hoursWorked; 41 } 42
Объектно-ориентированное программирование: полиморфизм 219 43 set 44 { 45 // предоставление неотрицательного значения hoursWorked 46 if (value > 0) 47 hoursWorked = value; 48 } 49 } 50 51 11 подмена метода базового класса для расчета 52 // зарплаты за отработанные часы 53 public override decimal Earnings() 54 { 55 // компенсация за сверхурочную работу (в полуторном размере) 56 if (HoursWorked <= 40) 57 { 58 return Wage * Convert.ToDecimal(HoursWorked); 59 } 60 61 else 62 { 63 // расчет базового оклада и оплаты за сверхурочную работу 64 decimal basePay — Wage * Convert.ToDecimal(40); 65 decimal overtimePay = Wage * 1.5M * 66 Convert.ToDecimal(HoursWorked - 40); 67 68 return basePay + overtimePay; 69 } 70 } 71 ( 72 // возвращение строкового представления HourlyWorker 73 public override string Tostring() 74 { 75 return "HourlyWorker: " + base.ToStringO; 76 } 77 } Методом Main (строки 9—48) класса EmployeesTest (листинг 7.14) объявляется ссылка employee класса Em- ployee (строки 22). Каждый тип служащего обрабатывается сходным образом в Main, поэтому обсудим только манипуляции объектом Boss. В строке 11 ссылке boss класса Boss присваивается объект Boss и передает его конструктору имя начальника ("John"), его фамилию ("Smith") и фиксированный недельный оклад (800). В строке22 присваивается ссылка boss производного класса ссылке employee базового класса Employee, так что можно продемонстрировать поли- морфное определение доходов boss. В строке 24 ссылка employee передается в качестве аргумента в метод GetString (строки 51—55), полиморфно активизирующий методы Tostring и Earnings на объект Employee, который данный метод получает в качестве аргумента. На этом этапе C# определяет, что объект, переданный в GetString, принадлежит к типу Boss, поэтому строки 53 и 54 активизируют методы класса Boss — Tostring и Earnings. Это — классические примеры полиморфного поведения. 1 // Листинг 7.14: EmployeesTest.cs 2 // Демонстрирует полиморфизм отображением зарплат 3 // разных категорий служащих 4 using System; 5 using System.Windows.Forms; 6 7 public class EmployeesTest 8 { 9 public static void Main(string!] args) 10 { 11 Boss boss = new Boss("John", "Smith", 800); 12
220 Глава 7 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 CoiranissionWorker Commissionworker = new CaranissionWorker("Sue", "Jones", 400, 3, 150); Pieceworker pieceworker = new Pieceworker("Bob", "Lewis”, Convert.ToDecimal(2.5), 200); HourlyWorker hourlyWorker « new HourlyWorker("Karen", "Price", Convert.ToDecimal(13.75), 50); Employee employee boss; string output = GetString(employee) + boss + ” earned " + boss.EarningO .ToString("C") + "\b\n"; employee = CoiranissionWorker; output += GetString(employee) + Commissionworker + " earned " + commissionworker.Earnings().Tostring("C") + "\n\n"; employee = pieceworker; output += GetString(employee) + pieceworker + " earned " + pieceworker.Earnings().ToString("C") + "\n\n"; employee = hourlyWorker output += GetString(employee) + hourlyWorker + " earned " + hourlyWorker.Earnings().Tostring("C") + "\n\n"; MessageBox.Show(output, "Demonstrating Polymorphism", MessageBoxButtons.OK, MessageBoxIcon.Information); ) // конец метода Main // возвращение строки, содержащей информацию об Employee public static string GetString(Employee worker) { return worker.ToStringO + " earned " + worker.Earnings() .ToString("C") + "\n"; } } Рис. 7.3. Демонстрация проверки классом EmployeesTest иерархии класса Employee Методом Earnings возвращается объект Decimal, на который затем в строке 54 вызывается метод Tostring. В этом случае строка "с", передаваемая в перегруженную версию Decimal-метода Tostring, обозначает Currency, и Tostring форматирует эту строку как сумму в валюте. Когда метод GetString возвращается в Main, строки 24 и 25 явно активизируют методы Tostring и Earnings через ссылку boss производного класса Boss для показа вызовов метода, не использующих полиморфной обра- ботки. Выходные данные, сгенерированные в строках 24 и 25, идентичны выходным данным, сгенерированным методами Tostring и Earnings через ссылку базового класса employees (т. е. методами, использующими поли- морфизм), и это подтверждает, что полиморфные методы активизируют соответствующие методы в производ- ном классе Boss. Для доказательства того, что ссылка базового класса employee может активизировать нужные версии производ- ного класса методов Tostring и Earnings для других категорий служащих, строки 27, 33 и 39 присваивают ссылке employee базового класса другой тип объекта Employee (CoiranissionWorker, Pieceworker И HourlyWorker, соответственно). После каждого присвоения приложение обращается к методу GetString для возвращения ре- зультатов через ссылку базового класса. После этого приложение обращается к методам Tostring и Earnings каждой ссылки производного класса для демонстрации, что C# корректно ассоциирует любое обращение к ме- тоду с соответствующим объектом производного класса. Результат работы программы представлен на рис. 7.3.
Объектно-ориентированное программирозание: полиморфизм 221 7.9. Пример: создание и использование интерфейсов Теперь приведем еше два примера полиморфизма с помощью интерфейсов, задающих наборы служб типа public (т. е. методов и свойств), которые должны быть реализованы классами. Интерфейс используется тогда, когда для наследования нет реализации по умолчанию (т. е. нет переменных экземпляра и реализаций методов по умолчанию). При том, что абстрактный класс лучше всего используется для предоставления данных и служб для объектов в иерархическом отношении, интерфейс можно использовать для предоставления служб, "объеди- няющих” разрозненные объекты, связанные друг с другом только посредством этого интерфейса. Определение интерфейса начинается с ключевого слова interface и содержит список методов и свойств типа public. Для использования интерфейса класс должен указать, что он реализует интерфейс, а также предоставить реализации для каждого метода и свойства, указанного в определении интерфейса. Класс, эффективно реали- зующий интерфейс, подписывает с компилятором "договор", в котором говорится, что "данный класс будет оп- ределять все методы и свойства, заданные интерфейсом". Распространенная ошибка программирования_____________________________________________ Когда класс реализует interface, оставляя хотя бы один метод или свойство interface неопределенными, то это приводит к ошибке. Класс должен определять каждый метод и каждое свойство в interface. Распространенная ошибка программирования_____________________________________________ В C# interface может быть объявлен только как public; объявление interface как private или protected является ошибкой. Интерфейсы обеспечивают единообразный набор методов и свойств объектам разрозненных классов. Эти мето- ды и свойства позволяют программам полиморфно обрабатывать объекты указанных разрозненных классов. Например, представьте разрозненные объекты, представляющие человека, дерево, автомобиль и файл. Эти объ- екты "не имеют никакого отношения" друг к другу: человек имеет имя и фамилию; дерево имеет ствол, крону из ветвей и множество листьев; автомобиль имеет колеса, коробку передач и прочие механизмы, позволяющие ему двигаться; файл содержит данные. По причине недостатка у этих классов общих черт их моделирование через иерархию наследования с обычными базовыми классами кажется нелогичным. Однако эти объекты, безусловно, имеют, по крайней мере, одну общую характеристику — возраст. Возраст человека представлен количеством лет с момента его рождения; возраст дерева определяется по количеству колец на срезе его ствола; возраст ав- томобиля обозначен датой его изготовления, а возраст файла — датой его создания. Можно использовать ин- терфейс, предоставляющий метод или свойство, которые объекты этих разрозненных классов могут реализовать для возвращения значения возраста каждого объекта. В данном примере используется интерфейс lAge (листинг 7.15) для возвращения информации о возрасте для классов Person (листинг 7.16) и Tree (листинг 7.17). Определение интерфейса lAge начинается в строке 4 с public interface и заканчивается на строке 8, закрывающейся фигурной скобкой. Строки 6 и 7 задают свойства только для чтения — Аде и Name, для которых каждый класс, реализующий интерфейс lAge, должен предоста- вить реализации. Объявление этих свойств как свойства "только для чтения" не требуется: интерфейс может предоставить методы, свойства "только для записи" и свойства со средствами доступа get и set. Содержанием объявлений этих свойств интерфейс lAge предоставляет объекту, реализующему lAge, возможность возвраще- ния его возраста и имени, соответственно. Однако классы, реализующие эти методы, не требуются ни интер- фейсу lAge, ни C# для возвращения данных имени и возраста. Компилятор требует только того^чтобы классы, реализующие интерфейс lAge, обеспечивали реализации для свойств этого интерфейса. В строке 5 листинга 7.16 используется обозначение наследования, принятое в C# (т. е. ClassName: in terfaceName) для указания на то, что класс person реализует интерфейс lAge. В данном примере класс Person реализует только один интерфейс. Любой класс может реализовать любое количество интерфейсов в дополне- ние к наследованию из одного класса. Для реализации более одного интерфейса в определении класса должен быть представлен разделенный запятыми список имен интерфейсов после двоеточия. Класс Person имеет пере- менные его членов yearBorn, firstName и lastName (строки 7—9), для которых конструктор (строки 12—22) задает значения. Из-за того что класс Person реализует интерфейс lAge, класс Person должен реализовать свой- ства Аде и Name, определенные в строках 25—31 и 34—40, соответственно. Свойство Аде позволяет клиенту по- лучить информацию о возрасте человека, а свойство Name возвращает строку, содержащую firstName и lastName. Обратите внимание, что свойство Аде рассчитывает возраст человека вычитанием yearBorn из значе- ния текущего года (через свойство Year свойства DateTime.Now, возвращающее текущую дату). Эти свойства удовлетворяют требования реализации, определенные в интерфейсе lAge, так что класс Person выполнил усло- вия "договора" с компилятором.
222 Глава 7 1 // Листинг 7.15: IAge.cs 2 // Интерфейс IAge объявляет свойство для настройки и получения 2а // данных о возрасте 3 4 public interface IAge 5 { 6 int Age { get; } 7 string Name { get; } 8 } 1 // Листинг 7.16: Person.cs 2 // Класс Person имеет дату рождения 3 using System; 4 5 public class Person : Age 6 { 7 private string firstName; 8 private string lastName; 9 private int yearBorn; 10 11 // конструктор 12 public Person(string firstNameValue, string lastNameValue, 13 int yearBornValue) 14 { 15 firstName = firstNameValue; 16 lastName = lastNameValue; 17 18 if (yearBornValue > 0 && yearBornValue <= DateTime.Now.Year) 19 yearBorn = yearBornValue; 20 else 21 yearBorn « DateTime.Now.Year; 22 } 23 24 // реализация свойством Age интерфейса IAge 25 public int Age 26 { 27 get 28 { 29 return DateTime.Now.Year - yearBorn; 30 } 31 } 32 33 // реализация свойством Name интерфейса IAge 34 public string Name 35 { 36 get 37 { 38 return firstName + " " + lastName; 39 } 40 ) 41 42 } // конец класса Person Классом Tree (листинг 7.17) также реализуется интерфейс IAge. Класс Tree имеет переменную членов rings (строка 7), представляющую количество колец на срезе ствола дерева — эта переменная напрямую соответству- ет возрасту дерева. Конструктор Tree (строки 10—14) принимает в качестве ар!умента значение типа int, обо- значающее год, когда дерево было посажено. Класс Tree включает в себя метод AddRing (строки 17—20), по- зволяющий программе увеличивать число колец на дереве. Из-за того что Tree реализует интерфейс IAge, класс
Объектно-ориентированное программирование: полиморфизм 223 Tree должен реализовать свойства Аде и Name, определенные в строках 23—29 и 32—38, соответственно. Свой- ство Аде возвращает значение rings, а СВОЙСТВО Name — string "Tree". (----------------------------------- ---------------------------------------------------------------------------------------------------------------------- 1 Листинг 7.17. Класс Tree реализует интерфейс IAge ‘ ’ Як м><яа1>ъГ>>1М<«м>»<МЙМ*«ММ«амп4*а»1I» •<ммГммм.*,. «маа- ’neiaaakk» *а»а.».*а**а»а>»>'ам**>*Г9Са*Мве^а«а1нааа«»**аа»ааа*а«ак 1 // Листинг 7.17: Tree.cs 2 // Класс Tree содержит число колец, соответствующее его возрасту 3 using System; 4 5 public class Tree : Age 6 { 7 private int rings; // число колец на стволе дерева 8 9 // конструктор 10 public Tree(int yearPlanted) 11 ( 12 // подсчет числа колец в Tree 13 rings = DateTime.Now.Year - yearPlanted; 14 } 15 16 // приращение числа колец 17 public void AddRingO 18 { 19 rings++; 20 } 21 22 // реализация свойством Age интерфейса IAge 23 public int Age 24 { 25 get 26 { 27 return rings; 28 ) 29 } 30 31 // реализация свойством Name интерфейса IAge 32 public string Name 33 { 34 get 35 { 36 return "Tree”; 37 ) 38 } 39 40 } // конец класса Tree Классом interfacesTest (листинг 7.18) демонстрируется полиморфизм на объектах разрозненных классов Person и Tree. В строке 9 создается объект tree класса Tree, а строка 10 формирует объект person класса Person. В строке 13 объявляется iAgeArray— массив из двух ссылок на объекты IAge. В строках 16 и 19 tree и person присваиваются первой и второй ссылкам в iAgeArray, соответственно. В строках 22 и 23 активизируется метод Tostring на tree, после чего активизируются его свойства Аде и Name для возвращения информации об имени и возрасте объекта person. Затем эти объекты обрабатываются полиморфно через iAgeArray ссылок на объекты IAge. В строках 30—34 определяется оператор foreach, использующий свойства Аде и Name для полу- чения информации об имени и возрасте для каждого объекта IAge в iAgeArray. Обратите внимание, что про- грамма также может активизировать public-методы класса object (например, Tostring) с помощью любой ссылки на интерфейс. Это становится возможным, потому что каждый объект напрямую или косвенно наследу- ет из класса Object. Таким образом, каждый объект будет гарантированно иметь public-методы класса Object. Замечание по технологии программирования________________________________________________ В C# ссылка на интерфейс может активизировать методы и свойства, объявляемые интерфейсом, а также public-методы класса Object.
224 Глава 7 Замечание по технологии программирования _________________________________________ В C# интерфейс предоставляет только public-службы, объявленные в интерфейсе, тогда как абстрактный класс обеспечивает public-службы, определенные в этом абстрактном классе, и члены класса, унаследованные из базового класса абстрактного класса. 1 // Листинг 7.18: InterfacesTest.cs 2 // Демонстрация полиморфизма с интерфейсами 3 using System.Windows.Forms; 4 5 public class InterfacesTest 6 { 7 public static void Main(string[] args) 8 { 9 Tree tree = new Tree(1978); 10 Person person = new Person("Bob", "Jones", 1971); 11 12 // создание массива ссылок IAge Рис. 7.4. Демонстрация полиморфизма 13 IAge(] IAgeArray = new I Age (2); на объектах разрозненных классов 14 15 // iAgeArray[0] полиморфно ссылается на объект Tree 16 iAgeArray [0] = tree; 17 18 // iAgeArray[1] полиморфно ссылается на объект Person 19 iAgeArray[0] = person; 20 21 // отображение информации о дереве 22 string output = tree + ": " + tree.Name + "\nAge is: " + 23 tree.Age + "\n\n"; 24 25 // отображение информации о человеке 26 output += person + " : " + person.Name + "\nAge is: " + 27 person.Age + "\n\n"; 28 29 // отображение имени и возраста для каждого объекта IAge в 29а // iAgeArray 30 foreach (IAge ageReference in iAgeArray) 31 { 32 output += ageReference.Name + ": Age is " + 33 ageReference.Age + "\n"; 34 } 35 36 MessageBox.Show(output, "Demonstrating Polymorphism"); 37 38 ) // конец метода Main 39 40 ) // конец класса InterfacesTest Результат работы программы представлен на рис. 7.4. В следующем примере снова рассматривается иерархия "точка — окружность — цилиндр” с использованием интерфейсов, а не абстрактного класса для описания общих методов и свойств классов иерархии. Будет показа- но, как класс способен реализовать интерфейс, после чего выступит как базовый класс для возможности насле- дования реализации производными классами. Создадим интерфейс iShape (листинг 7.19), определяющий мето- ды Area и Volume, а также свойство Name (строки 8 и 10). Каждый класс, реализующий интерфейс IShape, дол- жен обеспечивать реализации для этих двух методов и свойства, предназначенного только для чтения. Обратите внимание, что хотя методы в данном примере не принимают аргументов, методы интерфейса могут принимать аргументы (как и обычные методы). О хорошем стиле программирования По традиции имя каждого интерфейса должно начинаться с I.
Объектно-ориентированное программирование: полиморфизм 225 1 // Листинг 7.19: IShape.cs 2 // Интерфейс IShape для иерархии "точка-окружность-цилиндр" 3 4 public interface IShape 5 { 6 // классы, реализующие IShape, должны реализовать эти методы 7 //и это свойство 8 double Area(); 9 double Volume(); 10 string Name { get; } 11 } По причине того, что класс Point3 (листинг 7.20) реализует интерфейс IShape, класс Points должен реализовать все три члена IShape. В строках 59—62 реализуется метод Area, возвращающий значение о, потому что пло- щадь любой точки равна нулю. В строках 65—68 реализуется метод volume, также возврат нищий значение о, потому что объем любой точки равен нулю. В строках 71—И реализуется свойство для чтения Name, возвра- щающее имя класса в виде строки (" Point 3"). 1 // Листинг 7.20: Point3.cs 2 // Класс Point3 реализует интерфейс IShape и представляет 3 // пару координат (х, у) 4 using System; 5 6 // Point3 реализует IShape 7 public class Point3 : IShape 8 { 9 private int x, у; // координаты Point3 10 11 // конструктор по умолчанию 12 public Point3О 13 { 14 // здесь имеет место неявное обращение к конструктору Object 15 } 16 17 // конструктор 18 public Point3(int xValue, int yValue) 19 { 20 x = xValue; 21 у = yValue; 22 } 22 24 //-свойство X 25 public int X- 26 { 27 get 28 { 29 return x; 30 } 31 32 set 33 { 34 x = value; 35 } 36 } 37 38 // свойство Y 39 public int Y 15 Зак. 3333
226 Гпава 7 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 { get { return у; } set { у = value; } ) // возвращение строкового представления объекта Point3 public override string TostringО { return "[" + X + ", " + Y + } // реализация метола Area интерфейса IShape public virtual double Area() { return 0; } // реализация метода Volume интерфейса IShape public virtual double Volume() { return 0; P ' // реализация свойства Name интерфейса IShape public virtual string Name { get { return "Point3"; } } // конец класса Point3 Когда класс реализует интерфейс, этот класс вводит тот же тип отношения "является1, что и задается интерфей- сом. В нашем примере класс Points реализует интерфейс IShape. Следовательно, объект Point3 является IShape, и объекты любого класса, наследующего из Point3, также являются IShape. Например, класс Circle3 (листинг 7.21) наследует из класса Point3; следовательно, Circles является ishape. Класс Circie3 реализует интерфейс ishape неявно и наследует методы ishape, реализованные классом Point. По причине того, что ок- ружности не имеют объема, класс Circle3 не подменяет метод volume, возвращающий ноль. Однако для класса Circles в использовании метода Area или свойства Name класса Point3 необходимости нет. Класс Circles дол- жен обеспечить для них собственную реализацию, потому что площадь и имя окружности (точнее круга) отли- чаются от имени и площади точки. В строках 52—55 метод Area подменяется для возвращения значения пло- щади круга, а в строках 65—71 подменяется свойство Name для возвращения строки "circle3". 1 // Листинг 7.21: Circle3.cs 2 // Circle3 наследует из класса Point3 и подменяет ключевые члены 3 using System; 4 5 // Класс Circle3 наследует из класса Point3 6 public class Circle3 : Point3 7 { 8 private double radius; // радиус Circle3 9
Объектно-ориентированное программирование: полиморфизм 227 10 -11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61- 62 63 64 65 66 67 68 69 70 71 72 73 // конструктор по умолчанию public Circle3() { // здесь имеет место неявное обращение к конструктору Point3 } // конструктор public Circle3(int xValue, int yValue, double radiusValue) : base(xValue, yValue) { Radius = radiusValue; } // свойство Radius public double Radius { get { return radius; ) set { // обеспечение неотрицательного значения Radius if (value >=* 0) radius value; } } // расчет диаметра Circle3 public double Diameter() { return Radius * 2; } // расчет длины окружности Circle3 public double Circumference() { return Math.PI * Diameter(); ) // расчет площади Circle3 public override double Area() { return Math.PI * Math.Pow(Radius, 2); } // возвращение строкового представления объекта Circle3 puolic override string Tostring() { return "Center - " + base.ToString() + "; Radius = " + Radius; ) // подмена свойства Name из класса Point3 public override string Name { get { return "Circle3"; } ) ) // конец класса Circle3
228 Глава 7 Класс Cylinders (листинг 7.22) наследует из класса Circle2. cylinders реализует интерфейс ishape неявно, потому что Cylinders является производным от класса Points, реализующего интерфейс ishape. Cylinders на- следует метод Area и свойство Name от класса Circles и метод Volume от Points. Однако Cylinders подменяет свойство Name и методы Area и Volume для выполнения операций, специфических для Cylinders. В строках 40— 43 подменяется метод Area для возвращения значения площади поверхности цилиндра; в строках 46—49 под- меняется метод volume для возвращения значения объема цилиндра, а в строках 59—65 подменяется свойство Name для возвращения строки "Cylinders". Обратите внимание, что Points отмечает эти методы/свойства как virtual, и это позволяет производным классам их подменять. 1 // Листинг 7.22: Cylinders.cs 2 // Cylinders наследует из класса Circle2 и подменяет ключевые члены 3 using System; 4 5 // Cylinders наследует из класса Circles 6 public class Cylinders : Circles 7 { 8 private double height; // высота цилиндра 9 10 // конструктор по умолчанию 11 public Cylinders(> 12 { 13 // здесь происходит неявное обращение к конструктору Circles 14 } 15 16 // конструктор 17 public Cylinders(int xValue, int yValue, double radiusValue, 18 double heightvalue) : base(xValue, yValue, radiusValue) 19 { 20 Height = heightvalue; 21 ) 22 23 // свойство Height 24 public double Height 25 { 26 get 27 { 28 return height; 29 } 30 31 set 32 { 33 // обеспечение неотрицательного значения высоты 34 if (value >= 0) 35 height = value; 36 } 37 } 38 39 // расчет площади Cylinders 40 public override double Area() 41 { 42 return 2 * base.Area() + base.CircumferenceO * Height; 43 } 44 45 // расчет объема Cylinders 46 public override double Volume() 47 { 48 return base.Area() * Height; 49 } 50 51 11 возвращение строкового представления объекта Cylinders 52 public override string ToString()
Объектно-ориентированное программирование, полиморфизм 229 53 { 54 return "Center = ” + base.ToString() + 55 Height = ” + Height; 56 } 57 58 // подмена свойства Name из класса Circle3 59 public override string Name 60 { 61 get 62 { 63 return "Cylinders”; 64 } 65 } 66 67 } // конец класса Cylinders Класс interfaces2Test (листинг 7.23) демонстрирует иерархию "точка — окружность — цилиндр", в которой используются интерфейсы. Класс interfaces2Test отличается только по двум пунктам от примера, приведенно- го в листинге 7.8, где тестировалась иерархия классов, созданная из абстрактного базового класса Shape. В лис- тинге 7.23 в строке 17 объявляется arrayOf Shapes как массив ссылок на интерфейс ishape, а не как массив ссы- лок базового класса Shape. Результат работы программы представлен на рис. 7.5. 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 // Листинг 7.23: Interfaces2Test.cs // Демонстрация полиморфизма с интерфейсами //в иерархии "точка-окружность-цилиндр" using System.Windows.Forms; public class Interfaces2Test { public static void Main(string[] args) { // создание объектов Point3, Circle3 и Cylinders Point3 point ~ new Point3(7, 11); Circle3 circle = new Circle3(22, 8, 3.5); Cylinders cylinder = new Cylinders(10, 10, 3.3, 10); // создание массива ссылок IShape IShape[] arrayOfShapes = new IShape(3]; Рис. 7.5. Демонстрация иерархии "точка — окружность — цилиндр" с помощью интерфейсов If arrayOfShapes[0].ссылается arrayOfShapes[0] = point; на объект Point3 // arrayOfShapes[1] ссылается arrayOfShapes[1] = circle; на объект Circle3 // arrayOfShapes[2] ссылается arrayOfShapes[2] = cylinder; на объект Cylinders string output = point.Name + ": ” + point + "\n" + circle.Name + ": " + circle + "\n" + cylinder.Name + ": " + cylinder; foreach (Ishape shape in arrayOfShapes) { output += "\n\n" + shape.Name + "\nArea = " + shape.AreaO + "\nVolume = " + shape.VolumeO ; MessageBox.Show(output, "Demonstrating Polymorphism"); ) )
230 Глава 7 7.10. Делегаты В главе 4 обсуждался процесс передачи объектами переменных членов в качестве аргументов в методы. Однако иногда для объектов более предпочтительно передавать методы в качестве аргументов другим методам. Напри- мер, предположим, что необходимо отсортировать серию значений по возрастанию и по убыванию. Вместо того чтобы создавать отдельные методы сортировки по возрастанию и убыванию (по одному для каждого типа срав- нения), можно спроектировать один метод, принимающий в качестве аргумента ссылку на используемый метод сравнения. Для выполнения сортировки по возрастанию методу сортировки можно передать ссылку на метод сравнения по возрастанию; для выполнения сортировки по убыванию методу сортировки можно передать ссыл- ку на метод сравнения по убыванию. После этого метод сортировки будет использовать эту ссылку для сорти- ровки списка; методу не нужно будет знать о том, какой тип сортировки он выполняет: по возрастанию или по убыванию. Язык C# не предоставляет возможности передачи ссылок на методы в качестве аргументов напрямую другим методам, но обеспечивает так называемые делегаты (delegates), являющиеся классами, инкапсулирующими на- боры ссылок методам. Объект делегата, содержащий ссылки на методы, может быть передан в другой метод. Вместо прямой передачи ссылки на метод объект может отправить экземпляр делегата, содержащий ссылку метода, который необходимо передать. После этого метод, принимающий ссылку на делегата, может активизи- ровать методы, содержащиеся в нем. Делегат, содержащий один метод, называется одиночным (singlecast delegate) и создается или производится от класса Delegate. Делегаты, содержащие много методов, называются комбинированными (multicast delegate) и создаются или производятся от класса MulticastDelegate. Оба класса делегатов принадлежат к пространству имен System. Для использования делегата его сначала нужно объявить в программе. В объявлении делегата указывается заго- ловок метода (параметры и возвращаемое значение). Методы, ссылки которых будут содержаться в рамках объ- екта делегата, должны иметь тот же заголовок, что указан в объявлении делегата. После этого создаются мето- ды, имеющие данную сигнатуру. Второй шаг — создание экземпляра делегата, содержащего ссылку на данный метод. Затем можно активизировать содержащуюся в экземпляре делегата ссылку на метод. Данный процесс будет проиллюстрирован в следующем примере. Класс DelegateBubbleSort (листинг 7.24), являющийся модифицированной версией алгоритма пузырьковой сортировки, использует делегаты для сортировки целочисленного массива в порядке возрастания или убывания. В строках 6 и 7 представлено объявление делегата Comparator. Для объявления делегата (строка 7) объявляется сигнатура метода — ключевое слово delegate после модификатора доступа к члену класса (в данном случае public), за которым следует возвращаемый тип, имя делегата и список параметров. Делегат Comparator опреде- ляет сигнатуру для методов, принимающих два аргумента int и возвращающих bool. Обратите внимание, что делегат Comparator не содержит тела. Далее будет показано, что приложение (листинг 7.25) реализует методы, принадлежащие сигнатуре делегата Comparator, после чего передает эти методы (как аргументы типа Comparator) в метод SortArray. Объявление делегата не определяет его предполагаемой роли или реализации; рассматриваемое приложение использует этот делегат при сравнении двух значений типа int, тогда как другие приложения могут использовать его в иных целях. Листинг 7.24. Пузырьковая сортировка с помощью делегатов Т ... .. . .. . . ____ .......... ..... ______fe._______________;___ 1 // Листинг 7.24: DelegateBubbleSort.cs 2 // Демонстрирует делегаты для сортировки чисел 3 4 public class DelegateBubbleSort 5 { 6 public delegate bool Comparator(int element1, 7 int element2); 8 9 // сортировка массива с помощью делегата Comparator 10 public static void SortArray(int[] array, 11 Comparator Compare) 12 { 13 for (int pass = 0; pass < array.Length; pass++) 14 15 for (int i = 0; i < array.Length - 1; i++) 16
Объектно-ориентированное программирование: полиморфизм 231 17 if (Compare(array[i], array[i +1])) 18 Swap(ref array[i], ref array[i + 1]); 19 } 20 21 // перестановка двух элементов местами 22 private static void Swap(ref int firstElement, 23 ref int secondElement) 24 { 25 int hold = firstElement; 26 firstElement = secondElement; 27 secondElement = hold; 28 } 29 } В строках 10—19 определяется метод Sort Array, принимающий в качестве аргументов массив и ссылку на объ- ект делегата Comparator. Метод SortArray модифицирует массив путем сортировки его содержимого. В стро- ке 17 используется метод делегата для определения порядка сортировки массива. В строке 17 активизируется метод, вложенный в объект делегата, путем рассмотрения ссылки делегата как метода, содержащегося в объекте делегата. Язык C# активизирует напрямую вложенную ссылку на метод с передачей ее параметров array [i] и array [i+lj. Делегат Comparator определяет порядок сортировки для двух аргументов. Если делегат Comparator возвращает значение true, это означает, что элементы расположены не по порядку, поэтому в строке 18 активи- зируется метод Swap (строки 22—28) для перестановки элементов. Если делегаг Comparator возвращает значе- ние false, это означает, что два элемента расположены в правильном порядке. Для сортировки по возрастанию делегат comparator возвращает значение true, когда первый сравниваемый элемент больше второго сравнивае- мого элемента. Аналогично, для сортировки по убыванию делегат Comparator возвращает значение true, когда первый сравниваемый элемент меньше второго сравниваемого элемента. Класс Bubblesort Form (листинг 7.25) отображает форму Form с двумя текстовыми полями и тремя кнопками. В первом текстовом поле показан список несортированных чисел, а во втором — тот же список после сорти- ровки. Кнопка Create Data создает список несортированных значений. Кнопки Sort Ascending и Sort Descend- ing сортируют массив по возрастанию и убыванию соответственно. Методы SortAscending (строки 42—45) и SortDescending (строки 60—63) имеют сигнатуру, соответствующую сигнатуре, определенной в объявлении делегата comparator (т. е. каждый метод получает два значения int и возвращает bool). Далее будет показано, что программа передает в метод DelegateBubbleSort делегат SortArray, содержащий ссылки на методы SortAscending И SortDescending, которые задают поведение сортировки класса DelegateBubbleSort. rifi ----” Листинг 7.25. _____________ 1 // Листинг 7.25: BubbleSortForm.cs 2 // Демонстрация пузырьковой сортировки с помощью делегатов для 3 // определения порядка сортировки 4 using System; 5 using System.Drawing; 6 using System.Collections; 7 using System.ComponentModel; 8 using System.Windows.Forms; 9 10 public class BubbleSortForm : System.Windows.Forms.Form 11 { 12 private System.Windows.Forms.TextBox originalTextBox; 13 private Systern.Windows.Forms.TextBox sortedTextBox; 14 private System.Windows.Forms.Button createButton; 15 private System.Windows.Forms.Button ascendingButton; 16 private System.Windows.Forms.Button descendingButton; 17 private System.Windows.Forms.Label originalLabel; 18 private System.Windows.Forms.Label sortedLabel; 19 20 private int[] elementArray - new int[10]; 21 22 // генерация случайного набора чисел для сортировки 23 private void createButton_Click(object sender, 24 System.EventArgs e)
232 Гпава 7 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 { // очистить TextBoxes originalTextBox.Clear(); sortedTextBox.Clear(); // создание генератора случайных чисел Random randomNumber = new Random(); // заполнение elementArray случайными целыми числами for (int i « 0; i < elementArray.Length; i++) { elementArray[i] = randomNumber.Next(100); originalTextBox.Text + - elementArrayfi] + "\r\n"; ) } // реализация делегата для сортировки по возрастанию private bool SortAscending(int element1, int element2) { return element1 > element2; ) /Л сортировка случайно сгенерированных чисел по возрастанию private void ascendingButton_Click(object sender, System.EventArgs e) { // сортировка массива, передача делегата для SortAscending DelegateBubbleSort.SortArray(elementArray, new DelegateBubbleSort.Comparator( SortAscending)); DisplayResults(); } // реализация делегата для сортировки по убыванию private bool SortDescending(int elementl, int element2) { return elementl < element2; 1 // сортировка случайно сгенерированных чисел по убыванию private void descendingButton_Click(object sender, System. EventArgs e) { // сортировка массива, передача делегата для SortDescending DelegateBubbleSort.SortArray(elementArray, new DelegateBubbleSort.Comparator( SortDescending)) ; DisplayResults() ; } // отображение отсортированного массива в sortedTextBox private void DisplayResults() { sortedTextBox.Clear(); foreach (int element in elementArray) sortedTextBox.Text += element + "\r\n"; } // основная точка входа для приложения public static void Main(string[] args)
Объектно-ориентированное программирование: полиморфизм 233 88 { 89 Application.Run(new BubbleSortForm()); 90 } 91 } Методы ascendingButton_Click (строки 48—57) и descendingButton Click (строки 66—75) активизиру- ются при нажатии пользователем кнопок SortAscending и SortDescending соответственно. Метод ascendingButton_Click передает в DelegateBubbleSort метод SortArray несортированный elementArray (стро- ка 52) и ссылку на метод SortAscending. Синтаксис в строках 53—54 new DelegateBubbleSort.Comparator(SortAscending создает делегат Comparator, содержащий ссылку на метод SortAscending. В методе descendingButton_Click строки 70—72 передают в метод SortArray несортированный массив elementArray (строка 52) и ссылку на ме- тод SortDescending. (Мы продолжим использование делегатов в главах 9—II при рассмотрении обработки со- бытий и многопоточной обработки.) Результат работы программы представлен на рис. 7.6. б Рис. 7.6. Демонстрация пузырьковой сортировки: а — набор случайных целых чисел б — числа, отсортированные по возрастанию; в — числа, отсортированные по убыванию 7.11. Перегрузка операторов Манипуляции объектами классов завершаются отправкой сообщений (в форме обращений к методам) в объек- ты. Такое представление с вызовом методов для определенных типов классов, особенно для математических, может оказаться несколько тяжеловесным. Для этих классов было бы удобно пользоваться расширенным набо- ром встроенных в C# операторов для указания манипуляций объектами. В данном разделе рассматривается ак- тивизация операторов C# для работы с объектами классов в процессе, называемом перегрузкой операторов. Замечание по технологии программирования__________________________________________ Используйте перегрузку операторов, если при этом программа становится более понятной, вместо выполнения тех же операций с помощью явных обращений к методам. Замечание по технологии программирования____________________________________________ Избегайте избыточного или несогласованного использования перегрузки операторов, поскольку при этом про- грамма становится непонятной и сложной для прочтения. Язык C# позволяет программисту перегружать большинство операторов для повышения их "чувствительности" в используемом контексте. Некоторые операторы перегружаются часто, особенно операторы присваивания и различные арифметические операторы, например + и -. Функции, выполняемые перегруженными операторами, также могут выполняться явными обращениями к методам, однако представление операторами более естест- венно. В листинге 7.26 приведен пример использования перегрузки операторов классом комплексных чисел. Класс CompiexNumber перегружает операторы сложения (+), вычитания (-) и умножения (*) для того, чтобы про- грамма могла прибавлять, вычитать и умножать экземпляры класса CompiexNumber с помощью обычного мате- матического представления. В строках 7 и 8 объявляются члены данных для действительной и мнимой частей комплексного числа.
234 Глава 7 Листинг 7.26. Перегрузка операторов для комплексных чисел ...................... , Л.:...-. 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 // Листинг 7.26: CoirplexNumber.cs // Класс, перегружающий операторы сложения, вычитания //и умножения комплексных чисел public class ComplexNumber { private int real; private int imaginary; // конструктор по умолчанию public ComplexNumber() {} // конструктор public ComplexNumber(int a, int b) { Real = a; Imaginary = b; } // возвращение строкового представления ComplexNumber public override string Tostring() { return " (*' + real + (imaginary < 0 ? ” — " + (imaginary * -1) : " + " + imaginary) + "i)";. } // свойство Real public int Real { get { return real; } set ( real = value; ) } // конец свойства Real // свойство Imaginary public int Imaginary { get ( return imaginary; ) set { imaginary = value; ) } // конец свойства Imaginary // перегрузка оператора сложения public static ComplexNumber operator + ( ComplexNumber x, ComplexNumber y)
Объектно-ориентированное программирование: полиморфизм 235 61 { ,. 62 return new CornplexNumber( 63 x.Real + у.Real, x.Imaginary + y.Imaginary); 64 } 65 66 H альтернатива перегруженному оператору + 67 // для сложения 68 public static CornplexNumber Add(CornplexNumber x, 69 . CornplexNumber y) 70 { 71 return x + y; 72 } 73 74 // перегрузка оператора вычитания 75 public static CornplexNumber operator - ( 76 CornplexNumber x, CornplexNumber y) 77 { 78 return new CornplexNumber ( 79 x.Real - y.Real, x.Imaginary - y.Imaginary); 80 ) 81 82 // альтернатива перегруженному оператору - 83 // для вычитания 84 public static CornplexNumber Subtract(CornplexNumber x, 85 CornplexNumber y) 86 { 87 return x - y; 88 } 89 90 // перегрузка оператора умножения 91 public static ComplexNumber operator * ( 92 CornplexNumber x, CornplexNumber y) 93 ’ { 94 return new CornplexNumber( 95 x.Real * y.Real - x.Imaginary * y.Imaginary, 96 x.Real * y.Imaginary x.Real * y.Imaginary); 97 ) 98 99 // альтернатива перегруженному оператору * 100 // для умножения 101 public static CornplexNumber Multiply (CornplexNumber x, 102 CornplexNumber y) 103 { 104 return x * y; 105 } 106 107 ) // конец класса CornplexNumber В строках 59—64 перегружается оператор сложения (+) для выполнения сложения комплексных чисел (CornplexNumber). Ключевое слово operator, за которым следует оператор, указывает, что метод перегружает этот оператор. Методы, перегружающие бинарные операторы, должны принимать два аргумента. Первый аргу- мент — левый операнд, а второй аргумент — правый операнд. Перегруженный оператор + класса CornplexNumber принимает в качестве аргументов две ссылки CornplexNumber и возвращает CornplexNumber, пред- ставляющий сумму аргументов. Заметьте, что этот метод отмечен как public и static, что необходимо для пе- регруженных операторов. Тело метода (строки 62 и 63) выполняет сложение и возвращает результат в виде но- вой ССЫЛКИ CornplexNumber. Замечание по технологии программирования__________________________________________________________ Операторы следует перегружать для выполнения тех же самых или сходных функций с объектами классов, что операторы выполняют с объектами встроенных типов Избегайте неинтуитивного использования операторов Замечание по технологии программирования_________________________________________ Минимум один аргумент метода перегрузки операторов должен быть ссылкой на объект класса, в котором этот оператор перегружен. При этом программистам не придется изменять работу операторов во встроенных типах.
236 Глава 7 Не все языки .NET поддерживают перегрузку операторов. Следовательно, чтобы убедиться в том, что класс CompiexNumber можно использовать в других языках .NET, необходимо обеспечить альтернативный метод для выполнения сложения комплексных чисел. Метод Add (строки 68—72) обеспечивает альтернативное средство сложения комплексных чисел. В строках 75—105 предоставляются перегруженные операторы и альтернатив- ные методы для вычитания и умножения комплексных чисел. Класс CompiexText (листинг 7.27) предоставляет пользовательский интерфейс для сложения, вычитания и ум- ножения комплексных чисел. Методы firstButton_Click и secondButton_Click считывают CompiexNumber из текстовых полей realTextBox и imaginaryTextBox. Метод addButton Click (строки 56—59), метод subtractButton_Click (строки 62—66) и метод multiplyButton_Click (строки 69—73) используют перегружен- ные операторы класса CompiexNumber для выполнения операций сложения, вычитания и умножения. Результат работы программы представлен на рис. 7.7. 1 // Листинг 7.27: C014plexTest.cs 2 // Пример, в котором используется перегрузка операторов 3 4 using System; 5 using System.Drawing; 6 using System.Collections; 7 using System. ComponentModel; 8 using System.Windows.Forms; 9 using System.Data; 10 11 public class BubbleSortForm : System.Windows.Forms.Form 12 { 13 private System.Windows.Forms.Label realLabel; 14 private System.Windows.Forms. Label imaginaryLabel; 15 private System.Windows.Forms. Label statusLabel; 16 17 private System.Windows.Forms.TextBox realTextBox; 18 private System.Windows.Forms. TextBox imaginaryTextBox; 19 20 private System.Windows.Forms.Button firstButton; 21 private System.Windows.Forms. Button secondButton; 22 private System.Windows.Forms. Button addButton; 23 private System.Windows.Forms. Button subtractButton; 24 private System.Windows.Forms. Button multiplyButton; 25 26 private CompiexNumber x = new CompiexNumber (); 27 private CompiexNumber у = new CompiexNumber(); 28 29 [STAThread] 30 static void Main() 31 { 32 Application.Run(new ComplexTest()); 33 } 34 35 private void firstButton_Click( 36 object sender, System.EventArgs e) 37 { 38 x.Real = Int32.Parse(realTextBox.Text); 39 x.Imaginary = Int 32.Parse(imaginaryTextBox.Text); 40 realTextBox.Clear(); 41 imaginaryTextBox.Clear(); 42 statusLabel.Text = "First Complex Number is: " + x; 43 } 44 45 private void secondButton_Click( 46 object sender, System.EventArgs e) 47 { 48 у.Real = Int32.Parse(realTextBox.Text); 49 у. Imaginary я Int32.Parse(imaginaryTextBox.Text); 50 realTextBox.Clear();
Объектно-ориентированное программирование: полиморфизм 237 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 imaginaryTextBox.Clear(); statusLabel.Text = "Second Complex Number is: " + y; // сложение комплексных чисел private void addButton_Click(object sender, { statusLabel.Text = x + " + " + у + "= " + } // вычитание комплексных чисел private void subtractButton_Click( object sender, System.EventArgs e) { statusLabel.Text = x + " - " + у + "= " + } // умножение комплексных чисел private void multiplyButton_Click( object sender, System.EventArgs e) { statusLabel.Text = x + ” * " + у + "= " + ) } // конец класса ComplexTest a 6 d Рис. 7.7. Демонстрация перегрузки операторов: а — ввод действительной и мнимой частей первого комплексного числа; б — после нажатия кнопки Set First Complex Number; в — ввод действительной и мнимой частей второго комплексного числа; а — после нажатия кнопки Set Second Complex Number; д — сложение чисел; е — умножение чисел System.EventArgs e) (x + y) ; (x - y) ; (x * y); 6 a e
238 Глава 7 7.12. Резюме X Полиморфизм дает программистам возможность написания программ в общем виде для обработки большим разнообразием существующих и будущих связанных классов. Одним из средств обработки объектов множества различных типов является использование оператора switch для выполнения соответствующей операции на каж- дом объекте, исходя из его типа. Полиморфное программирование может устранить необходимость такой логи- ки switch. Полиморфизм позволяет одному обращению к методу выполнять различные операции, в зависимости от типа объекта, принимающего вызов. Одно сообщение предполагает множество форм, отсюда и термин — "полимор- физм". С помощью этой концепции программист может работать г общими аспектами, оставляя имеющейся программе право самой "позаботиться" о частностях. Абстрактные классы определяют общее поведение иерархии наследования. Программа не может создавать объ- екты абстрактных классов, но можно объявлять ссылки на абстрактные классы. Такие ссылки способны поли- морфно манипулировать экземплярами производных классов. Класс типа sealed не может быть базовым и, следовательно, он обозначает конец иерархии наследования. Делегатами называются классы, инкапсулирующие набор ссылок на методы. Делегаты облегчают полимор- физм, давая программисту возможность указания обращений к методам, которые не будут определены до неко- торого момента. C# позволяет разработчикам перегружать большинство операторов для повышения их "чувствительности" в используемом контексте. Перегрузка операторов делает код более удобочитаемым путем предоставления ин- туитивно понятных операторов, которые можно использовать вместо тяжеловесных и расширенных обращений к методам.
ГЛАВА 8 Обработка исключительных ситуаций Логично выбрать метод и применить его. Если он не годится, лучше честно в этом признаться и попробовать другой. Но, в любом случае, нужно пробо- вать хотя бы что-нибудь. Франклин Делано Рузвельт Отбрось же худшую из двух частей. С другой — живи до просветленья. Уильям Шекспир Если они бегут и не знают, куда направляются, То я должен откуда-то появиться и поймать их. Джером Дэвид Сэлинджер И часто так бывает, что прощение вины Ее усугубляет во сто крат. Уильям Шекспир У меня очень хорошая память на лица, но для вашего я сделаю исключение. Гручо (Джулиус Генри) Маркс Темы данной главы: П обработка исключительных ситуаций и ошибок; □ применение блоков try для выделения кода, в котором могут иметь место исключительные ситуации; О использование блоков catch для определения обработчиков исключительных ситуаций; □ использование блока finally для освобождения ресурсов; П иерархия классов исключений в С#; П создание исключительных ситуаций, определенных программистом. 8.1. Введение В данной главе рассматривается процесс обработки исключительных ситуаций. Исключение — это указание на проблему, имеющую место во время исполнения программы. Термин "исключение" происходит из того, что, несмотря на возможность проблем, возникают они нечасто; если "правилом" является то, что оператор исполня- ется в основном корректно, то "исключением из правила" является возникновение проблем. Обработка исклю- чительных ситуаций помогает разработчикам создавать программные приложения, которые могут решать (или обрабатывать) "исключения из правил". Во многих случаях обработка исключения позволяет программе про- должать работу так, будто ничего не произошло. Более серьезная проблема может нарушить нормальную рабо- ту программы, и вместо команды об уведомлении пользователя о сбое, просто прервать исполнение программы в рабочем порядке. Особенности, описываемые в данной главе, позволяют программистам создавать понятные, надежные и отказоустойчивые программы. Стиль и подробности обработки исключительных ситуаций в C# частично основаны на работе Эндрю Кенига (Andrew Koenig) и Бьерна Страуструпа (Bjame Stroustrup), озаглавленной "Exception Handling for C++ (revised)” ("Обработка исключительных ситуаций в C# (исправлено)")1. Проектировщики в C# реализовали механизм об- 1 А. Кениг и Б. Страуструп, "Обработка исключительных ситуаций в C++ (исправлено)". Материалы конференции Usenix C++. — Сан-Франциско. — Апрель —1990.'—С. 149—176
240 Гпава 8 работки исключительных ситуаций подобно тому, как это сделано в C++, взяв за основу наработки Кенига и Страуструпа. Данная глава начинается с обзора концепций обработки исключительных ситуаций, после чего демонстрируют- ся основные методики обработки исключений. Далее в главе рассматривается иерархия классов обработки ис- ключений. Обычно любая программа запрашивает и освобождает ресурсы (например, файлы на диске) во время исполнения. Часто эти ресурсы ограничены, либо могут использоваться только одной программой за раз. Авто- ры демонстрируют часть механизма обработки исключений, позволяющую программе использовать ресурс, после чего гарантированно освободить его для использования другими программами. Затем в главе приводится пример, представляющий несколько свойств класса System. Exception (базового класса всех классов исключе- ний), за которым следует пример, демонстрирующий процесс создания и использования собственных (разрабо- танных программистом) классов исключений. В конце главы описывается практическое применение обработки исключительных ситуаций, где программа обрабатывает исключения, порожденные арифметическими расчета- ми, в результате которых появляются значения, выходящие за рамки конкретного ти^а данных; такое условие называется арифметическим переполнением. 8.2. Обзор процесса обработки исключительных ситуаций Логика программы часто проверяет условия, определяющие ход исполнения программы Рассмотрим следую- щий псевдокод: Выполнить задачу Если предыдущая задача выполнена некорректно, Выполнить обработку ошибки Выполнить следующую задачу Если предыдущая задача выполнена некорректно, Выполнить обработку ошибки Представленный псевдокод начинается с выполнения определенной задачи. После этого проверяется коррект- ность ее выполнения. Если выполнение не корректно, то ошибка исправляется (обрабатывается), В противном случае весь процесс запускается заново и продолжается выполнением следующей задачи. Несмотря на дейст- венность такой формы логики обработки исключительных ситуаций, совмещение логики программы с логикой обработки ошибок может затруднить читаемость программы, возможность ее модификации, обслуживания и отладки, особенно если речь идет о крупных приложениях. На самом деле, если многие потенциальные пробле- мы возникают нечасто, то совмещение логики программы с логикой обработки исключений может значительно снизить производительность программы, потому что ей придется проверять массу дополнительных условий для определения возможности выполнения каждой очередной задачи. Обработка исключительных ситуаций позволяет разработчику удалить код обработки исключений из "основной магистрали" исполнения программы. При этом программа становится понятнее, и повышается возможность ее модификации. Программисты могут обрабатывать любые исключительные ситуации на выбор: все типы исклю- чений, все исключения определенного типа либо все исключения группы взаимосвязанных типов. Такая гиб- кость снижает вероятность пропуска ошибок, и этим повышается устойчивость программного приложения. Совет по тестированию и отладке________________________________________________________ Обработка исключительных ситуаций помогает повысить отказоустойчивость программы При простоте написа- ния кода обработки исключений программисты будут с большим желанием им пользоваться. Замечание по технологии программирования_______________________________________________ Не пользуйтесь исключениями для обычной передачи управления, хотя это и можно делать. Прослеживать большое количество случаев исключений сложно, и программы с большим количеством исключений сложнее читать и обслуживать. Обработка исключительных ситуаций создана для обработки ошибок синхронизации— ошибок, возникающих во время нормального управления процессом исполнения программы. Расхожими примерами таких ошибок являются внедиапазонное индексирование массивов, арифметическое переполнение (т. е. появление значения за пределами представимого диапазона значений), деление на ноль, недопустимые параметры методов и дефицит доступной памяти. Обработка исключений не предназначена для работы с асинхронными событиями, такими как расширения I/O, поступления сетевых сообщений, щелчки кнопками мыши, нажатия клавиш и т. п.
Обработка исключительных ситуаций 241 О хорошем стиле программирования_____________________________________________________ Избегайте использования обработки исключений в целях, не имеющих отношения к исправлению ошибок, пото- му что при этом программа становится слишком сложной для прочтения При работе с языками программирования, не поддерживающих обработку исключительных ситуаций, разработ- чики часто откладывают написание кода обработки ошибок "на потом”, а иногда попросту забывают его вклю- чить. В результате этого создаваемые программные продукты теряют свою надежность. C# позволяет програм- мистам легко обрабатывать исключительные ситуации с самого начала создания проекта. При этом разработчи- ки должны прикладывать массу усилий к внедрению стратегии обработки исключений в проекты создания программного обеспечения. Замечание по технологии программирования_____________________________________________ Попытайтесь включить стратегию обработки исключений в систему в самого начала процесса разработки. До- бавление эффективной методики обработки исключений после реализации системы может быть затрудни- тельным. Замечание по технологии программирования_____________________________________________ Раньше программисты использовали множество методов реализации кода обработки исключений. Обработка исключительных ситуаций обеспечивает единообразную методику обработки ошибок. Это помогает разработчи- кам, работающим над крупными проектами, разбираться в кодах обработки исключений, написанных их кол- легами. Механизм обработки исключительных ситуаций также полезен для обработки проблем, возникающих при взаи- модействии программы с элементами программных средств, например, с методами, конструкторами, компоно- вочными блоками и классами. Вместо того чтобы обрабатывать возникающие проблемы изнутри, указанные программные элементы часто используют исключения для уведомления приложения о возникновении ошибок. Это дает программистам возможность реализации заказных средств обработки исключений для каждого прило- жения. Распространенная ошибка программирования_____________________________________________ Приостановка работы программного компонента может оставить ресурс, — например, поток файлов или устрой- ство ввода/вывода — в состоянии, в котором другие программы не могут получить доступ к ресурсам. Это назы- вается утечкой ресурсов Совет по повышению производительности________________________________________________ Если исключительная ситуация не возникает, то код ее обработки понижает производительность очень незначи- тельно. Поэтому программы, реализующие обработку исключений, работают более эффективно, нежели про- граммы, выполняющие обработку исключений в рамках логики программы. Совет по повышению производительности________________________________________________ Обработка исключений должна применяться только для решения проблем, возникающих нечасто. В качестве "эвристического правила" можно привести следующую формулировку если оценка проблемы занимает минимум 30% времени выполнения того или иного оператора, тогда программа должна сделать проверку на предмет ошибки "на линии"; в противном случае непроизводительные издержки обработки исключительной ситуации за- медлят исполнение программы1. Замечание по технологии программирования______________________________________ Методы с условиями, содержащими распространенные ошибки, должны возвращать значение null (или другое подходящее значение), а не выбрасывать исключение. Программа, вызывающая такой метод, просто может проверить возвращаемое значение для определения успеха или отказа при вызове метода1 2 Сложные программы обычно составляются из предопределенных программных компонентов (например, из оп- ределенных в .NET Framework), а также особых компонентов приложений, использующих предопределенные компоненты. Когда предопределенный компонент сталкивается с проблемой, то ему требуется механизм сопос- тавления проблемы с компонентом, присущим данному приложению: предопределенный компонент не может "знать" заранее, как каждое приложение будет обрабатывать возникающие проблемы. Обработка исключитель- ных ситуаций упрощает комбинирование программных компонентов и организацию их эффективного совмест- 1 См. "Best Practices for Handling Exceptions [C++]". .NET Framework Developer s Guide, Visual Studio .NET Online Help ("Лучшие методы обработки исключительных ситуаций [С#]'1. Руководство разработчика структуры .NET. Оперативная справка Visual Studio .NET). 2 См. "Best Practices for Handling Exceptions [C++]" ("Лучшие методы обработки исключительных ситуаций [С#]"). 16 Зак. 3333
242 Глава 8 ного функционирования предоставлением предопределенным компонентам возможности передачи возникаю- щих проблем компонентам приложения с их последующей обработкой методами данного приложения. Обработка исключений соотносится с ситуациями, в которых метод, обнаруживший ошибку, не может ее обра- ботать. Такой метод просто выбрасывает исключение. Нет гарантий, что для обработки такого типа исключе- ний в наличии будет обработчик исключений— код, исполняющийся при обнаружении программой исключе- ния. При наличии обработчика исключение будет захватываться и обрабатываться. Результат незахваченного исключения зависит от режима, в котором исполняется программа, — в режиме отладки или в стандартном ре- жиме. Если программа, работающая в режиме отладки, обнаруживает незахваченное исключение, то появляется диалоговое окно, в котором разработчик может выяснить проблему в отладчике, либо продолжить исполнение программы путем игнорирования возникшей проблемы. В стандартном режиме исполнения Windows- приложение выводит диалоговое окно, разрешающее'пользователю продолжить или приостановить исполнение программы, а в консольном окне открывается диалоговое окно, позволяющее либо открыть программу в отлад- чике, либо остановить ее исполнение. Для активизации обработки исключительных ситуаций в C# применяются блоки типа try. Блок try состоит из ключевого слова try, за которым следуют фигурные скобки, заключающие блок кода, в котором могут иметь место исключения. Блок try охватывает утверждения (операторы), которые могут вызвать появление исключе- ний. Сразу за блоком try следует ноль или более блоков типа catch (также называемы,х обработчиками catch) Каждый обработчик catch содержит в круглых скобках параметр исключения, обозначающий тип исключения, который может исправить обработчик catch. Если в параметр исключения входит имя необязательного пара- метра, то обработчик catch может использовать это имя параметра для взаимодействия с захваченным объек- том исключения. В необязательном порядке программисты могут включать обработчик catch без параметров, который будет захватывать все типы исключений. После последнего обработчика catch необязательный блок finally содержит код, исполняющийся всегда, независимо от того, имеет место исключение или нет. Распространенная ошибка программирования____________________________________________________ Обработчик catch без параметров должен быть последним обработчиком catch, идущим за конкретным блоком try; в противном случае возникает синтаксическая ошибка. Когда вызванный в программе метод или универсальный язык Common Language Runtime (CLR) обнаруживают проблему, то метод или CLR выдают исключение. Точка программы, в которой возникает исключение, называ- ется точкой выбрасывания исключения— важным местоположением для целей отладки (см. разд. 8.6). Исклю- чения являются объектами классов, расширяющих класс Exception пространства имен System. Если исключение имеет место в блоке try, то блок try истекает (т. е. сразу прекращает свое исполнение), и управление про- граммой передается первому обработчику catch (если он есть), следующему за блоком try. Считается, что в C# применяется модель прерывания обработки исключений, потому что блок try, включающий в себя выданное исключение, сразу прекращает исполнение, если имеет место это исключение1. Как в любом блоке кода, когда исполнение блока try прекращается, определенные в блоке локальные переменные выходят за пределы области действия. Затем CLR осуществляет поиск первого обработчика catch, который может проанализировать тип появившегося исключения. CLR определяет местонахождение совпадающего (соответствующего) catch сравне- нием типа выданного исключения с каждым типом параметра исключения catch до тех пор, пока не найдется соответствие. Последнее имеет место, если типы идентичны, либо если тип выданного исключения является производным классом типа параметра исключения. По окончании обработки с помощью catch, локальные пе- ременные, объявленные в рамках обработчика catch (включая параметр catch), выходят за рамки области дей- ствия. При нахождении соответствия исполняется код, входящий в совпадающий обработчик catch. Все остав- шиеся обработчики catch для этого блока try игнорируются, и исполнение программы возобновляется в блоке finally (если он имеется). Исполнение программы продолжается с первого утверждения (оператора), указанной за последовательностью try/catch/finally. Если в блоке try исключения нет, тогда CLR игнорирует обработчики исключений для данного блока. Испол- нение программы возобновляется со следующего оператора, приведенного за последовательностью try/catch. Если исключение, возникшее в блоке try, не имеет совпадающего обработчика catch, либо если исключение имеет место в операторе, не входящем в блок try, тогда исполнение метода, содержащего этот оператор, сразу прекращается, и CLR пытается определить местоположение включающего блока try в вызываемом методе. Данный процесс называется развертыванием стека (см. разд. 8.6). 1 В некоторых языках применяется модель возобновления обработки исключения, когда после обработки управление возвращается в точку, в которой исключение было выдано, и исполнение программы возобновляется с этой точки.
Обработка исключительных ситуаций 243 8.3. Пример: исключение DivideByZeroException Рассмотрим простой пример обработки исключительной ситуации. В приложении, приведенном в листинге 8.1, используются try и catch для обозначения блока кода, который может выдавать исключения и при их появле- нии обрабатывать. Форма данного приложения содержит два текстовых поля (TextBox), в которые пользователь может вводить целые числа При нажатии кнопки Click to Divide программа активизирует метод divideButton_ciick (строки 46—84), который принимает данные ввода пользователя, преобразует значения ввода в тип int и делит первое число (numerator — числитель) на второе число (denominator — знаменатель). Если предположить, что пользователь вводит целые числа и не указывает 0 в качестве знаменателя для деления, тогда divideButton ciick отображает результат деления в outputLabel. Однако, если пользователь вводит не целое число или указывает 0 в качестве знаменателя, тогда возникает исключительная ситуация Данная про- грамма демонстрирует процесс захвата таких исключений 1 // Листинг 8.1: DivideByZeroTest.cs 2 // Основы обработки исключительных ситуаций в C# 3 4 using System; 5 using System.Drawing; 6 using System.Collections; 7 using System.ComponentModel 8 using System.Windows.Forms; 9 using System.Data; 10 11 // класс демонстрирует обработку исключений, возникающих 12 // при делении на ноль в целочисленной арифметике 13 //и при неправильном цифровом форматировании 14 public class DivideByZeroTest : System.Windows.Forms.Form 15 { 16 private System.Windows.Forms.Label numeratorLabel; 17 private System.Windows.Forms.TextBox numeratorTextBox; 18 19 private System.Windows.Forms.Label denominatorLabel; 20 private System.windows.Forms.TextBox denominatorTextBox; 21 22 private System.Windows.Forms.Button divideButton; 23 private System.Windows.Forms.Label outputLabel; 24 25 // требуется переменная разработчика 26 private System.ComponentModel.Container components = null; 27 28 // конструктор по умолчанию 29 public DivideByZeroTest() 30 { • 31 // требуется для поддержки Windows Form Designer 32 InitializeComponent(); 33 } 34 . . k 35 // основная точка входа для приложения 36 [STAThread] 37 static void Main() 38 { 39 Application.Run(new DivideByZeroTest()); 40 } 41 42 // код, сгенерированный Visual Studio .NET 43 44 // получение введенных пользователем целых чисел и деление 45 // числителя на знаменатель 46 private void divideButton_Click( 47 object sender, System.EventArgs e)
244 Глава 8 48 { 49 outputLabel.Text = 50 51 // получение пользовательского ввода и вызов Quotient 52 try 53 { 54 // Convert.Tolnt32 генерирует FormatException, если 55 // аргумент не является целым числом 56 int numerator = Convert.Tolnt32(numeratorTextBox.Text); 57 int denominator = 58 Convert.Tolnt32(denominatorTextBox.Text); 59 60 // при делении генерируется исключение 61 // DivideByZeroException, если знаменатель равен 0 62 int result = numerator / denominator; 63 64 outputLabel.Text = result.Tostring(); 65 66 }// конец try 67 68 // обработка неверного формата числа 69 catch (FormatException) 70 { 71 MessageBox.Show("You must enter two integers", 72 "Invalid Number Format", 73 MessageBoxButtons.OK, MessageBoxIcon.Error); 74 . } 75 ' 76 // пользователь, сделавший попытку деления на ноль 77 catch (DividebyZeroException DivideByZeroException) 78 { 7 9 MessageBox.Show(DividebyZeroExcepfion.Message, 80 "Attempted to Divide by Zero", 81 MessageBoxButtons.OK MessageBoxIcon.Error); 82 } 83 84 } // конец метода divideButton_Click 85 86 } // конец класса DivideByZeroTest Перед подробным обсуждением программы рассмотрим рис. 8.1. В первом окне показано успешное вычисле- ние, где пользователь вводит числитель 100 и знаменатель 7. Обратите внимание, что результат (14)— целое число, потому что при делении на целые числа всегда получаются целые числа. В следующих двух окнах пока- зан результат ввода не целого значения: во втором текстовом поле пользователь вводит слово "hello". При на- жатии кнопки Click to Divide программа делает попытку преобразовать строку ввода пользователя в значения int с помощью метода Convert.Tolnt32. Если аргумент к convert.Tolnt32 не является допустимым представ- лением целого числа (в данном случае недопустимым строковым представлением целого числа), то метод гене- рирует FormatException (пространство имен System). Программа обнаруживает исключение и выводит диалого- вое окно сообщения об ошибке, указывая, что пользователь должен ввести два целых числа. Последние два окна демонстрируют результат после попытки деления на ноль. В целочисленной арифметике CLR автоматически осуществляет проверку на предмет деления на ноль и генерирует DividebyZeroException (пространство имен System), если знаменатель равен нулю. Программа обнаруживает исключение и выводит диалоговое окно сооб- щения об ошибке деления на ноль*. Рассмотрим действия пользователя и передачу управления, выдающую результаты, показанные на рис. 8.1. Пользователь вводит значения в текстовые поля (TextBox), обозначающие числитель и знаменатель, затем на- жимает кнопку Click to Divide. В этой точке программа активизирует метод divideButton Click (строки 46— 1 CLR допускает деление на ноль чисел с плавающей точкой, что дает в результате плюс или минус бесконечность, в зависимости от знака делимого.. Деление нуля на ноль является особым случаем, результатом которого будет значение, называемое "не число". Программы могут осуществлять проверки на предмет таких результатов с помощью констант для плюс бесконечности (Positiveinfinity), минус бесконечности (Negativeinfinity) и нечисла (NaN), определяемых в структурах Double (для вычислений double)u Single (для вычислений float).
Обработка исключительных ситуаций 245 84). В строке 49 присваивается пустая строка outputLabel для удаления всех предыдущих результатов, потому что программа готова к выполнению новых вычислений. В строках 52—66 определяется блок try, содержащий код, который может выводить исключения, а также код, который не должен исполняться при возникновении исключения. Например, программа не должна отображать новый результат в outputLabel (строка 64), если ус- пешно не завершено вычисление в строке 62. Помните, что исполнение блока try сразу прерывается при воз- никновении исключения, поэтому остаток кода в блоке try исполняться не будет. в г Рис. 8.1. Демонстрация обработки исключений: а — числитель и знаменатель заданы корректно; б — знаменатель является тестом; в — выброшено исключение; г — знаменатель равен 0; д — выброшено исключение с сообщением о делении на 0 Два оператора, считывающие целые числа из текстовых полей (строки 56—58), вызывают метод Convert. Toint 32 для преобразования строки в значения int. Этот метод выдает исключение Format Except ion, если он не может преобразовать аргумент в целое число. Если в строках 56—58 значения преобразуются кор- ректно (т. е. если исключение не возникает), тогда в строке 62 numerator делится на denominator и переменной result присваивается результат. Если знаменатель равен нулю, тогда в строке 62 CLR необходимо выдать DividebyZeroException. Если в строке 62 исключение не возникает, тогда в строке 64 отображается результат деления. Если в блоке try исключений не возникает, тогда программа успешно завершает исполнение блока try, достигая строки 64 и игнорируя обработчики catch в строках 69—74 и 77—82: исполнение программы продолжается с первого оператора, указанного за последовательностью try/catch. В данном примере програм- ма достигает конца обработчика исключений divideButton Click, поэтому исполнение метода прекращается, и программа ожидает следующего действия пользователя. Сразу за блоком try расположены два обработчика catch (также называются блоками catch): в строках 69—74 определяется обработчик исключений для FormatException, а в строках 77—82 — обработчик исключений для DividebyZeroException. Каждый обработчик catch начинается с ключевого слова catch, за которым в круглых скобках следует параметр исключения, указывающий тип исключения, обрабатываемого catch. Код обработки исключения появляется в обработчике catch. Вообще говоря, при появлении в блоке try исключения обработ- чик catch захватывает его и обрабатывает. В листинге 8.1 первый обработчик catch указывает, что он захваты- вает типы FormatExceptions (выданные методом Convert .Toint32), а второй обработчик catdh указывает, что он захватывает типы DividebyZeroExceptions (выданные CLR). При возникновении исключения исполняется только совпадающий обработчик catch. В данном примере оба обработчика исключений выводят для пользова- теля диалоговое окно с сообщением об ошибке. Когда управление программой достигает конца обработчика catch, то программа рассматривает исключение как обработанное, и управление продолжается с первого опера- тора, идущего за последовательностью try/catch (в данном примере — конец метода). Во второй раз пользователь вводит в качестве знаменателя слово "hello”. При исполнении строк 57 и 58 Convert.Toint32 не может преобразовать эту строку в int, поэтому Convert.Toint32 создает объект FormatException и выдает его для указания, что метод не смог преобразовать string в int. При возникновении исключения блок try истекает (его исполнение прекращается). Любые локальные переменные, определенные в блоке try, выходят за рамки области действия; следовательно, такие переменные не доступны для обработчиков исключений. После этого CLR пытается определить местоположение совпадающего обработчика catch, начи- ная с catch в строке 69. Программа сравнивает тип выданного исключения (FormatException) с типом в круг- лых скобках, следующим за ключевым словом catch (также FormatException). Имеет место соответствие, по- этому данный обработчик исключений исполняется, и программа игнорирует все прочие обработчики исключе- ний, указанные за соответствующим блоком try. Как только обработчик исключений catch прекращает свое исполнение, локальные переменные, определенные в рамках обработчика catch, выходят из области действия.
246 Гпава 8 Если соответствия нет, тогда программа сравнивает тип выданного исключения с очередным обработчиком catch в последовательности и повторяет процесс до нахождения соответствия. Замечание по технологии программирования_____________________________________________ Включите в блок try значительную логическую часть программы, в которой несколько операторов могут выда- вать исключения, вместо использования отдельных блоков try для каждого оператора, выдающего исключи- тельную ситуацию. Однако для необходимой степени детализации обработки исключений каждый блок try дол- жен содержать достаточно маленький раздел кода, чтобы при возникновении исключительной ситуации был из- вестен контекст, и обработчики catch могли надлежащим образом ее обработать. Распространенная ошибка программирования ____________________________________________ Попытка доступа к локальным переменным блока try в одном из связанных с блоком try обработчике catch является синтаксической ошибкой. До исполнения соответствующего обработчика catch выполнение блока try прекращается, и его локальные переменные выходят за рамки области действия. Распространенная ошибка программирования_____________________________________________ Указание разделенного запятыми списка параметров исключений в обработчике catch является синтаксической ошибкой. Каждый catch может иметь только один параметр исключения. В третий раз пользователь вводит о в качестве знаменателя. При исполнении строки 62 CLR выдает объект DivideByZeroException для регистрации попытки деления на ноль. При обнаружении исключения исполнение блока try также сразу прерывается, и программа пытается определить местоположение совпадающего обработ- чика catch, начиная с обработчика catch в строке 69. Программа сравнивает тип выданного исключения (DivideByZeroException) с указанным в круглых скобках типом, за которым следует ключевое слово catch (FormatException). В этом случае совпадения нет, поскольку типы исключений разные и Format Except ion не является базовым классом DivideByZeroException. Поэтому программа переходит к строке 77 и сравнивает тип выданного исключения (DivideByZeroException) с типом в круглых скобках, следующим за ключевым словом catch (DivideByZeroException). Происходит совпадение, и обработчик исключений исполняется. В строке 79 в этом обработчике используется свойство Message класса Exception для отображения пользователю сообщения об ошибке. При наличии дополнительных обработчиков catch программа их проигнорирует. 8.4. Иерархия .NET-исключений Механизм обработки исключительных ситуаций позволяет выдавать и захватывать только объекты класса Exception и производных от него классов1. В данном разделе описываются несколько классов исключений .NET Framework. Помимо этого, будет обсуждаться вопрос о том, может тот или иной метод выдавать исключения. Класс Exception пространства имен System является базовым классом иерархии исключений .NET Framework. Двумя наиболее важными классами, производными от класса Exception, являются ApplicationException и SystemException. ApplicationException— это базовый класс, который разработчики могут расширять для соз- дания типов данных исключений, относящихся к конкретным приложениям. Классы исключений, определенные программистами, рассматриваются в разд. 8.7. Программы могут восстанавливаться из большинства ApplicationException и продолжать свое исполнение. CLR может сгенерировать SystemException в любой точке программы во время ее исполнения. Многие из таких исключений можно избежать корректным кодированием. Они называются исключениями во время прогона и являются производными от класса SystemException. Например, если программа делает попытку доступа к эле- менту вне диапазона индексов массива, тогда CLR выдает исключение типа indexOutofRangeException (класс, производный от SystemException). Точно так же исключение во время прогона имеет место, когда программа использует ссылку на объект для манипуляций с еще не существующим объектом (т. е. ссылка имеет значение null). Попытка использования такой нулевой ссылки вызывает NullReferenceException (другой тип SystemException). В соответствии с материалами Microsoft "Лучшие методы обработки исключительных ситуа- ций [С#]"1 2, программы, как правило, не могут возобновлять работу из большинства исключений, выдаваемых CLR. Следовательно, программы обычно не должны выдавать или захватывать SystemException. 1 На самом деле, можно захватывать (catch) исключения типов, непроизводных от класса Exception, используя обработчик catch без параметров. Это полезно для обработки исключений из кода, написанного на других языках, не требующих, чтобы все типы исключений были производными от класса Exception в .NET Framework 2 См. 'Best Practices for Handling Exceptions [C++]". .NET Framework Developer's Guide, Visual Studio .NET Online Help ("Лучшие методы обработки исключительных ситуаций [С#]". Руководство разработчика структуры .NET. Оперативная справка Visual Studio .NET).
Обработка исключительных ситуаций 247 Примечание ______________________________________________________________________________ Полный список производных от Exception классов см. по ссылке "Класс Exception" в указателе оперативной документации Visual Studio .NET. Преимущество использования иерархии обработки исключений состоит в том, что обработчик catch может за- хватывать исключения конкретного типа, либо использовать тип базового класса для захвата исключений в иерархии связных типов исключений. Например, обработчик catch, указывающий параметр исключения типа Exception, также может захватывать исключения всех классов, расширяющих Exception, потому что Exception является базовым классом для всех классов исключений. Этим обеспечивается полиморфная обработка связан- ных исключений. Преимуществом последнего подхода является то, что обработчик исключений может исполь- зовать параметр исключения для манипуляций захваченным исключением. Если обработчику не нужен доступ к захваченному исключению, тогда параметр исключения можно опустить. Если тип исключения не указан, тогда обработчик будет захватывать все исключения. Использование наследования при работе с исключениями позволяет обработчику захватывать соответствующие исключения с кратким комментарием. Разумеется, обработчик исключений может захватывать каждый тип ис- ключения производного класса отдельно, однако захват типа исключений базового класса будет более сжатым. Впрочем, это имеет смысл только в том случае, если поведение обработки одинаково для базового класса и производных классов. В противном случае исключения каждого производного класса следует захватывать от- дельно. Итак, теперь нам известно, что существует множество различных типов исключений. Также мы выяснили, что выдавать исключения могут как методы, так и CLR. Но как определить, нто исключение может иметь место программе? Для методов в классах .NET Framework можно изучить детальное описание методов в оперативной документации. Если метод выдает исключение, то его описание содержит раздел под названием "Exceptions", в котором указываются типы исключений, выдаваемые этим методом, и кратко описываются их потенциальные причины. Например, обратитесь к разделу "Convert.ToInt32 method" в указателе оперативной документации Visual Studio .NET. В документе, описывающем метод, щелкните на ссылке public static int Tolnt32 (string);. В открывающемся документе в разделе "Exceptions" отмечается, что метод convert.Tolnt32 выдает три типа исключений; ArgumentException, FormatException И OverflowException, И описываются причины появления каждого типа. Замечание по технологии программирования_________________________________________________ Если метод может выдавать исключения, то операторы, активизирующие этот метод, должны помещаться в блоках try, и исключения должны захватываться и обрабатываться. Определить выдачу исключительных ситуаций CLR сложнее. Обычно такая информация появляется в "C# Lan- guage Specification" ("Спецификации языка С#"), имеющейся в оперативной документации. Для доступа к спе- цификации языка выберите команду Contents... в меню Help Visual Studio. В окне Contents разверните Visual Studio .NET | Visual Basic and Visual C# | Reference | Visual C# Language | C# Language Specification. В спецификации языка определен синтаксис языка и указаны случаи, в которых имеют место исключительные ситуации. Например, на рис. 8.1 продемонстрировано, что CLR выдает DivideByZeroException при попытке программы деления на ноль в целочисленной арифметике. В спецификации языка (см. разд. 7.7.2 в специфика- ции) рассматривается оператор деления и его исключение. В этом разделе пользователь найдет подробности о том, когда может возникнуть исключение DivideByZeroException. 8.5. Блок finally Часто программы запрашивают и освобождают ресурсы динамически (т. е. во время исполнения). Например, программа, считывающая файл с диска, сначала запрашивает открытие этого файла. При успешном исполнении этого запроса программа считывает его содержимое. Как правило, операционные системы разрешают осущест- влять операции с одним файлом не более, чем одной программе за раз. Следовательно, когда программа закан- чивает обработку файла, она его закрывает (т. е. освобождает ресурс). После этого данным файлом могут поль- зоваться другие программы. Закрытие файла предотвращает утечку ресурсов, при которой ресурс файла недос- тупен для других программ, потому что программа, использовавшая этот файл, его не закрыла. Во избежание утечек ресурсов, программы, получающие определенные типы ресурсов (например, файлы), должны явно осво- бождать эти ресурсы в системе. В таких языках программирования, как С и C++, где программист несет ответственность за динамическое управление памятью, самым распространенным типом утечки ресурсов является утечка памяти. Это происхо- дит, когда программа распределяет память (в C# это делается с помощью операции new), но не освобождает ее,
248 Глава 8 когда данный объем памяти уже ей (программе) не нужен. В C# это не имеет особого значения, потому что CLR выполняет "сборку мусора" памяти, которая уже не требуется исполняющейся программе. Впрочем, в C# могут иметь место другие типы утечки ресурсов (например, от не закрытых файлов, как упоминалось ранее). Совет по тестированию и отладке_________________________________________________________ CLR не устраняет утечки памяти полностью. CLR не будет отправлять объект "в мусор" до тех пор, пока про- грамма больше не станет ссылаться на этот объект. Таким образом, утечки памяти могут иметь место, если про- граммисты по ошибке оставляют ссылки на ненужные объекты. Большинство ресурсов, требующих явного освобождения, имеют потенциальные исключения, связанные с об- работкой этих ресурсов. Например, программа, обрабатывающая файл, может получать lOException во время обработки. По этой причине код, обрабатывающий файл, как правило, появляется в блоке try. Независимо от успеха обработки программой файла, последняя должна закрыть файл, если он больше не нужен. Предположим, что программа поместила весь код запроса ресурса и освобождения ресурса в блок try. Если исключений не происходит, тогда блок try исполняется нормально и освобождает все ресурсы после их использования. Однако при возникновении исключения исполнение блока try может прекратиться до исполнения кода освобождения ресурса. Можно скопировать весь код освобождения ресурсов в обработчики catch, но при этом обслуживать и модифицировать код станет намного сложнее. Механизм обработки исключительных ситуаций C# предоставляет блок finally, который будет гарантированно исполняться, если управление программой входит в соответствующий блок try. Блок finally исполняется неза- висимо от того, как Выполняется блок try: в нормальном режиме или при возникновении исключительной си- туации. Такая гарантия делает блок finally идеальным местоположением для размещения кода, освобождаю- щего ресурсы, полученные и использованные в соответствующем блоке try. Если блок try исполняется успеш- но, тогда блок finally исполняется сразу после прекращения работы блока try. При возникновении в блоке try исключения блок finally исполняется сразу после обработки этого исключения обработчиком catch. Если ис- ключение не захвачено обработчиком hatch, относящимся к данному блоку try, либо, если обработчик catch, относящийся к данному блоку try, выдает исключительную ситуацию, то исполняется блок finally, после чего исключение обрабатывается следующим включающим блоком try (при его наличии). Совет по тестированию и отладке_________________________________________________________ Блок finally обычно содержит код для освобождения ресурсов, полученных в соответствующем блоке try; это превращает блок finally в эффективное средство устранения утечек ресурсов Совет по тестированию и отладке________________________________________________________ Единственной причиной неисполнения блока finally при передаче управления в соответствующий блок try за- ключается в том. что до исполнения блока finally может прекратиться исполнение всего программного прило- жения Совет по повышению производительности___________________________________________________ Как правило, ресурсы должны освобождаться сразу, как только становится очевидным, что они больше не нужны программе, чтобы они сразу стали доступными для повторного применения. Это повысит использование ресур- сов в программе. Если за блоком try следует один или более обработчиков catch, то использование блока finally не обязатель- но. Если за блоком try не заданы обработчики catch, то блок fi naily должен появляться сразу после блока try. Если после блока try указаны обработчики catch, то блок finally появляется после последнего обработчика catch. Последовательность try/catch/finally могут разделять только пробелы, пустые строки и комментарии. Распространенная ошибка программирования________________________________________________ Размещение блока finally перед обработчиком catch является синтаксической ошибкой. Программное приложение C# в листинге 8.2 демонстрирует, что блок finally исполняется всегда, даже если в соответствующем блоке try не имеют места никакие исключительные ситуации. Программа состоит из метода Main (строки 10—59) и четырех других static-методов, которые Main активизирует для показа finally: DoesNotThrowException (строки 62—85), ThrowExceptionWithCatch (строки 88—114), ThrowExceptionWithoutCatch (строки 117—138) И ThrowExceptionCatchRethrow (строки 141—173). Примечание______________________________________________________________________________ В этом примере используются static-методы, так что Main может активизировать их напрямую, без создания объектов класса UsingExceptions. Это позволяет сосредоточиться на механике последовательности try/catch/finally.
Обработка исключительных ситуаций 249 Листинг 8.2. Демонстрация исполнения блока finally в любом случае, исключительных ситуаций 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 // Листинг 8.2: UsingExceptions.cs // Использование блоков finally using System; // демонстрация обязательного исполнения finally class UsingExceptions { // входная точка для приложения static void Main(string[] args) { // Случай 1: в вызванном методе не имеют место исключения Console.WriteLine("Calling DoesNotThrowException”); DoesNotThrowException(); // Случай 2: исключение имеет место и захватывается // в вызванном методе Console.WriteLine("\nCalling ThrowExceptionWithCatch"); ThrowExceptionWithCatch(); // Случай 3: исключение имеет место, но не захватывается // в вызванном методе из-за отсутствия обработчиков catch Console.WriteLine( "\nCalling ThrowExceptionWithoutCatch"); // вызов ThrowExceptionWithoutCatch try .{ ThrowExceptionWithoutCatch(); } // обработка исключения, возвращенного из // ThrowExceptionWithoutCatch catch { Console.WriteLine("Caught exception from " + "ThrowExceptionWithoutCatch in Main"); ) // Случай 4: исключение имеет место и захватывается в вызванном // методе, после чего перенаправляется вызывающему элементу Console.WriteLine( "\nCalling ThrowExceptionCatchRethrow"); // вызов ThrowExceptionCatchRethrow try { ThrowExceptionCatchRethrow(); ) // обработка исключения, возвращенного из // ThrowExceptionCatchRethrow catch { Console.WriteLine("Caught exception from " + "ThrowExceptionCatchRethrow in Main"); } } // конец метода Main
250 Глава 8 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 76а 77 78 79 80 ai 82 83 84 85 86 87 88 39 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 105а 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 // исключительные ситуации не выдаются public static void DoesNotThrowExceptionO { // блок try не выдает исключений try { Console.WriteLine("In DoesNotThrowException”); } // данный обработчик catch никогда не исполняется catch { Console.WriteLine("This catch never executes"); ) // блок finally исполняется, потому что выполнился // соответствующий блок try finally { Console.WriteLine( ’’Finally executed in DoesNotThrowException"); } Console.WriteLine("End of DoesNotThrowException"); } // конец метода DoesNotThrowException // выдача исключения и его локальный захват public static void ThrowExceptionWithCatchO {’ // блок try выдает исключение try { Console.WriteLine("In ThrowExceptionWithCatch”); throw new Exception( "Exception in ThrowExceptionWithCatch"); ) // захват исключения, выданного в блок try catch (Exception error) { Console.WriteLine("Message: " + error Message); } // блок finally исполняется, потому что выполнился // соответствующий блок try finally { Console.WriteLine( "Finally executed in ThrowExceptionWithCatch"); ) Console.WriteLine("End of ThrowExceptionWithCatch"); } // конец метода ThrowExceptionWithCatch // выдача исключения и его не захват локально public static void ThrowExceptionWithoutCatch() { II выдача исключения, но не захват его try
Обработка исключительных ситуаций 251 121 122 123 124 125 126 127 128 128а 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 163а 164 165 166 167 168 169 170 171 172 173 174 175 { Console.WriteLine("In ThrowExcept ionWithoutCatch"); throw new Exception( "Exception in ThrowExceptionWithoutCatch*'); ) // блок finally исполняется, потому что выполнился // соответствующий блок try finally { Console.WriteLine("Finally executed in " + "ThrowExceptionWithoutCatch"); ) // недостижимый код; будет сгенерирована логическая ошибка Console.WriteLine("This will never be printed"); } // конец метода ThrowExceptionWithoutCatch // выдача исключения, его захват и повторная выдача public static void ThrowExceptionCatchRethrow() { 11 блок try выдает исключение try { Console.WriteLine("Int ThrowExceptionCatchRethrow"); throw new Exception( "Exception in ThrowExceptionCatchRethrow"); ) // захват любого исключения; размещение в ошибочном объекте catch(Exception error) { Console.WriteLine("Message: ” + error.Message); // повторная выдача исключения для последующей обработки throw error; // недостижимый код; будет сгенерирована логическая ошибка } // блок finally исполняется, потому что выполнился // соответствующий блок try finally { Console.WriteLine("Finally executed in " + "ThrowExceptionCatchRethrow"); ) // недостижимьй код; будет сгенерирована логическая ошибка Console.WriteLine("This will never be printed"); } // конец метода ThrowExceptionCatchRethrow } // конец класса UsingException Результат работы программы: Calling DoesNotThrowException In DoesNotThrowException Finally executed in DoesNotThrowException End of DoesNotThrowException
252 Глава 8 Calling ThrowExceptionWithCatch In ThrowExceptionWithCatch Message: Exception in ThrowExceptionWithCatch Finally executed in ThrowExceptionWithCatch End of ThrowExceptionWithCatch Calling ThrowExceptionWithoutCatch In ThrowExceptionWithoutCatch Finally executed in ThrowExceptionWithoutCatch Caught exception from ThrowExceptionWithoutCatch in Main Calling ThrowExceptionCatchRethrow In ThrowExceptionCatchRethrow Message: Exception in ThrowExceptionCatchRethrow Finally executed in ThrowExceptionCatchRethrow Caught exception from ThrowExceptionCatchRethrow in Main В строке 14 метода Main активизируется метод DoesNotThrowException (строки 62—85). Блок try (строки 65— 68) начинается с выдачи сообщения (строка 67). Блок try не выдает никаких исключений, поэтому управление программой доходит до закрывающей скобки блока try и обработчика catch (строки 71—74) и исполняет блок finally (77—81), выдающий сообщение. В этой точке работа программы продолжается с первого оператора после блока finally (строка 83), который выдает сообщение, указывающее на достижение конца метода. Затем управление программой возвращается методу маш. В строке 20 метода Main активизируется метод ThrowExceptionWithCatch (строки 88—114), начинающийся с выдачи сообщения в своем блоке try (строки 91—97). После этого блок try создает новый объект Exception и использует оператор throw для выдачи объекта исключения (строки 95—96). Строка, переданная в конструк- тор, становится сообщением об ошибке объекта исключения. После выполнения оператора throw в блоке try исполнение блока try сразу прекращается, и управление программой продолжается с первого обработчика catch (строки 100—103), следующего за этим блоком try. В данном примере тип выброшенного исключения соответствует типу, указанному в catch, поэтому в строке 102 выдается сообщение, указывающее на имевшее место исключение. Затем исполняется блок finally (строки 106—ПО) и выдается сообщение. В этой точке управление программой продолжается с первого оператора после блока finally (строка 112), указывающего на достижение конца метода, после чего управление программой возвращается в метод Main. Обратите внимание, что в строке 102 использовано свойство Message объекта исключения для доступа к сообщению об ошибке, от- носящемуся к исключению (сообщению, переданному в конструктор Exception). В разд. 8.6 рассматривается несколько свойств класса Exception. Распространенная ошибка программирования______________________________________________________ Выражение throw— объект исключения — должно принадлежать к классу Exception или к одному из его про- изводных классов. В строках 27—30 метода Main определяется блок try, в котором метод Main активизирует метод ThrowExceptionWithoutCatch (строки 117—138). Блок try позволяет методу Main захватить любые исключения, выдаваемые ThrowExceptionWithoutCatch. Блок try в строках 120—126 метода ThrowExceptionWithoutCatch начинается с выдачи сообщения. После этого блок try выдает Exception (строки 124 и 125), и исполнение блока try сразу прекращается. Обычно управление программой возобновляется с первого обработчика catch, сле- дующего за блоком try. Однако данный блок try не имеет соответствующих ему обработчиков catch. Следова- тельно, исключение в методе ThrowExceptionWithoutCatch не захватывается. Нормальное управление програм- мой не может продолжаться до тех пор, пока исключение не будет захвачено и обработано. Таким образом, CLR приостановит исполнение ThrowExceptionWithoutCatch, и управление программой вернется в метод Main. До того как управление вернется в метод Main, исполняется блок finally (строки 129—133) и выдается сообщение. В этой точке управление программой возвращается в метод Main: любые операторы после блока finally испол- няться не будут. В данном примере из-за того, что исключение, выданное в строках 127 и 128, не захвачено, исполнение метода ThrowExceptionWithoutCatch всегда завершается после прекращения исполнения блока finally. В методе Main обработчик catch в строках 34—38 захватывает исключение и отображает сообщение, указывающее, что исключение в Main было захвачено. В строках 46—49 метода Main определяется блок try, в котором Main активизирует метод ThrowExceptionCatchRethrow (строки 141—173). Блок try позволяет Main захватить любое исключение, выда- ваемое ThrowExceptionCatchRethrow. Блок try в строках 144—150 метода ThrowExceptionCatchRethrow начина- ется выдачей сообщения. Затем блок try выдает Exception (строки 148 и 149). Исполнение блока try сразу пре-
Обработка исключительных ситуаций 253 кращается, и управление программой продолжается в первом обработчике catch (строки 153—161), следующем за блоком try. В данном примере выданный тип исключения соответствует типу, указанному в catch, поэтому строка 155 выдает сообщение, указывающее на то, что имело место исключение В строке 158 используется оператор throw для повторной выдачи исключения. Это указывает на то, что обработчик catch выполнил час- тичную обработку исключения (или вообще никакой), и теперь передает исключение назад в вызывающий ме- тод (в данном случае Main) для дальнейшей обработки. Обратите внимание, что выражение к оператору throw является ссылкой на захваченное исключение. При повторной выдаче первоначального исключения можно так- же использовать оператор throw; без выражения. В разд. 8.6 рассматривается оператор throw с выражением. Такой оператор throw позволяет программистам захватить исключение, создать объект исключения, после чего выдать другой тип исключения из обработчика catch. Разработчики библиотек классов часто это делают для "подгонки" типов исключений, выдаваемых из методов, под свои библиотеки классов с целью обеспечения дополнительной информации для отладки. Замечание по технологии программирования________________________________________________ Перед выдачей исключения в вызывающий метод выдающий исключение метод должен освободить любые ре- сурсы, занятые до того, как имело место исключение. Замечание по технологии программирования________________________________________________ По возможности, метод должен обрабатывать выдаваемые в него исключения, а не передавать исключения в другие части программы. Обработка исключения в методе ThrowExceptionCatchRethrow — не полная, потому что программа не может исполнить код в обработчике catch, помещенном после вызова оператора throw (строка 158). Следовательно, работа метода ThrowExceptionCatchRethrow прекратится, и управление программой будет возвращено в метод Main. Опять же, исполнится блок finally (строки 164—168) и выдаст сообщение до того, как управление вер- нется в метод Main. Когда управление возвращается в метод Main, обработчик catch в строках 53—57 захватит исключение и отобразит сообщение, указывающее на то, что исключение было захвачено. После этого работа программы прекращается. Обратите внимание, что точка, в которой управление программой возобновляется после исполнения блока finally, зависит от состояния обработки исключительной ситуации. Если блок try успешно исполняется или если обработчик catch захватывает и обрабатывает исключение, тогда управление продолжается со следующего оператора после блока finally. Если исключение не захватывается либо если обработчик catch перебрасывает его, тогда управление программой продолжается со следующего объемлющего блока try. Объемлющий блок try может быть расположен в вызывающем методе или в одном из его вызывающих элементов. Вложение по- следовательности try/catch в блок try также возможно, и в этом случае обработчики catch внешнего блока try будут обрабатывать любые исключения, которые не были захвачены во внутренней последовательности try/catch. Если блок try имеет соответствующий блок finally, тогда этот блок finally исполняется даже в том случае, если исполнение блока try прерывается из-за оператора return. Распространенная ошибка программирования________________________________________________ Выдача исключения из блока finally может представлять определенную опасность. Если незахваченное ис- ключение ожидает обработки во время исполнения блока finally, и этот блок выдает новое исключение, неза- хваченное в блоке finally, тогда первое исключение теряется, и в следующий объемлющий блок try переда- ется только новое исключение Совет по тестированию и отладке_________________________________________________________ При размещении в блоке finally кода, который может выдать исключение, всегда вкладывайте такой код в по- следовательность try/catch, захватывающую надлежащие типы исключений. Это предотвратит потерю неза- хваченных и переброшенных исключений, возникающих до исполнения блока finally. Замечание по технологии программирования________________________________________________ Механизм обработки исключений C# удаляет код обработки ошибок из основной магистрали исполнения про- граммы для повышения удобочитаемости программы. Не вводите try/catch/f inally вокруг каждого операто- ра, который может выдать исключение, потому что при этом читаемость программы снижается. Лучше размес- тить один блок try вокруг значительной части кода После этого блока try разместите блоки catch для обра- ботки каждого из возможных исключений, а за ними — один блок finally.
254 Гпава 8 8.6. Свойства класса Exception Как обсуждалось в разд. 8.4, типы данных исключений являются производными из класса Exception, имеющего несколько свойств. Эти свойства часто используются с целью формулирования сообщений об ошибках для за- хваченного исключения. Рассмотрим два важных свойства: Message и stackTrace. Свойство Message сохраняет сообщение об ошибке, относящееся к объекту Exception. Данное сообщение может быть сообщением по умол- чанию, связанным с типом исключения, либо специально подготовленным сообщением, переданным в конст- руктор объекта исключения при построении объекта исключения. Свойство stackTrace содержит строку, пред- ставляющую стек вызова метода. Среда исполнения программы ведет список вызовов методов, сделанных к определенному моменту, string StackTrace представляет этот последовательный список методов, обработка которых не закончена на момент возникновения исключительной ситуации. Точное местоположение в про- грамме, в котором имеет место исключение, называется точкой выброса исключения. Совет по тестированию и отладке___________________________________________________________ Стек содержит полный список вызовов методов на время возникновения исключительной ситуации. Это позво- ляет программисту просматривать серии вызовов методов, приведших к исключению. Информация в системе прослеживания стека включает в себя имена методов в стеке вызовов на время возникновения исключения, имена классов, в которых определены эти методы имена пространств имен, в которых определены эти классы, а также номера строк. Номер первой строки в следе (trace) стеков указывает точку выброса исключения Номера последующих строк указывают местоположения, из которых вызывался каждый метод. Другое свойство, которым часто пользуются разработчики библиотек классов, — innerException. Обычно про- граммисты применяют его для "упаковывания" объектов исключений, захваченных в коде, с последующей вы- дачей новых типов исключений, характерных для их библиотек. Например, программист, реализующий систему бухгалтерского учета, может иметь некий код номеров счетов, в котором эти номера введены как string, но в коде представлены целыми числами. Как уже известно, программа может преобразовывать string в значения int с помощью метода Convert.Tolnt32, выдающего исключение FormatException при обнаружении недопус- тимого числового формата. При появлении некорректного формата номера счета разработчик бухгалтерской системы может либо задать другое сообщение об ошибке, нежели введенное по умолчанию в FormatException, либо указать НОВЫЙ тип исключения, например InvalidAccountNumberFormatExpression. В этих случаях про- граммист сделает так, что код будет захватывать FormatException, создавать в обработчике catch новый тип исключения с передачей первоначального исключения как одного из аргументов конструктора. Первоначаль- ный объект исключения становится InnerException нового объекта исключения. Когда в коде, использующем библиотеку бухгалтерской системы, появляется invalidAccountNumberFormatExpression, обработчик catch, захватывающий исключение, может просмотреть первоначальное исключение через свойство InnerException. Таким образом, данное исключение указывает, что был введен недопустимый номер счета, и что проблема за- ключалась в неправильном (некорректном) числовом формате. Следующий пример (листинг 8.3) демонстрирует свойства Message, StackTrace и InnerException, а также метод Tostring. Кроме того, в примере показано развертывание стека— процесс, при котором делается попытка распрложения надлежащего обработчика catch для не захваченного исключения. В данном примере прослежи- ваются методы на стеке вызовов, так что особо обсуждаться будет свойство stackTrace и механизмы разверты- вания стеков. Исполнение программы начинается с вызова Main, который становится первым методом в стеке вызовов мето- дов. В строке 16 блока try в методе Main активизируется Methodi (строки 43—46), который становится вторым методом в стеке. Если Methodi выдает исключение, блок catch в строках 22—38 обрабатывает это исключение и выдает о нем информацию. В строке 45 метода Methodi активизируется Method2 (строки 49—52), который ста- новится третьим методом в стеке. Затем в строке 51 метода Method2 активизируется Methods (определенный в строках 55—70), который становится четвертым методом в стеке. Совет по тестированию и отладке___________________________________________________________ При считывании следа стека начинайте сверху и сначала прочтите сообщение об ошибке. После этого прочи- тайте оставшуюся часть стека с поиском первой строки, указывающей на код, написанный в программе. Обычно это и есть местоположение, которое вызвало появление исключительной ситуации. В этой точке стек вызовов методов для программы — следующий: Methods Method? Methodi Main с последним методом (Methods), вызванным сверху, и первым методом (Main), вызванным снизу. Блок try (строки 58—61) в методе Methods активизирует метод convert.Tolnt32 (строка60) и делает попытку преобра-
Обработка исключительных ситуаций 255 зовать string в int. В этой точке Convert.Tolnt32 становится пятым— и последним— методом в стеке вы- зовов. Аргумент к convert.Toint32— не в целочисленном формате, поэтому в строке60 выдается исключение FormatException, захваченное в строке 64 в Method3. Исключение прерывает обращение к Convert.Tolnt32, и этот метод удаляется из стека вызовов методов. Обработчик catch создает объект Exception, после чего выдаст это исключение. Первый аргумент к конструктору Exception для данного примера является сообщением об ошибке— "Exception occurred in Method3". Второй аргумент—объект innerException—захваченное ис- ключение FormatException. Обратите внимание, что StackTrace для этого нового объекта исключения отобра- зит точку, в которой было выдано исключение (строка 66). Теперь прекращается исполнение метода Method3, потому что исключение, выданное в обработчик catch, не захватывается в теле метода. Таким образом, управ- ление будет возвращено оператору, который активизировал Methods в предыдущем методе в стеке вызовов (Method?). При этом Methods удаляется (или выносится) из стека вызовов методов. О хорошем стиле программирования_________________________________________________________ При захвате и повторной выдаче исключения введите в него дополнительную информацию для отладки. Для этого создайте объект Exception с более подробной информацией по отладке и передайте первоначальное за- хваченное исключение в конструктор нового исключения для инициализации свойства InnerException. Когда управление возвращается в строку 51 в Method2, CLR определяет, что строка 51 не является блоком try. Следовательно, исключение не может быть захвачено в Method2, и исполнение Method? прекращается. При этом Method? развертывается из стека вызовов методов, и управление возвращается в строку 45 в Methodi. Строка 45 также не является блоком try, поэтому исключение не может быть захвачено в Methodi. Исполнение метода прекращается и развертывается (возвращается) из стека вызовов с возвращением управления в строку 16 метода Main, расположенного в блоке try. Исполнение блока try в Main прекращается, и обработчик catch в строках 22—38 захватывает исключение. Обработчик catch использует метод Tostring и свойства Message, stackTrace и innerException для создания выходных данных. Обратите внимание, что развертывание стека продолжается до тех пор, пока либо catch не обработает исключение, либо не прекратится исполнение программы. ---------- i Листинг 8.3. Свойства класса Exception и развертывание стека 1 // Листинг 8.3: Properties.cs 2 // Развертывание стека и свойства класса Exception 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 using System; // демонстрация использования свойств Message, StackTrace // и InnerException class Properties { static void Main(string[] args) ,1 // вызов Methodi; любое сгенерированное им исключение будет // захвачено в следующем обработчике catch try { Methodi(); 1 // выходное строковое представление Exception; // затем выходные значения свойств InnerException, // Message и StackTrace catch (Exception exception) { Console.WriteLine( "exception.ToString(): \n{0)\n", exception.ToString()); Console.WriteLine("exception.Message: \n{0)\n", exception"Message); Console.WriteLine("exception.StackTrace: \n{0)\n", exception.StackTrace);
256 Гпава 8 34 Console.WriteLine( 35 "exception.InnerException: \n{0}", 36 exception.InnerException); 37 38 } // конец catch 39 40 } // конец Main 41 42 // вызов Method2 43 public static void Methodi () 44 { 45 Method2(); 46 } 47 48 // вызов Method3 49 public static void Method2() 50 { 51 Method3 (); 52 } 53 54 // выдача исключения Exception, содержащего InnerException 55 public static void Method3() 56 { 57 // попытка преобразования нецелочисленной строки в int 58 s try 59 { 60 Convert.Tolnt32("Not an integer"); 61 ) 62 63 // захват FormatException и переход в новое исключение 64 catch (FormatException error) 65 { 66 throw new Exception( 67 "Exception occurred in Method3", error); 68 } 69 70 } // конец метода Method3 71 72 } // конец класса UsingExceptions Результат работы программы: exception.ToString(): System.Exception: Exception occurred in Method3 ----> System.FormatException: Input string was not in a correct format. at System.Number.Parselnt32(String s, Numberstyles style. NumberFormatInfo info) at System.Convert.Tolnt32 (String s) at Properties.Method3()in C:\books\2002\cspfepl\cspfepl_examples\chi8\Fig08 03\ Properties\Properties.cs:line 60 --- End of inner exception stack trace --- at Properties.Method3()in C:\books\2002\cspfepl\cspfepl_examples\ch08\Fig08 03\ Properties\Properties.cs:line 66 at Properties.Method2()in C:\books\2002\cspfepl\cspfepl_examples\ch08\Fig08_03\ Properties\Properties.cs:line 51 at Properties.Methodi()in C:\books\2002\cspfepl\cspfepl examples\chO8\FigO8 03\ Properties\Properties.cs:line 45 at Properties.Main (String[] args)in C:\books\2002\cspfepl\cspfepl_examples\ch08\Fig08_03\ Properties\Properties.cs:line 16
Обработка исключительных ситуаций 257 exception.Message: Exception occurred in Methods exception.StackTrace: at Properties.Methods()in C:\books\2002\cspfepl\cspfepl examples\chO8\FigO8_O3\ Properties\Properties.cs:line 66 at Properties.Method2()in C:\books\2002\cspfepl\cspfepl examples\chO8\FigO8_O3\ Properties\Properties.cs:line 51 at Properties.Methodi()in C:\books\2002\cspfepl\cspfepl_examples\ch08\Fig08_03\ Properties\Properties.cs; lirie 45 at Properties.Main (String[] args)in C:\books\2002Xcspfepl\cspfepl examples\ch08\Fig08_03\ PropertiesXProperties. cs-: line 16 exception.InnerException: System.FormatException: Input string was not in a correct format. at System.Number.ParseInt32 (String s, Numberstyles style, NumberFormatlnfo info) at System.Convert.Tolnt32 (String s) at Properties.Methods()in C:\books\2002\cspfepl\cspfepl_examples\ch08\Fig08_03\ Properties\Properties.es:line 60 В первом блоке вывода (переформатированном для наглядности) показано строковое представление исключе- ния, возвращенное из метода Tostring. Оно начинается с имени класса исключения, за которым следует значе- ние свойства Message. В следующих восьми строках показано строковое представление объекта InnerException. В оставшейся части блока вывода показано свойство StackTrace для исключения, выданного в Methods. stackTrace представляет состояние стека вызовов методов в точке выброса исключения, а не в точке, где ис- ключение будет захвачено. Каждая строка stackTrace, начинающаяся с "at", представляет метод в стеке вызо- вов. Эти строки указывают метод, в котором имело место исключение, файл, в котором находится этот метод, и номер строки в файле. Также обратите внимание, что механизм прослеживания стеков включает в себя просле- живание стека внутреннего исключения. Совет по тестированию и отладке_________________________________________________ _ _ ________ При захвате и повторной выдаче исключения введите в него дополнительную информацию для отладки. Для этого создайте объект Exception с более подробной информацией по отладке и передайте первоначальное за- хваченное исключение в конструктор нового исключения для инициализации свойства InnerException. Метод Tostring исключения возвращает строку, содержащую имя исключения, необязательный символ string, введенный при построении исключения, внутреннее исключение (если оно есть) и след стека. Следующий блок вывода (две строки) просто отображает свойство Message (Exception occurred in Methods)' исключения, выданного в Methods. Третий блок вывода демонстрирует свойство StackTrace исключения, выданного в Methods. Обратите внима- ние, что в свойство stackTrace входит след стека, начинающийся в строке 66 в Methods, потому что это — точ- ка, в которой был создан и выдан объект Exception. След стека всегда начинается с точки выдачи исключения. Наконец, последний блок вывода показывает представление Tostring свойства InnerException, включающее в себя пространство имен и имена классов этого объекта исключения, его свойства Message и StackTrace. 8.7. Классы исключений, определенные пользователем Во многих случаях программисты могут использовать существующие в .NET Framework классы исключений для обозначения исключительных ситуаций, возникающих в программах. Однако иногда появляется необходи- мость в создании типов исключений, более характерных для конкретных проблем программирования. Опреде- ленные пользователем классы исключений должны быть прямо или косвенно производными от класса ApplicationException пространства имен System. О хорошем стиле программирования________________________________________________________ Ассоциирование каждого типа сбоев с надлежащим образом именованным классом исключений повышает про- зрачность программы. 17 Зак 3333
258 Глава 8 Замечание по технологии программирования______________________________________ Перед созданием определенных пользователем классов исключений изучите классы исключений, имеющиеся в .NET Framework; возможно, нужный класс исключений уже существует. Замечание по технологии программирования_______________________________________________ Разработчикам следует создавать классы исключений только в случае необходимости захвата и обработки но- вых исключений иначе, нежели существующие классы исключений. В листингах 8.4 и 8.5 показан процесс задания и использования определенного пользователем класса исключе- ний. Класс NegativeNumberException (листинг 8.4)— определенный программистом класс исключений, пред- ставляющий исключения, возникающие при выполнении программой некорректной операции с отрицательным числом, например, извлечение квадратного корня отрицательного числа. 1 // Листинг 8.4: NegativeNumberException.cs 2 // NegativeNumberException представляет исключения, вызванные 3 // некорректными операциями с отрицательными числами 4 5 using System; 6 7 // NegativeNumberException представляет исключения, вызванные 8 // некорректными операциями с отрицательными числами 9 class NegativeNumberException : ApplicationException 10 { 11 // конструктор по умолчанию 12 public NegativeNumberException() 13 : base("Illegal operation for a negative number") 14 { 15 } 16 17 // конструктор для составления сообщения об ошибке 18 public NegativeNumberException(string message) 19 : base(message) 20 { 21 ) 22 23 // конструктор для составления сообщения об ошибке и 24 // указания объекта внутреннего исключения 25 \ public NegativeNumberException( _ 26 string message, Exception inner) 27 : base(message, inner) 28 { 29 } 30 31 } // конец класса NegativeNumberException В соответствии с доктриной корпорации Microsoft1, определенные программистами исключения должны рас- ширять класс ApplicationException, иметь имя класса, оканчивающееся словом "Exception", и определять три конструктора: по умолчанию, конструктор, принимающий аргумент типа string (сообщение об ошибке), и кон- структор, принимающий аргументы string и Exception (сообщение об ошибке и объект внутреннего исключе- ния). Вероятнее всего, NegativeNumberException будет иметь место во время выполнения арифметических операций, поэтому кажется логичным выделение класса NegativeNumberException из класса ArithmeticException. Однако класс ArithmeticException является производным от класса SystemException— категории исключений, вве- денной CLR. ApplicationException— специфический базовый класс для исключений, выданных программой пользователя, а не CLR.. 1 См. "Best Practices for Handling Exceptions [C++]". .NET Framework Developer's Guide, Visual Studio .NET Online Help ("Лучшие методы обработки исключительных ситуаций [С#]". Руководство разработчика структуры .NET. Оперативная справка Visual Studio .NET).
Обработка исключительных ситуаций 259 Класс SquareRootTest (листинг 8.5) демонстрирует определенный пользователем класс исключений. Данное приложение позволяет разработчику ввести числовое значение, после чего активизирует метод SquareRoot (строки 42—52) для извлечения квадратного корня этого значения. С этой целью SquareRoot активизирует ме- тод sqrt класса Math, принимающий в качестве аргумента неотрицательное значение double. Если аргумент от- рицательный, то Sqrt обычно возвращает константу NaN из класса Double. В данной программе авторы хотят предостеречь пользователя от извлечения квадратного корня отрицательного числа. Если введенное пользова- телем числовое значение— отрицательное, тогда SquareRoot выдает исключение NegativeNumberException (строки 46 и 47). В противном случае SquareRoot активизирует метод Sqrt класса Math для извлечения квадрат- ного корня. Когда пользователь вводит значение и нажимает кнопку Square Root, программа активизирует метод squareRootButton Click (строки 56—85). Блок try (строки 62—68) делает попытку активизации SquareRoot СО значением, введенным пользователем. Если введено недопустимое значение, имеет Место FormatException, и блок catch в строках 71—76 обрабатывает это исключение. Если пользователь ввел отрицательное число, то метод SquareRoot выдает NegativeNumberException (строки 46 и 47). Блок catch в строках 79—83 захватывает и обрабатывает это исключение. р* - •♦••'•виаав —ж-— Листинг 8.5. Класс sqi tTest выдает'исключение, если появляется ошибка при извлечении ' квадратного корня Г”" oia 1 // Листинг 8.5: SquareRootTest.cs 2 // Демонстрация определенного пользователем класса исключения 3 4 using System; 5 using System.Drawing 6 using System.Collections; 7 using System.ComponentModel; 8 using System.Windows.Forms; 9 using System.Data 10 11 // прием данных ввода и извлечение квадратного корня 12 public class SquareRootTest : Systern.Windows.Forms.Form 13 { 14 private System.windows.Forms.Label inputLabel; 15 private System.windows.Forms.TextBox inputTextBox; 16 17 private System.windows.Forms.Button squareRootButton; 18 19 private System.windows.Forms.Label outputLabel; 20 21 // требуется переменная разработчика 22 private System.ComponentModel.Container components = null; 23 24 // конструктор по умолчаний 25 public SquareRootTest() 26 { 27 I/ требуется для поддержки Windows Form Designer 28 InitializeComponent(); 29 } 30 31 // код, сгенерированный Visual Studio .NET 32 33 // главная точка входа для приложения 34 [STAThread] 35 static void Main() 36 { 37 Application.Run(new SquareRootTest()); 38 } 39 40 // извлечение квадратного корня параметра; если параметр 41 // отрицательный, ввод NegativeNumberException 42 public double SquareRoot(double operand)
260 Глава 8 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 { // при отрицательном операнде выдать NegativeNumberException if (operand < 0) throw new NegativeNumberException( "Square root of negative number not permitted"); // извлечение квадратного корня return Math.Sqrt(operand); } конец класса SquareRoot // получение данных от пользователя, преобразование в double и // извлечение квадратного корня private void squareRootButton_Click( object sender, System.EventArgs e) { outputLabel.Text = ""; // захват любых выданных NegativeNumberException try { double result = SquareRoot(Double.Parse(inputTextBox.Text)); outputLabel.Text = result.ToString(); } // обработка недопустимого числового формата catch (FormatException notlnteger) { MessageBox.Show(notlnteger.Message, "Invalid Opefation", MessageBoxVuttons.OK, MessageBoxIcon.Error); } // отображение MessageBox при вводе отрицательного числа catch (NegativeNumberException error) { MessageBox.Show(error.Message, "Invalid Operation", MessageBoxButtons.OK, MessageBoxIcon.Error); } ) // конец метода squareRootButton_Click } // конец класса SquareRootTest Результат работы программы представлен на рис. 8.2. б Рис. 8.2. Демонстрация класса SquareRootTest: а — ввод целого числа; б — ввод дробного числа; в — ввод отрицательного числа; а — сообщение об ошибке ввода отрицательного числа
Обработка исключительных ситуаций 261 8.8. Обработка переполнений с помощью операторов checked и unchecked В .NET Framework встроенные типы данных хранятся в структурах с фиксированным размером. К примеру, максимальное значение int равно 2 147 483 647. В целочисленной арифметике значение, превышающее 2 147 483 647, вызывает переполнение", тип int не может представить такое число. Переполнение также может иметь место в других встроенных типах С#. Переполнения часто являются причиной того, что программы вы- дают некорректные результаты. В C# существуют операторы checked и unchecked для указания того, имеет место целочисленная арифметика в проверенном или непроверенном контексте. В проверенном контексте CLR выдает OverflowException (про- странство имен System). Если переполнение имеет место во время оценки арифметического выражения. В не- проверенном контексте результат усекается при возникновении переполнения. Операции ++, —, *, /, + и - (как унарные, так и бинарные) могут вызвать переполнение, если используются с типами целочисленных данных (например, int и long). Также, переполнение могут вызвать явные преобразова- ния между типами целочисленных данных. Например, результатом преобразования целого числа 1 000 000 из int в short является переполнение, потому что short может сохранить максимальное значение, равное 32 767. В листинге 8.6 показаны переполнения, возникающие как в проверенном, так и непроверенном контекстах. Программа начинается с определения переменных int — nuiriberl и number2 (строки 11 и 12) и присвоения каж- дой переменной максимального значения для int, равного 2 147 483 647 (определенного Int32.Maxvalue). За- тем в строке 13 определяется переменная sum (инициализирована к 0) для сохранения суммы переменных numberl и number2. После этого в строках 15 и 16 выдаются значения numberl и number2. В строках 19—25 определяется блок try, где в строке 24 numberl и number2 складываются в проверенном кон- тексте. В круглых скобках появляется выражение для оценки в проверенном контексте, за которым следует ключевое слово checked. Переменные numberl и number2 уже содержат максимальное значение для int, поэтому сложение этих двух значений вызывает OverflowException. Обработчик catch в строках 28—31 захватывает исключение и выдает его строковое представление. В строке 39 те же вычисления выполняются в непроверенном контексте. Результат вычислений должен быть 4 294 967 294. Однако данное значение требует больше памяти, нежели можно сохранить в int, поэтому опера- тор unchecked усекает его часть, в результате чего на выходе появляется -2. Понятно, что результат непрове- ренного вычисления не является фактической суммой переменных. I Листинг 8.6. Операторы checked и unchecked и обработка арифметического переполнения , 1 // Листинг 8.6: Overflow.cs 2 // Демонстрация операторов checked и unchecked 3 4 using System; 5 б // демонстрация использования операторов checked и unchecked 7 class Overflow 8 { 9 static void Main(string[] args) 10 { 11 int numberl = Int32.MaxValue; 11 2 147 483 647 12 int nuiriber2 = Int32.MaxValue; // 2 147 483 647 13 int sum = 0; 14 15 Console.WriteLine( 16 "numberl: {О У\nnumber2: {1}", numberl, number2); 17 18 , // вычисление суммы numberl и number2 19 try 20 { 21 Console.WriteLine( 22 "\nSum integers in checked context:"); 23 24 sum = checked(numberl + number2); 25 } 26
262 Глава В 27 // захват исключения overflow , 28 catch (OverflowException overflowException) 29 { 30 Console.WriteLine(overflowException.ToString()); 31 } 32 33 Console.WriteLine( 34 "\nsum after checked operation: {0}", sum); 35 3 6 Console.WriteLine( 37 "\nSum integers in unchecked context:"); 38 39 sum = unchecked(numberl + number2); 40 41 Console.WriteLine( 42 "sum after unchecked operation: {0}", sum); 43 44 } // конец метода Main 45 46 } // конец класса Overflow Результат работы программы: numberl: 2147483647 . number2: 2147483647 Sum integers in checked context: System.OverflowException: Arithmetic operation resulted in an overflow at Overflow.Overflow.Main (String [ ] args)in C:\books\2001\cspfepl\cspfepl_examples\ch8\Fig08_06\ Overflow\Overflow.cs:line 24 sum after checked operation: 0 Sum integers in unchecked context: sum after checked operation: -2 По умолчанию вычисления осуществляются в непроверенном контексте. Это— довольно опасная практика, если вычисления производятся не в константных выражениях (например, не в буквенно-целочисленных значе- ниях). Константные выражения оцениваются в проверенном контексте во время компилирования. Результатом переполнений в таких выражениях является появление ошибок во время компиляции. Есть возможность задать свойства проекта, которые контекст по умолчанию для оценки неконстантных выражений должен будет прове- рять на предмет арифметического переполнения. В свойствах проекта можно задать проверенный контекст по умолчанию. Для этого выберите нужный проект в окне Solution Explorer. Затем в меню View выберите команду Property Pages. В диалоговом окне Property Pages щелкните на папке Configuration Properties. В Code Gen- eration измените значение Check for Arithmetic Overflow/Underflow на true. О хорошем стиле программирования_______________________________________________________________ При выполнении вычислений, результатом которых может стать переполнение, рекомендуется использовать проверенный контекст и определять обработчики исключений. Замечание по технологии программирования_______________________________________________________ Ключевые слова checked и unchecked можно использовать для оценки блоков операторов в проверенном и не- проверенном контекстах, соответственно, за которыми вводится надлежащее ключевое слово с блоком кода в фигурных скобках. 8.9. Резюме Исключение — это указание на проблему, возникающую во время исполнения программы. Механизмы обра- ботки исключений позволяют программистам создавать приложения, способные решать проблему исключений, обеспечивать программе возможность продолжать свое исполнение так, будто ничего не произошло, и, следова- тельно, помогать разработчикам в написании понятных, надежных и более отказоустойчивых программ. Обра-
Обработка исключительных ситуаций 263 ботка исключительных ситуаций также позволяет программисту удалить код обработки ошибок из "главной линии" исполнения программы. Когда метод обнаруживает ошибку и не может ее обработать, тогда он выдает исключение. Нет гарантии, что в наличии будет обработчик исключений именно появившегося типа. Если он есть, тогда исключение будет за- хвачено и обработано. В коде обработки исключений используются последовательности try/catch/finally. В блоке try содержится код, который может выдавать исключения, и код, который не должен исполняться при появлении исключения. Блок try состоит из ключевого слова try, за которым следуют фигурные скобки ({}), выделяющие блок кода, где может иметь место исключение. Если исключение появляется в блоке try, то ис- полнение блока прекращается, и программа передает управление первому обработчику catch, следующему за блоком try. Каждый обработчик catch начинается с ключевого слова catch, за которым следует необязатель- ный параметр исключения, указывающий тип исключения, обрабатываемого catch. Код обработки исключений появляется в теле обработчика catch. CLR осуществляет поиск первого catch, который может обработать тип возникшего исключения. Нужный обработчик— первый, в котором тип выданного исключения совпадает с типом исключения или является его производным, указанным в параметре исключения обработчика catch. За последним обработчиком catch указывается необязательный блок finally, который содержит код, всегда исполняемый, независимо от того, возникло ли исключение или нет. Блок finally является идеальным место- положением для размещения кода, освобождающего ресурсы, полученные и использованные в соответствую- щем блоке try. Если исключения не возникают, или если исключение захвачено и обработано, то программа возобновляет исполнение со следующего оператора после try/catch/finally. Класс Exception (пространства имен System)— базовый класс иерархии исключений .NET Framework. ApplicationException— базовый класс, который программисты могут расширять для создания новых типов данных исключений, характерных для конкретных приложений. Программы могут восстанавливаться из боль- шинства исключений ApplicationException и продолжать исполнение. CLR генерирует исключение SystemException. Как правило, программы не восстанавливаются после большинства исключений, выданных CLR. Следовательно, программы обычно не выдают SystemException и не пытаются захватить (catch) такие исключения. Оператор throw выдает объект исключения. Это оператор можно использовать в обработчике catch для повтор- ной выдачи исключения. Такое событие указывает, что блок catch выполнил частичную обработку исключения и передает его назад в вызывающий метод для дальнейшей обработки. Свойство Message класса Exception сохраняет сообщение об ошибке, связанной с объектом Exception. Оно может быть сообщением по умолчанию, ассоциированным с типом исключения, либо специально подготовлен- ным сообщением, переданным в конструктор объекта исключения во время его создания программой. Свойство StackTrace класса Exception содержит строку, представляющую стек вызовов методов в точке выброса исклю- чения. Свойство InnerException класса Exception обычно используется для "упаковывания" захваченного объекта исключения в новый объект исключения и, следовательно, для выдачи объекта нового типа исключе- ния. В C# имеются операторы checked и unchecked для задания режима проверки переполнения при выполнении арифметических операций в проверенном или непроверенном контексте. В проверенном контексте оператор checked выдает исключение OverflowException, если переполнение имеет место во время оценки арифметиче- ского выражения. В непроверенном контексте оператор unchecked усекает результат при возникновении пере- полнения.
ГЛАВА 9 (га Концепции графического пользовательского интерфейса: часть 1 ... даже мудрейшие пророки сперва убедятся в том, что событие произошло. Хорас Уолпол ... Пользователь должен ощущать власть над компьютером, а не науборот. Это достигается в программных приложениях, обладающих тремя качест- вами: реактивностью, разрешительностью и последовательностью. "Macintosh изнутри". Том 1 Apple Computer, Inc 1985 г Чтобы лучше видеть тебя, дитя мое... Волк из сказки "Красная Шапочка" Темы данной главы: □ принципы проектирования графических пользовательских интерфейсов; □ понимание, использование и создание событий; □ пространства имен, содержащие компоненты графических пользовательских интерфейсов, классы обработки событий и интерфейсы; □ создание графических пользовательских интерфейсов; □ создание кнопок, меток, текстовых полей, панелей, рамок изображений, флажков и переключателей и мани- пуляции ими; □ использование событий мыши и клавиатуры. 9.1. Введение Графический пользовательский интерфейс (Graphical User Interface, GUI) дает возможность визуального взаи- модействия с программами. Именно от GUI зависит, как будет "выглядеть” программа и какие "впечатления” возникну от работы с ней. Предлагая различные приложения с постоянным набором интуитивных компонентов пользовательских интерфейсов, GUI экономят время пользователей, которым уже не приходится запоминать, какие функции соответствуют нажатию тех или иных комбинаций клавиш, что заметно повышает производи- тельность и продуктивность работы. Замечание о "впечатлениях и ощущениях"_____________________________________________ Интуитивно понятные пользовательские интерфейсы облегчают и ускоряют освоение пользователями новых программ В качестве примера GUI на рис. 9.1 показано окно браузера Internet Explorer с некоторыми отмеченными ком- понентами GUI. В окне представлена строка меню, содержащая меню File, Edit, View, Favorites, Tools и Help. Под строкой меню расположен набор кнопок: каждая из них выполняет в Internet Explorer определенную задачу. Под кнопками находится поле раскрывающегося списка, где пользователь может вводить адрес сайта в World Wide Web, который он хочет посетить. Слева от поля расположена метка, указывающая предназначение поля По правой границе окна расположены полосы прокрутки. Они используются, когда информации больше, чем может отобразиться в одном окне. Перемещением ползунков полосы прокрутки пользователь может просмат- ривать разные части Web-страницы. Меню, кнопки, текстовые поля, метки и полосы прокрутки — это части GUI программы Internet Explorer. Они образуют удобный для пользователя (дружественный) интерфейс, по- средством которого пользователи взаимодействуют с Web-браузером Internet Explorer.
Концепции графического пользовательского интерфейса: часть 1 265 - Кнопка г Поле раскрывающегося списка г Метка г Пункт меню г Строка меню Полоса прокрутки Рис. 9.1. Образец окна Internet Explorer с компонентами GUI GUI строятся из компонентов GUI (иногда их называют оконными элементами управления (controls или widgets)). Компонент GUI — это объект, с которым пользователь взаимодействует с помощью мыши или кла- виатуры. Несколько распространенных компонентов GUI представлено в табл. 9.1. В последующих разделах каждый из этих компонентов GUI рассматривается подробно. В следующей главе представлены более сложные компоненты GUI. Таблица 9.1. Некоторые основные компоненты GUI Элемент управления Описание Label Область, в которой отображаются значки или неизменяемый текст TextBox Область, в которую пользователь вводит данные с клавиатуры. В данной области также может отображаться информация Button Область, при щелчке по которой запускается событие CheckBox Выбранный или не выбранный элемент управления GUI ComboBox Раскрывающийся список элементов, из которых пользователь может выбрать нужный щелчком на нем мышью или вводом в текстовое поле, если это допускается ListBox Область, отображающая список элементов, из которых пользователь может выбрать нуж- ный щелчком на нем мышью. Допускается выбор нескольких элементов Panel Контейнер, в который могут быть помещены компоненты ScrollBar Обеспечивает доступ польвователя к диапазону значений, не вмещающихся в контейнер 9.2. Формы Windows Формы Windows (также называются Win Forms) создают GUI для различных программ. Форма — это графиче- ский элемент, появляющийся на рабочем столе. Форма может быть диалоговым окном, просто окном или окном MDI (окно многодокументного интерфейса, рассматриваемое в главе 10). Компонент — это класс, реализую- щий интерфейс icomponent, определяющий различные поведения, которые могут реализовать компоненты. Элемент управления, например, кнопка или метка, — это компонент с графической частью. Элементы управле- ния видимы, в отличие от компонентов, которым недостает графической части. На рис. 9.2 показаны элементы управления и компоненты форм Windows, имеющиеся в панели Toolbox Visual Studio .NET: в первых двух панелях показаны элементы управления, а в третьей— компоненты Когда пользо- ватель выбирает компонент или элемент управления, он может добавить их в форму. Обратите внимание, что Pointer (пиктограмма вверху списка) не является компонентом; она обозначает операцию мыши по умолчанию.
266 Гпава 9 Если ее выделить, то можно пользоваться курсором мыши без переноса элемента на панель. В этой и следую- щих главах будут обсуждаться многие из представленных элементов управления. Рис. 9.2. Элементы управления и компоненты для форм Windows Pointer • Z3 D*eet«yEntry ' > '•QiretforySearcher /..г • 111 Eventlog HleJvstemWatdTer ^MessageQueue . ^^РЫогойпсфСоиЯег ZB Procrw. „ |£jReportDocument. §5$ Servfcetontroler 0 fimer При взаимодействии с окнами говорится, что активное окно — в фокусе. Активным называется окно, располо- женное поверх всех остальных окон, строка заголовка которого подсвечена. Окно становится активным, когда пользователь щелкает на нем мышью в любом месте. Когда окно в фокусе, операционная система направляет вводимые пользователем данные с клавиатуры и от мыши в данное приложение. Форма служит контейнером для компонентов и элементов управления. Элементы управления добавляются в форму с помощью кода. При "перетягивании” элемента управления из панели Toolbox в форму Visual Studio .NET генерирует этот код, который создает этот элемент управления и задает его основные свойства. Пользова- тель может сам написать код, однако элементы управления гораздо проще создавать и модифицировать в пане- ли Toolbox и окне Properties, предоставив Visual Studio NET разбираться с деталями. В предыдущих главах уже рассматривалось подобное визуальное программирование. В следующих нескольких главах попробуем соз- дать более сложные GUI с помощью визуального программирования. При обращении пользователя к элементу управления с помощью мыши или клавиатуры генерируются события (рассматриваются в разд. 9.3) и обрабатываются специальными "обработчиками событий". Как правило, резуль- татом события становится какая-либо операция. Например, нажатие кнопки ОК в окне MessageBox генерирует событие. В ответ на это событие обработчик MessageBox закрывает это окно сообщений. Каждый представляемый в данной главе класс .NET Framework (т. е. форма, компонент и элемент управления) принадлежит пространству имен System.Windows.Forms. Класс Form— основное окно, используемое про- граммными приложениями в Windows, полностью квалифицирован как System.windows.Forms.Form. Точно так же класс Button фактически является System. Windows. Forms. But ton. Общий процесс проектирования приложений в Windows требует создания формы Windows с указанием ее свойств, с добавлением элементов управления со своими свойствами и реализацией обработчиков событий. В табл. 9.2 показаны общие свойства, методы и события класса Form. При еоздании элементов управления и обработчиков событий Visual Studio .NET генерирует большую часть кода, относящегося к GUI. Программисты могут использовать Visual Studio .NET для выполнения большей час- ти подобных задач графически, путем "перетягивания и сбрасывания" (drag and drop) компонентов на форму и задания свойств в окне Properties. В визуальном программировании интегрированная среда разработки (Inte- grated Development Environment, IDE), как правило, поддерживает код, относящийся к GUI, и программисту ос- тается только написать обработчики событий.
Концепции графического пользовательского интерфейса: часть 1 267 Таблица 9.2. Общие свойства, методы и события класса Form Свойства, методы и события класса Form Описание/Делегат и аргументы события Общие свойства AcceptButton Кнопка, которая будет считаться нажатой при нажатии клавиши <Enter> AutoScroll Появление полосы прокрутки при необходимости (если данные не умещаются на одном экране) CancelButton Кнопка, которая считается нажатой при нажатии клавиши <Escape> FormBorderStyle Граница формы (например, none, single, 3D, sizable) Font Гарнитура отображенного в форме текста, а также гарнитура шрифта по умолчанию до- бавляемых в форму элементов Text Текст в строке заголовка формы Общие методы Close Закрытие формы и освобождение всех ресурсов. Открыть закрытую форму заново нельзя Hide Сокрытие формы (ресурсы не освобождаются) Show Отображение скрытой формы Общие события (Делегат EventHandler, аргумент события EventArgs) Load Имеет место до показа формы Visual Studio .NET генерирует обработчик события по умолчанию, когда программист дважды щелкает кнопкой мыши на форме в режиме проек- тирования 9.3. Модель обработки событий Интерфейсы GUI — событийно-управляемые (т. е. генерируют события, когда пользователь программы взаи- модействует с GUI). Типичным взаимодействием является перемещение указателя мыши, щелчки ее кнопками, нажатия кнопок, ввод текста в поля, выбор элементов из меню и закрытие окон. Обработчики событий — это методы, обрабатывающие события и выполняющие операции. Например, рассмотрим форму, цвет которой из- меняется при нажатии находящейся в ней кнопки. При нажатии кнопка генерирует событие и передает его в обработчик, а код последнего изменяет цвет формы. Каждый элемент управления, способный генерировать события, имеет связанного с ним делегата, определяю- щего сигнатуру для обработчиков событий этого элемента управления. Вспомните из главы 7, что делегатами называются объекты, ссылающиеся на методы. Делегаты событий — многоадресные (класс MulticastDeiegate): в них содержатся списки ссылок на методы. Каждый метод должен иметь одну и ту же сигнатуру (т. е. один и тот же список параметров). В модели обработки событий делегаты выступают в роли посредников между объ- ектами, генерирующими события, и методами, их обрабатывающими (рис. 9.3). Рис. 9.3. Модель обработки событий с помощью делегатов Замечание по технологии программирования___________________________________________ Делегаты позволяют классам задавать методы, которые не будут ни именованы, ни реализованы до тех пор, по- ка не будет инстанциирован класс. Это чрезвычайно полезно при создании обработчиков событий. Например, создателю класса Form не нужно присваивать имя или определять метод, который будет обрабатывать событие
268 Глава 9 Click. С помощью делегатов класс может указать, когда будет вызван обработчик такого события. Программи- сты могут создавать собственные формы, после чего присвоить имя и определить обработчик данного события. До тех пор, пока метод зарегистрирован с надлежащим делегатом, он будет вызываться в нужное время. При возникновении события вызывается каждый метод, на который ссылается делегат. Каждый метод в делега- те должен иметь одну и ту же сигнатуру, потому что всем им передается одинаковая информация. 9.3.1. Основы обработки событий В большинстве случаев создавать собственные события не приходится. Вместо этого можно обрабатывать со- бытия, созданные элементами управления .NET, такими как кнопки и текстовые поля. Эти элементы управления уже имеют делегатов для каждого события, которое они могут вызвать. Программист создает обработчик собы- тий и регистрирует его с делегатом; Visual Studio .NET помогает решить эту задачу. В следующем примере соз- дается форма, при щелчке кнопкой мыши на которой отображается окно сообщения. Затем будет проанализи- рован код события, сгенерированный Visual Studio .NET. Сначала создадим новое Windows-приложение. Для регистрации и определения обработчика событий щелкните на кнопке Events (изображение желтой молнии) окне Properties формы (рис. 9.4). Данное окно позволяет про- граммисту осуществлять доступ, модифицировать и создавать обработчики событий для любого элемента управления. В столбце слева перечислены события, которые может сгенерировать объект. В столбце справа — зарегистрированные обработчики для соответствующего события; изначально данный список пустой. Кнопка раскрывающегося списка показывает, что для одного события можно зарегистрировать несколько обработчи- ков. В нижней части окна отображается краткое описание события. Список событий, поддерживаемых элементом управления Выделенное событие Описание события f. Seeing : Ocon vAen the control e dekeo. i Activated BKfaCobrOwiqad ausesValdationChanged , ChangeUICues_________ Значек переключения на список событий Ссылка на текущее событие (пустая) Рис. 9.4. Раздел событий в окне Properties В данном примере при щелчке кнопкой мыши форма выполнит определенную операцию. Дважды щелкните мышью на событии Click в окне Properties для создания пустого обработчика события в коде программы. private void Fo rm?Va.me_Click (object sender, System.EventArgs e) { } Это — метод, который будет вызван при щелчке на форме. В качестве реакции последней зададим отображение окна сообщения. Для этого вставим оператор MessageBox. Show ("Form was pressed"),* в обработчик событий для получения private void FonnWame_Click(object sender, System.EventArgs e), { MessageBox.Show("Form was pressed"); } Теперь программу можно скомпилировать и исполнить (листинг 9.1). Всякий раз при щелчке кнопкой мыши на форме (рис. 9.5, а) появляется окно сообщения (9.5, б). г- *•—~~ ..............— .... ,Л. .м — Листинг 9.1. Простой пример обработки события с помощью визуального программирования 1 // Листинг 9.1: SimpleEventExample.cs 2 // Использование Visual Studio .NET для создания обработчика событий 3
Концепции графического пользовательского интерфейса- часть 1 269 4 using System; 5 using System.Drawing 6 using System. Collections; 7 using System.ComponentModel; 8 using System.Windows.Forms; 9 using System.Data; 10 11 // программа, демонстрирующая простой обработчик событий 12 public class MyForm : System.Windows.Forms.Form 13 { 14 private System.ComponentModel.Container components = null; 15 16 // код, сгенерированный Visual Studio .NET 17 18 [STAThread] 19 static void Main() 20 { 21 Application.Run(new MyForm()); 22 } 23 24 // Visual Studio .NET создает пустой обработчик, пишется 25 // определение: показать окно сообщения при щелчке на форме 26 private void MyForm_Click(object^ sender, System.EventArgs e) 27 { 28 MessageBox.Show("Form was pressed") 29 } 30 31 } // конец класса MyForm Рис. 9.5. Демонстрация обработки события: а — форма во время выполнения программы; б — сообщение после щелчка мышью по форме Рассмотрим программу подробно. Прежде всего, создается обработчик события (строки 26—29). Каждый обра- ботчик событий должен иметь сигнатуру, задаваемую соответствующим делегатдм события. В обработчики событий передаются две ссылки на объекты. Первая — ссылка на объект, вызвавший событие (sender), а вто- рая — ссылка на объект аргументов события (е). Аргумент е принадлежит к типу EventArgs. Класс EventArgs — базовый класс для объектов, содержащих информацию о событии. Для создания обработчика событий необходимо найти сигнатуру делегата. При двойном щелчке кнопкой мыши на имени события в окне Properties Visual Studio .NET создает метод с надлежащей сигнатурой. Правило при- своения имен — шяЭлементаУправления_ИмяСо&л'ия-, в данном случае обработчик события имеет имя MyForm ciick. Если не пользоваться окном Properties, то следует просмотреть класс аргументов события. Обра- титесь к указателю документации "Class ИмяЭлементаУправления" (т. е. Form class) и щелкните на вкладке Events (рис. 9.6). Откроется список всех событий, которые может сгенерировать данный класс. Щелкните кноп- кой мыши на имени события для открытия его делегата, типа аргументов события и описания (рис. 9.7). Общий формат метода обработки события таков: void ИмяЭлементаУправления_ИмяСобытия(object Sender, EventArgs е) { код обработки события }
270 Глава 9 где имя обработчика события по умолчанию является именем элемента управления, за которым следует символ подчеркивания (_) и имя события. Обработчики событий имеют возвращающий тип void и принимают два ар- гумента: object (обычно sender) и экземпляр класса аргументов события. Различия между разными классами EventArgs рассматриваются в последующих разделах. Имя класса Список событий Рис. 9.6. Список событий Form Делегат события -i г- Имя события Аргумент события Рис. 9.7. Подробности события Click О хорошем стиле программирования____________________________________________________________ Для поддержания упорядоченности методов придерживайтесь правил присвоения имен обработчикам собы- тий — ИмяЭлементаУправления_ИмяСобьггия. При этом пользователю понятно, каким событием управляет ме- тод и для какого элемента управления. Visual Studio .NET использует это правило именования при создании об- работчиков событий из окна Properties После создания обработчика событий его необходимо зарегистрировать с объектом делегата, содержащим список обработчиков для вызова. В процесс регистрации обработчика события с объектом делегата входит до- бавление обработчика в список запуска делегата. Элементы управления имеют ссылку делегата для каждого из их событий; ссылка делегата цмеет то же имя, что и событие. Например, если обрабатывается событие EventName для объекта myControl, тогда ссылка делегата будет myControl.EventName. Visual Studio .NET регист- рирует события с помощью приведенного ниже кода из метода initializeComponent: this.Click += new System.EventHandler(this.MyFormClick); Левая часть — это ссылка делегата Му.Form.Click (this относится к объекту класса MyForm). Изначально ссылка делегата пуста: ей нужно присвоить ссылку на объект (правая часть). Для каждого обработчика событий необ- ходимо создать новый объект делегата. Создаем новый объект делегата написанием new System.EventHandler(ИмяМетода) что возвращает объект делегата, инициализированный с методом ИмяМетода. ИмяМетода — это имя обработчика событий, в данном случае — MyForm.MyForm Click. Операция += добавляет делегат EventHandler в список за-
Концепции графического пользовательского интерфейса: часть 1 271 пуска текущего делегата. Поскольку ссылка делегата изначально пуста, регистрация первого обработчика собы- тий создает объект делегата. Вообще, для регистрации обработчика событий следует написать ИмяОбгьекта.ИмяСобытия += new System.EventHandler (ОбработчикМоегоСобытия); С помощью подобных операторов можно добавить большее количество обработчиков событий. Многоадресная передача события — это возможность иметь несколько обработчиков для одного события. Каждый обработчик события вызывается при возникновении события, однако порядок, в котором вызываются обработчики, не оп- ределен. Для удаления метода из объекта делегата пользуйтесь операцией -=. Распространенная ошибка программирования_______________________________________________ Предположение, что несколько обработчиков событий, зарегистрированных для одного события, вызываются в определенном порядке, приводит к логическим ошибкам. Если порядок имеет значение, зарегистрируйте первый обработчик событий, чтобы он вызывал остальные по порядку с передачей управления и аргументов события Замечание по технологии программирования_______________________________________________ События для заранее подготовленных компонентов NET обычно поддерживаются последовательных схем при- своения имен. Если событие имеет имя EventName, тогда его делегат— Even tNameEvent Handle г, а класс аргу- ментов события — EventNameEventArgs. Однако события, использующие класс EventArgs, применяют делегат Even tHandle г. Примечание_____________________________________________________________________________ Информация, необходимая для регистрации события: класс EventArgs (параметр для обработчика событий) и делегат EventHandler (для регистрации обработчика событий). Этот код может создать Visual Studio NET, либо его может создать сам программист. Если код создается Visual Studio NET*, тогда программисту не нужно прохо- дить все шаги и полностью контролировать процесс. Для простых событий и их обработчиков часто проще пре- доставить генерирование кода Visual Studio .NET. Для более сложных решений может потребоваться регистра- ция собственных обработчиков событий В последующих разделах будет указываться класс EventArgs и деле- гат EventHandler для каждого описываемого события. Более подробную информацию о конкретных типах событий см. в документации по ссылке "Class ИмяКласса" в подкатегории Events. 9.4. Свойства элементов управления и их размещение В данном разделе представлен обзор свойств, присущих многим элементам управления. Последние являются производными от класса Control (пространство имен System.Windows.Forms). В табл. 9.3 содержится список общих свойств и методов для класса Control. Свойство Text задает текст, который имеется на элементе управ- ления; он может варьироваться, в зависимости от контекста. Например, текст формы windows — это строка за- головка, а текст кнопки появляется на ее лицевой стороне. Метод Focus переносит фокус на элемент управле- ния. Когда фокус сосредоточен на элементе управления, последний становится активным. При нажатии клави- ши <ТаЬ> свойство Tabindex определяет порядок, в котором элементам управления присваивается фокус. Свойство Tabindex задается автоматически Visual Studio .NET, но программист может его изменить. Это полез- но для разработчиков, которым приходится вводить информацию в разные местоположения: после ввода нуж- ной информации они быстро -выбирают следующий элемент управления нажатием клавиши <ТаЬ>. Свойство Enabled указывает, может ли быть использован элемент управления. Программы могут присваивать свойству Enabled значение false, когда какая-либо опция недоступна для пользователя. В большинстве случаев текст элемента управления будет отображаться серым, а не черным цветом, если элемент управления не активен. Элементы управления можно скрывать без необходимости их отключения присвоением свойству visible зна- чения false, либо вызовом метода Hide. Когда свойству элемента управления visible присвоено значение false, этот элемент управления существует, но не показан в форме. Таблица 9.3. Свойства и методы класса control Свойства и методы класса Control Описание Общие свойства BackColor Цвет фона элемента управления Backgroundimage Фоновое изображение элемента управления Enabled Показывает, активизирован элемент управления или нет (т е может ли пользователь взаи- модействовать с ним) Неактивный элемент управления будет отображен, но определенные его части будут выделены серым цветом
272 Глава 9 Таблица 9.3 (окончание) Свойства и методы класса control Описание Focused Показывает, имеет ли элемент управления фокус. (Элемент управления, так или иначе, используется) Font Используется для отображения текста элемента управления ForeColor Цвет переднего плана элемента управления. Обычно это цвет, используемый для отобра- жения свойства Text элемента управления Tabindex Порядок табуляции элемента управления При нажатии клавиши <ТаЬ> фокус перемещает- ся на элементы управления в возрастающем порядке табуляции Этот порядок задается программистом TabStop При значении true для выбора элемента управления разработчик может воспользоваться Text клавишей <ТаЬ> Текст, относящийся к элементу управления. Местоположение и внешний вид варьируются с типом элемента управления TextAlign Выравнивание текста элемента управления по одному из трех положений по горизонтали (по левому краю, по центру, по правому краю) и по одному из трех вертикальных положений (верху, посередине, низу) Visible Показывает, вйдим ли элемент управления на форме Общие методы Focus Передача фокуса элементу управления Hide Сокрытие элемента управления (подобно присвоению свойству visible значения false) Show Показ элемента управления (подобно присвоению свойству visible значения true) Visual Studio .NET дает программисту возможность привязывать и стыковывать элементы управления, что помогает определить способы их размещения в контейнере (например, в форме). При привязывании элементы управления располагаются на фиксированном расстоянии от боковых границ контейнера, даже если размер элемента управления изменен. Стыковка позволяет растягивать элементы управления вдоль боковых сторон контейнеров. Пользователь может захотеть, чтобы элемент управления появлялся в определенном месте (вверху, внизу, слева или справа) формы, даже при изменении ее размеров. Этого можно добиться привязыванием элемента управле- ния к нужной стороне (верхней, нижней, левой или правой). После этого элемент управления сохраняет фикси- рованное расстояние от стороны своего контейнера. В большинстве случаев "родительским" контейнером явля- ется форма; однако и другие элементы управления могут служить родительским контейнером. При изменении размеров родительского контейнера все элементы управления перемещаются. Непривязанные элементы управления перемещаются относительно их первоначального положения на форме, тогда как привя- занные элементы управления перемещаются так, что расстояние от каждой стороны контейнера, к которой они привязаны, не меняется. Например, на рис. 9.8 показано, что самая верхняя кнопка привязана к верхней и левой сторонам родительской формы. При изменении размеров формы привязанная кнопка не перемещается, остава- ясь на фиксированном расстоянии от верхней и левой сторон формы. Непривязанная кнопка изменяет свое ме- стоположение при изменении размеров формы. До изменения размера После изменения размера Постоянное расстояние до левого и верхнего края формы Рис. 9.8. Демонстрация привязывания
Концепции графического пользовательского интерфейса: часть 1 273 Создайте простое Windows-приложение, содержащее два элемента управления. Привяжите один к правой сто- роне настройкой свойства Anchor, как показано на рис. 9.9. Второй элемент управления оставьте непривязан- ным. Теперь измените размер формы перетягиванием правой стороны вправо. Обратите внимание, что оба эле- мента управления перемещаются. Привязанный перемещается так, что он всегда находится на одинаковом рас- стоянии от правой стороны. Непривязанный перемещается так, что он находится на одном месте формы, относительно каждой ее стороны. Этот элемент управления останется ближе к той стороне, к которой он был изначально ближе, но будет менять свое расположение при изменении пользователем размера окна приложения. Щелкните кнопку со стрелкой в поле свойства Anchor и отобразите список привязок Полоса пристыковки покажет сторону, к которой будет привязан элемент управления Рис. 9.9. Манипуляции свойством Anchor элемента управления Иногда программистам нужно, чтобы элемент управления растягивался по всей длине стороны формы даже при изменении ее размеров. Это удобно, когда необходимо, чтобы один элемент управления оставался превали- рующим на форме, например, строка состояния, которая может быть расположена в нижней части окна про- граммы. Стыковка позволяет сделать так, что элемент управления будет растягиваться по всей длине стороны родительского контейнера (верхней, нижней или боковым). При изменении размеров родительской формы, раз- меры стыкованного элемента управления также изменяются. На рис. 9.10 показано, что кнопка стыкована с верхней стороной формы (она расположена поперек верхней части). При изменении размеров формы размеры кнопки также изменяются: она всегда заполняет верхнюю часть формы. Опция стыковки Fill эффективно сты- кует элемент управления с любыми сторонами родительского контейнера, и он заполняет всю форму. Форма Windows имеет свойство DockPadding, задающее расстояние от стыкованных элементов управления до края формы. Значение по умолчанию — 0; при этом элементы управления прикрепляются к краю формы. Свойства расположения элементов управления в общем виде приведены в табл. 9.4. До изменения размера После изменения размера Рис. 9.10. Демонстрация стыковки Опции привязывания и стыковки относятся к родительскому контейнеру, который может быть, а может и не быть формой. (Другие родительские контейнеры будут рассмотрены далее.) Минимальный и максимальный размеры формы можно задать с помощью свойств MinimumSize и MaximumSize соответственно. Оба свойства используют структуру size, имеющую свойства Height и width, задающие размер формы. Эти свойства позво- ляют программисту проектировать разметку GUI для заданного диапазона размеров. Для задания форме фикси- рованного размера введите одинаковые минимальное и максимальное значения. 18 3ак. 3333
274 Глава 9 Таблица 9.4. Свойства расположения элементов управления класса control Общие свойства расположения Описание Anchor Сторона родительского контейнера, к которой привязывается элемент управления; значения можно комбинировать, например, Top, Left Dock Сторона родительского контейнера для стыковки элемента управления; значения нельзя комбинировать DockPadding (для контейнеров) Задает интервал стыковки элементов управления внутри контейнера. Значение по умолчанию — 0, поэтому элементы управления плотно примыкают к стороне контей- нера Location Местоположение верхнего левого угла элемента управления относительно контейне- ра Size Размер элемента управления. Принимает структуру size, имеющую свойства Height (высота) и width (ширина) Minimumsize, Maximumsize (для форм Windows) Минимальный и максимальный размеры формы Замечание о "впечатлениях и ощущениях"_________________________________________________ Сделайте так, чтобы размеры форм Windows можно было изменять: пользователям с ограниченным простран- ством экрана монитора будет проще одновременно пользоваться несколькими приложениями. Убедитесь, что разметка GUI согласована для всех допустимых размеров формы. 9.5. Метки, текстовые поля и кнопки Метки представляют текстовые инструкции или информацию о программе. Метки определяются классом Label, являющимся производным от класса Control. Label отображает текст, который пользователь не может редактировать. После создания меток программы очень редко меняют их содержание. В табл. 9.5 представлены общие свойства Label. Таблица 9.5. Свойства класса Label Общие свойства Label Описание, делегат и аргументы события Font Гарнитура шрифта текста Text Текст метки TextAlign Выравнивание текста на элементе управления. Одно из трех положений по горизонтали (left, center или right) и одно из трех положений по вертикали (top, middle или bottom) Текстовое поле (класс TextBox) — это область, в которую текст может вводиться пользователем с клавиатуры, либо просто отображаться. Запаролированное текстовое поле — это TextBox, в котором все вводимые пользо- вателем данные скрыты. По мере ввода все символы в таком текстовом поле отображаются в виде особых сим- волов (обычно *). Изменением свойства Passwordchar текстового поля последнему задается пароль; при этом указывается символ, который будет отображать содержимое. Удаление значения Passwordchar из окна Properties возвращает текстовое поле в обычный режим. В табл. 9.6 показаны общие свойства и события тек- стовых полей. Таблица 9.6. Свойства и события класса TextBox Свойства и события класса TextBox Описание, делегат и аргументы события Общие свойства Accept sReturn При значении true и нажатии клавиши <Enter> создается новая строка, если текстовое поле занимает много строк. При значении false и нажатии клавиши <Enter> нажимается кнопка формы по умолчанию Multiline При значении true текстовое поле может отображать много строк. По умолчанию — false
Концепции графического пользовательского интерфейса: часть 1 275 Таблица 9.6 (окончание) Свойства и события класса TextBox Описание, делегат и аргументы события PasswordChar Введенный текст отображается в виде символов, что делает TextBox текстовым полем с паролем доступа. Если символ не указан, тогда TextBox отображает введенный текст Readonly Если true, то текстовое поле имеет серый фон, и текст редактировать нельзя. По умолча- нию — false ScrollBars Для многострочных текстовых полей; указывает на появление полосы прокрутки (попе, horizontal, vertical или both) Text Текст, отображаемый в текстовом поле Общие события (Делегат EventHandler, аргументы события EventArgs) TextChanged Вызывается при изменении текста в TextBox (пользователь добавил или удалил симво- лы). Создается по умолчанию при двойном щелчке на данном элементе управления в ре- жиме проектирования Кнопка — это элемент управления, на котором пользователь щелкает кнопкой мыши для запуска выполнения той или иной операции. В программе могут использоваться несколько типов кнопок, таких как кнопки позиций списка (т. е. флажки) и селективные кнопки (т. е. переключатели). Все типы кнопок являются производными от базового класса ButtonBaSe (пространство имен System.Windows.Forms), определяющего общие особенности кнопок. В данном разделе особое внимание будет уделено классу Button, который часто используется для пода- чи команды. Другие типы кнопок описываются в последующих разделах. Текст на лицевой поверхности Button называется меткой кнопки. В табл. 9.7 показаны общие свойства и события Button. Таблица 9.7. Свойства и события класса Button Свойства и события класса Button Описание, делегат и аргументы события Общие свойства Text Текст, отображаемый на лицевой поверхности Button Общие события (Делегат EventHandler, аргументы события EventArgs) Click Вызывается при щелчке кнопкой мыши на элементе управления. Создается по умолчанию при двойном щелчке на данном элементе управления в режиме проектирования Замечание о "впечатлениях и ощущениях*_____________________________________________________ Несмотря на то, что метки, текстовые поля и другие элементы управления могут реагировать на щелчки кнопка- ми мыши, это, прежде всего, относится к классу Button. Для запуска выполнения пользовательских операций пользуйтесь элементом управления Button (например, кнопкой ОК) вместо элементов управления других типов. В листинге 9.2 используются классы TextBox, Button и Label. Пользователь вводит текст в запаролированное текстовое поле и нажимает кнопку. После этого текст появляется в метке. На самом деле, текст отображаться не будет: целью запаролированных текстовых полей является сокрытие вводимого текста от любого, кто может заглядывать пользователю через плечо. А ~ .5; : .: "" ’ * .... .... ...............................................тим» — -мм.. Листинг 9.2. Программа, отображающая скрытый текст в запаролированном текстовом поле 1 // Листинг 9.2: LabelTextBoxButtonTest.cs 2 // Использование TextBox, Label и Button для отображения 3 // скрытого текста в текстовом поле с паролем 4 5 6 using System; using System.Drawing; 7 using System.Collections; 8 using System.ComponentModel; 9 using System.Windows.Forms; 10 using System.Data; 11
276 Гпава 9 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 • 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 // пространство имен содержит форму для отображения скрытого текста namespace LabelTextBoxButtonTest { /// <summary> /// форма, создающая текстовое поле с паролем и /// ярлык для отображения содержимого текстового поля /// </summary> public class LabelTextBoxButtonTest : System. Windows. Forms. Form { private System.Windows.Forms.Button displayPasswordButton; private System.Windows.Forms.Label displayPasswordLabel; private System.Windows.Forms.TexBox inputPasswordtextBox; /// <summary> III Требуется переменная проектировщика. Ill </summary> private System.ComponentModel.Container components = null; // конструктор по умолчанию public LabelTextBoxButtonTest() { InitializeComponent(); } /// <summary> III Освобождение всех используемых ресурсов. /// </summary> protected override void Dispose (bool disposing) { if (disposing) { if (components ! = null) { components.Dispose(); } ) base.Dispose(disposing); } tregion Windows Form Designer generated code /// <summary> /// Требуется метод для поддержки Designer; не изменять /// содержимого этого метода в редакторе кода. /// </summary> private void InitializeComponent() { this.displayPasswordButton = new System .Windows. Forms. Button () ; this. inputPasswordTextBox •* new System.Windows.Forms.textBox(); this.displayPasswordLabel « new System.Windows.Forms.Label(); this.SuspendLayout(); 11 II отображение PasswordButton // this.displayPasswordButton.Location = new System.Drawing.Pont(96, 96); this.displayPasswordButton.Name = "displayPasswordButton";
Концепции графического пользовательского интерфейса: часть 1 277 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 this.displayPasswordButton.Tabindex = 1; this.displayPasswordButton.Text = "Show Me"; this.displayPasswordButton.Click += new System.EventHandler( this.displayPasswordButton_Click); // 11 ввод PasswordTextBox // this.inputPasswordTextBox.Location = new System.Drawing.Point(16, 16); this.inputPasswordTextBox.Name = "inputPasswordTextBox"; this.inputPasswordTextBox.PasswordChar = " * "; this.inputPasswordTextBox.Size = new System.Drawing.Size (264, 20); this.inputPasswordTextBox.Tabindex = 0; this.inputPasswordTextBox.Text = // // отображение PasswordLabel // this.displayPasswordLabel.Borderstyle = System.Windows.Forms.Borderstyle.Fixed3D; this.displayPasswordLabel.Location • new System.Drawing.Point(16, 48); this.displayPasswordLabel.Name = "displayPasswordLabel"; this.displayPasswordLabel.Size = new System.Drawing.Size(264, 23); this.displayPasswordLabel.Tabindex = 2; // // LabelTextBoxButtonTest // this. AutoScaleBaseSize = new System.Drawing.Size(5, 13); this.ClientSize = new System.Drawing.Size (292, 133); this.Controls.AddRange( new System.Windows.Forms.Control[] { this.displayPasswordLabel, this.inputPasswordTextBox, this.displayPasswordButton }); this.Name = "LabelTextBoxButtonTest"; this.Text = "LabelTextBoxButtonTest"; this.ResumeLayout(false); } // конец метода InitializeComponent // конец разборной области, начинающейся со строки 53 ttendregion /// <summary> /// Основная точка входа для приложения. /// </summary> [STAThread] static void Main() { Application.Run(new LabelTextBoxButtonTest());
278 Глава 9 137 // отображение пользовательских входных данных в метке 138 protected void displayPasswordButton_Click( 139 object sender. System.EventArgs e) 140 { 141 // текст не изменен 142 displayPasswordLabel.Text = 143 inputPasswordTextBox.Text; 144 } 145 146 } // конец класса LabelTextBoxButtonTest 147 148 } // конец пространства имен LabelTextBoxButtonTest Результат работы программы представлен на рис. 9.11. Рис. 9.11. Демонстрация скрытого текста в запаролированном текстовом поле: а — ввод пароля; б — нажатие кнопки Show Me и отображение введенного текста Сначала создается GUI перетягиванием элементов управления (Button, Label и TextBox) на форму. После их расположения в окне Properties изменим их имена (заданием свойства Name) со значений по умолчанию — textboxl, labell, buttonl — на более описательные: displayPasswordLabel, inputPasswordTextBox и displayPasswordButton. Visual Studio .NET создает код и размещает его в методе initializeComponent. Теперь, когда читатель уже имеет представление об объектно-ориентированном программировании, можно сказать, что свойство (Name) — не совсем свойство, а, скорее, средство изменения имени переменной ссылки на объект. Для удобства это значение можно изменить в окне Properties Visual Studio .NET. Однако свойство не может мани- пулировать этим значением. Затем зададим свойству Text элемента displayPasswordLabel значение Show Me и очистим свойства Text эле- ментов displayPasswordLabel и inputPasswordTextBox,' чтобы при исполнении программы они были изначально пустыми. Свойство Borderstyle (стиль рамки) элемента displayPasswordLabel установлено на Fixed3D для трехмерного представления метки. Обратите внимание, что свойство Borderstyle текстовых полей установлено на Fixed3D по умолчанию. Задается символ пародирования присвоением свойству Passwordchar символа *. Это свойство может принять только один символ. Рассмотрим с помощью щелчка правой кнопки мыши на проекте и выбором команды View Code код, генери- руемый Visual Studio .NET. Это важно, потому что в окне Properties изменять можно не все значения. В предыдущих главах отмечалось, что Visual Studio .NET добавляет комментарии к коду. Они появляются на всем его протяжении (например, в строках 15—18). В дальнейших примерах некоторые из сгенерированных комментариев будут удаляться, чтобы программы были более компактными и удобочитаемыми (если они не демонстрируют еще неописанных функций). Visual Studio .NET вставляв/ объявления для элементов управления, добавляемых в форму (строки 22—24), а именно: Label, TextBox и Button. IDE обрабатывает эти объявления, упрощая добавление и удаление элемен- тов управления. В строке 29 объявляется ссылка components— массив, в котором содержатся добавляемые компоненты. В данной программе никаких компонентов не используется (только элементы управления), поэто- му данная ссылка равна null. Для формы создается конструктор; он вызывает метод InitializeComponent. Метод InitializeComponent соз- дает в форме компоненты и элементы управления и задает их свойства. Обычные комментарии "что делать" от Visual Studio .NET удаляются, потому что кода для добавления в конструктор больше нет. Когда они присутст- вовали, то появлялись в качестве напоминания (инструкции) в окне Task List. Метод Dispose освобождает рас- пределенные ресурсы, но в данных программах не вызывается явно. В строках 53—126 содержится так называемая сворачиваемая область, охватывающая метод InitializeComponent. Следует помнить, что директивы препроцессора #region и ttendregion позволяют программисту сворачивать в Visual Studio .NET код в одну строку. Это дает возможность сосредоточиться на других частях программы.
Концепции графического пользовательского интерфейса: часть 1 279 Методом InitializeComponent (строки 58—123) задаются свойства добавленных в форму элементов управле- ния (TextBox, Label и Button). В строках 60—66 создаются новые объекты для этих элементов управления. В строках 86—88’ и 92 задаются свойства Name, PasswordChar и Text для inputPasswordTextBox. Свойство Tabindex изначально задается Visual Studio .NET, но может быть изменено разработчиком. Комментарий в строках 54—57 советует не модифицировать содержимое метода InitializeComponent. В книге авторы немного изменили его в целях форматирования, хотя это и не рекомендуется. Сделано это только для того, чтобы читатели могли увидеть важные части кода. Технология Visual Studio .NET изучает метод для соз- дания вида кода в процессе проектирования. Если этот метод изменить, то Visual Studio .NET может не распо- знать модификаций и некорректно отобразить проект. Важно отметить, что проектный вид основан на коде, а не наоборот. Совет по тестированию и отладке__________________________________________________________ Для поддержания точности проектного вида не изменяйте код в методе InitializeComponent. Вносите измене- ния в режиме проектирования или в окне свойств. При щелчке кнопкой мыши на элементе управления генерируется событие click. С помощью процедуры, описанной в разд. 9.3.1, создается обработчик. Нужно, чтобы на событие Click отреагировал displayPasswordButton, поэтому дважды щелкаем в окне Events (в качестве альтернативы можно просто щелк- нуть кнопкой мыши на displayPasswordButton). При этом создается обработчик событий с именем displayPasswordButton_click (строка 138). Visual Studio .NET регистрирует обработчик событий (строки 77— 79). Visual Studio .NET добавляет этот обработчик в событие Click с помощью делегата EventHandier. После этого необходимо реализовать обработчик событий. Всякий раз при щелчке на displayPasswordButton этот ме- тод вызывается и отображает текст inputPasswordTextBox в displayPasswordLabel. Несмотря на то, ЧТО inputPasswordTextBox отображает все звездочками, он сохраняет текст ввода в свойстве Text. Для показа текста зададим свойству Text элемента displayPasswordLabel значение свойства Text элемента inputPasswordTextBox (строки 142 и 143). Пользователь должен запрограммировать эту строку вручную. При щелчке кнопкой мыши на displayPasswordButton запускается событие Click и исполняется обработчик событий displayPasswordButton_Click (с обновлением displayPasswordLabel). В данной программе большая часть кода была сгенерирована Visual Studio .NET. Это упрощает решение таких задач, как создание элементов управления, задание их свойств и регистрация обработчиков событий. Однако необходимо четко понимать этот механизм, в определенных программах может сложиться так, что свойства придется задавать самому разработчику с помощью кода. 9.6. Группы и панели Группы (элемент управления GroupBox) и панели (элемент управления Panel) объединяют элементы управления в GUI. Например, кнопки, выполняющие определенные операции, можно разместить в рамках GroupBox или Panel в режиме проектирования форм Visual Studio .NET. При перемещении GroupBox или Panel также переме- щаются и все входящие в них кнопки. Основным отличием между этими двумя классами является то, что GroupBox могут отображать заголовки, а Panel могут иметь полосы прокрутки. Последние дают пользователю возможность просмотра дополнительных элементов управления в Panel путем прокрутки видимой области. GroupBox по умолчанию имеют тонкие грани- цы, тогда как границы Panel можно устанавливать по желанию изменением значений свойства Borderstyle. Замечание о “впечатлениях и ощущениях"___________________________________________________ Panel и GroupBox могут содержать в себе другие панели и группы. Замечание о “впечатлениях и ощущениях"___________________________________________________ Компоновку GUI рекомендуется осуществлять с помощью привязывания и стыковки элементов управления (вы- полняющих функции схожего направления) внутри GroupBox или Panel. После этого GroupBox или Panel можно привязать или стыковать в рамках формы. При этом элементы управления делятся на функциональные "груп- пы", работа с которыми заметно облегчается. Для создания группы перетяните ее элемент управления GroupBox из панели Toolbox и поместите в форму. Соз- дайте новые элементы управления и разместите их внутри GroupBox, чтобы они стали частью этого класса. Эти элементы управления добавляются в свойство Controls класса GroupBox. Свойство Text класса GroupBox опре- деляет его заголовок. Далее представлены перечни общих свойств классов GroupBox (табл. 9.8) и Panel (табл. 9.9).
280 Глава 9 Таблица 9.8. Свойства класса GroupBox Свойства класса GroupBox Описание Controls Элементы управления, содержащиеся в GroupBox Text Текст, отображенный в верхней части класса GroupBox (его заголовок) Таблица 9.9. Свойства класса Panel Свойства класса Panel . — .. I . Описание AutoScroll Появление полос прокрутки, когда область панели слишком мала для отображения сразу всех элементов управления. По умолчанию — false Borderstyle Граница Panel (по умолчанию — None; другие опции: Fixed3D, FixedSingle) Controls Элементы управления, содержащиеся в Panel Для создания панели перетяните ее элемент управления Panel на форму и добавьте другие элементы управле- ния. Для активизации полос прокрутки свойству AutoScroll следует присвоить значение true. Если размер па- нели изменен, и окно не может отображать все элементы управления, то появляются полосы прокрутки (рис. 9.12). Ими можно пользоваться для просмотра элементов управления в Panel (как при исполнении, так и при проектировании формы). Это дает программисту возможность видеть GUI так, как его будет видеть клиент. Полосы прокрутки Рис. 9.12. Создание панели с полосами прокрутки Замечание о "впечатлениях и ощущениях"______________________________________________________ Пользуйтесь панелями с полосами прокрутки во избежание "загромождения" GUI, а также для уменьшения раз- меров последнего. В программе из листинга 9.3 используются классы GroupBox и Panel для расположения кнопок Эти кнопки из- меняют текст в метке. Группа (с именем mainGroupBox) имеет две кнопки — hiButton (с названием Hi) и byeButton (с названием Вуе). Панель (с именем mainPanei) также имеет две кнопки — leftButton (Far Left) и rightButton (Far Right). Эле- мент управления mainPanei также имеет свойство AutoScroll, которому присвоено значение true, обеспечи- вающее, при необходимости, появление полос прокрутки (т. е. если содержимое панели занимает больше места, чем сама панель). Метка (с именем messageLabel) изначально пуста. Обработчики событий для четырех кнопок расположейы в строках 36—61. Для создания пустого обработчика события click дважды щелкните на кнопке в режиме проектирования (вместо использования окна Events). Для изменения текста messageLabel в каждый обработчик добавляется строка. ............ Листинг 9.3. Использование классов GroupBox и Panel для расположения кнопок . /< '.".'2k7' 1 // Листинг 9.3: GroupBoxPanelExample.es 2 // Использование групповых блоков и панелей для размещения кнопок 3
Концепции графического пользовательского интерфейса: часть 1 231 4 5 б 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 using System; using System.Drawing; using System.Collections; using System.ComponentModel; using System.Windows.Forms; using System.Data; /// форма для отображения группового блока относительно панели public class GroupBoxPanelExample : System.Windows.Forms.Form { private System.Windows.Forms.Button hiButton; private System.Windows.Forms.Button byeButton; private System.Windows.Forms.Button leftButton; private System.Windows.Forms.Button rightButton; private Systern.Windows.Forms.GroupBox mainGroupBox; private System.Windows.Forms.Label messageLabel; private System.Windows.Forms.Panel mainPanel; private System.ComponentModel.Container components = null; // метод Dispose, сгенерированный Visual Studio .NET [STAThread] static void Main() { Application.Run(new GroupBoxPanelExample()); 1 // обработчики событий для изменения messageLabel // обработчик событий для кнопки Hi private void hiButton_Click( object sender, System.EventArgs e) ( messageLabel.text= "Hi pressed"; 1 // обработчик событий для кнопки Bye private void byeButton_Click( object sender, System.EventArgs e) < messageLabel.text= "Bye pressed"; 1 // обработчик событий для крайней левой кнопки private void leftButton_Click( object sender, System.EventArgs e) { messageLabel.text= "Far left pressed"; 1 // обработчик событий для крайней правой кнопки private void rightButton_Click( object sender, System.EventArgs e) { messageLabel.text= "Far right pressed"; 1 } // конец класса GroupBoxPanelExample Результат работы программы представлен на рис. 9.13
282 Гпава 9 Рис. 9.13. Демонстрация панели и группы: а — нажата кнопка Hi; б — нажата кнопка Far Left; в — нажата кнопка Far Right 9.7. Флажки и переключатели В C# имеются два типа кнопок состояния — флажки CheckBox и переключатели RadioButton, которые могут пребывать в состоянии on/off или true/false. Классы CheckBox и RadioButton являются производными от клас- са ButtonBase. RadioButton отличается от CheckBox тем, что, как правило, одновременно группируются не- сколько переключателей, и только один переключатель в группе может быть выбранным (true). Флажок — это маленький белый квадрат, который может быть либо пустым, либо отмеченным "галочкой" при выборе. На использование флажков никаких ограничений не налагается: за один раз их можно выбрать сколько угодно. Текст, появляющийся рядом с флажком, называется меткой флажка. Список общих свойств и событий класса CheckBox показан в табл. 9.10. Таблица 9.10. Свойства и события класса CheckBox Свойства и события класса CheckBox Описание, делегат и аргументы события Общие свойства Checked Показывает, отмечен ли элемент управления CheckBox Checkstate Показывает, отмечен ли элемент управления CheckBox (имеет "галочку") или не отмечен Перечисление (т. е. серия связных констант) со значениями Checked, Unchecked или Indeterminate Text Текст справа флажка (называется меткой) Общие события (Делегат EventHandler, аргументы события EventArgs) CheckedChanged Вызывается всякий раз, когда флажок отмечается или отметка отменяется. Создается по умолчанию при двойном щелчке на данном элементе управления в режиме проектирования CheckStateChanged Вызывается при изменении свойства Checkstate Программа, представленная в листинге 9.4, дает пользователю возможность отметить флажок для изменения стиля шрифта для метки. Один флажок делает шрифт полужирным, а другой — курсивным. При установке обо- их переключателей стиль шрифта меняется на полужирный курсив. При начальном исполнении программы ни один элемент управления CheckBox не отмечен. Свойству Text первого флажка с именем boldCheckBox присвоен стиль Bold (полужирный). Другой флажок с именем italicCheckBox имеет метку italic (курсив). Метка с именем outputLabel отображает строку "Watch the font style change" ("Наблюдение за изменением стиля шрифта"). 1 // Листинг 9.4: CheckBoxTest.cs 2 // Использование флажков для переключения стиля (полужирный/курсив) 3
Концепции графического пользовательского интерфейса: часть 1 283 4 using System; 5 using System.Drawing; 6 using Systern.Collections; 7 using System.ComponentModel; 8 using System. Windows.Forms; 9 using System.Data; 10 11 III форма содержит кнопки позиций списков, позволяющие 12 III пользователю изменять текст-пример 13 public class CheckBoxTest : System.Windows.Forms.Form 14 ( 15 private System.Windows.Forms.CheckBox boldCheckBox; 16 private System.Windows.Forms.CheckBox italicCheckBox; 17 18 private System.Windows.Forms.Label outputLabel; 19 20 private System.ComponentModel.Container components = null; 21 22 // метод Dispose, сгенерированный Visual Studio .NET 23 24 // основная точка входа для приложения 25 [STAThread] 26 static void Main() 27 { 28 Application.Run(new CheckBoxTest()); 29 } 30 31 // сделать текст полужирным, если не полужирный; 32 // если полужирный — не изменять 33 private void boldCheckBox_CheckedChanged( 34 object sender, System.EventArgs e) 35 { 36 outputLabel.Font = 37 new Font(outputLabel.Font.Name, 38 outputLabel.Font.Size, 39 outputLabel.Font.Style Л FontStyle.Bold); 40 } 41 42 // сделать текст курсивом, если не курсив; 43 // если курсив — не изменять 44 private void rtalicCheckBox_CheckedChanged( 45 object sender, System.EventArgs e) 46 { 47 outputLabel.Font = 48 new Font(outputLabel.Font.Name, 4 9 outputLabel.Font.Size, 50 outputLabel.Font.Style Л Fontstyle.Italic); 51 } 52 53 } // конец класса CheckBoxTest После размещения в форме элементов управления определим их обработчики событий. После двойного щелчка на boldCheckBox создается и регистрируется пустой обработчик события CheckedChanged. Для понимания кода, добавленного в обработчик события, сначала рассмотрим свойство Font элемента outputLabel. Для изменения гарнитуры шрифта свойство Font должно быть задано объекту Font. Используемый конструктор Font принимает название гарнитуры, кегль (размер) и стиль. Первые два аргумента используют объект Font элемента outputLabel, а именно: outputLabel. Font .Name и outputLabel. Font .Size (строки 37 и 38). Стиль при- надлежит к перечислению класса Fontstyle, содержащего стили Regular (обычный), Bold (полужирный), Italic (курсив), Strikeout (зачеркнутый) и Underline (подчеркнутый). Свойство style объекта Font задается при создании объекта Font; само по себе свойство style предназначено только для чтения. Стили можно комбинировать с помощью побитовых операций, выполняющих преобразования по битам. В дан- ной программе необходимо задать такой стиль гарнитуры, чтобы текст стал полужирным, если ранее он тако-
284 Глава 9 вым не был, и наоборот. Обратите внимание, что в строке 39 для этого используется побитовая операция (Л). При применении этой операции к двум битам получится следующее: если точно один из соответствующих би- тов равен 1, то результату нужно присвоить значение 1. С помощью операции Л (строка 39) значения битов для полужирного стиля задаются точно так же. Значения битов операнда в правой части (Fontstyle.Boid) всегда заданы на полужирный стиль. Тогда операнд в левой части (outputLabel. Font. style) не должен быть полужир- ным для того, чтобы окончательный стиль стал полужирным. (Вспомните для xor: если одно значение— 1, то другое должно быть 0, иначе результат не будет 1.) Если outputLabel. Font. style — полужирный, тогда окон- чательный стиль полужирным не будет. Данная операция также позволяет комбинировать стили. К примеру, если текст изначально был курсивным, то теперь он будет полужирным курсивом, а не просто полужирным. Можно было явно протестировать текущий стиль и изменить его, в соответствии с тем, что необходимо. На- пример, в методе boldCheckBox CheckChanged можно было протестировать обычный стиль, сделать его полу- жирным, протестировать "полужирность", вернуть обычный стиль, протестировать на предмет курсива, сделать полужирный курсив и просто курсив. Однако такой способ имеет один недостаток: для каждого вновь добав- ляемого стиля удваивается количество комбинаций. Для добавления флажка подчеркивания пришлось бы про- тестировать восемь возможных стилей. Точно так же при добавлении флажка перечеркивания текста в каждом обработчике событий пришлось бы тестировать 16 комбинаций. С помощью операции исключающего "ИЛИ" (Л) такого нагромождения можно избежать. Каждому новому стилю нужен только один оператор в его обработ- чике события. Помимо этого, стили можно легко убирать удалением их обработчиков. Если бы проверялось каждое условие, то пришлось бы удалять обработчик события и все ненужные условия тестирования в других обработчиках. Результат работы программы представлен на рис. 9.14. а Рис. 9.14. Демонстрация флажков: а — исходное состояние формы; б— первый флажок отмечен; в — отмечены оба флажка Переключатели (определены в классе RadioButton) похожи на флажки (класс CheckBox), потому что они также имеют два состояния: отмеченное и не отмеченное (не выбранное). Однако переключатели, как правило, объ- единяются в группу, в которой за один раз можно отметить только один переключатель. Выбор другого пере- ключателя в группе отменяет выбор всех остальных. Переключатели представляют собой набор взаимоисключающих опций (т. е. набор, в котором нельзя выбрать несколько опций одновременно). Замечание о "впечатлениях и ощущениях"_________________________________________________ Пользуйтесь переключателями (элемент управления RadioButton), когда необходимо выбрать только одну оп- цию в группе. Замечание о “впечатлениях и ощущениях"______________________________________________ Пользуйтесь флажками (элемент управления CheckBox), когда необходимо выбрать несколько опций в группе. Все добавляемые в форму переключатели становятся частью одной группы. Для создания новых групп пере- ключателей следует добавлять в элемент управления GroupBox или Panel. Общие свойства и события класса RadioButton представлены в табл. 9.11. Замечание по технологии программирования_____________________________________________ Формы, группы и панели могут выполнять роль логических групп для переключателей. Последние в каждой груп- пе будут взаимоисключающими друг для друга, но не для переключателей, принадлежащих другим группам. Таблица 9.11. Свойства и события класса RadioButton Свойства и события класса RadioButton Описание, делегат и аргументы события Общие свойства Checked Показывает, отмечен ли переключатель Text Текст справа от переключателя (называется меткой)
Концепции графического пользовательского интерфейса: часть 1 285 Таблица 9.11 (окончание) Свойства и события класса RadioButton Описание, делегат и аргументы события Общие события (Делегат EventHandler, аргументы события EventArgs) Click Вызывается при щелчке пользователем кнопкой мыши на элементе управления CheckedChanged Вызывается всегда, когда переключатель отмечается или отметка отменяется. Создается по умолчанию при двойном щелчке на данном элементе управления режиме проектиро- вания В программе, представленной в листинге 9.5, используются переключатели для выбора опций окна MessageBox. Пользователи выбирают нужные им атрибуты, после чего нажимают кнопку отображения, а затем появляется окно MessageBox. Метка в левом нижнем углу показывает результат MessageBox (Yes, No, Cancel и т. д.). Значки для окна MessageBox и типы кнопок показаны в таблицах в главе 3. 1 // Листинг 9.5: RadioButtonTest.cs 2 // Использование переключателей для задания опций окна сообщения 3 4 using System; 5 using System.Drawing; 6 using System.Collections; 7 using System.ComponentModel; 8 using Systern.Windows.Forms; 9 using System.Data; 10 11 /// форма содержит несколько переключателей; пользователь 12 III выбирает один из каждой группы для создания окна сообщения 13 public class RadioButtonTest : System.Windows.Forms.Form 14 { 15 private System.Windows.Forms.Label promptLabel; 16 private System.Windows.Forms„Label displayLabel; 17 private Systern.Windows.Forms.Button displayButton; 18 19 private System.Windows.Forms.RadioButton questionButton; 20 private System.Windows.Forms.RadioButton informationButton; 21 private System.Windows.Forms.RadioButton exclamationButton; 22 private System.Windows.Forms.RadioButton errorButton; 23 private System.Windows.Forms.RadioButton retryCancelButton; 24 private System.Windows.Forms.RadioButton yesNoButton; 25 private System.Windows.Forms.RadioButton yesNoCancelButton; 26 private System.Windows.Forms.RadioButton okCancelButton; 27 private System.Windows.Forms.RadioButton okButton; 28 private System.Windows.Forms.RadioButton 29 abortRetrylgnoreButton; 30 31 private System.Windows.Forms.GroupBox groupBox2; 32 private System.Windows.Forms.GroupBox groupBoxl; 33 34 private MessageBoxIcon iconType = MessageBoxIcon.Error; 35 private MessageBoxButtons buttonType - 3 6 MessageBoxButtons.OK; 37 38 // основная точка входа для приложения 39 [STAThread] 40 static void Main() 41 { 42 Application.Run(new RadioButtonTest()); 43 } 44
286 Глава 9 45 46 47 48 49 50 51 52 53 54 55 56 57 58 // изменение кнопки, исходя из опции, выбранной пользователем private void buttonType_CheckedChanged( object sender, System.EventArgs e) { if (sender == okButton) // отображение кнопки OK buttonType = MessageBoxButtons.OK; // отображение кнопок OK и Cancel else if (sender == okCancelButton) buttonType = MessageBoxButtons.OkCancel; // отображение кнопок Abort, Retry и Ignore else if (sender «= abortRetrylgnoreButton) buttonType = MessageBoxButtons.AbortRetrylgnore; 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 // отображение кнопок Yes, No и Cancel else if (sender — yesNoCancel Button) buttonType = MessageBoxButtons.YesNoCancel; II отображение кнопок Yes и No else if (sender == yesNoButton) buttonType = MessageBoxButtons.YesNo; // только одна опция слева отображает // кнопки Retry и Cancel else buttonType - MessageBoxButtons.RetryCancel; } II конец метода buttonType CheckedChanged // смена значка, исходя из выбранной опции private void iconType_CheckedChanged( object sender, System.EventArgs e) { if (sender = errorButton) II отображение значка ошибки iconType = MessageBoxIcon.Error; // отображение восклицательного знака else if (sender — exclamationButton) iconType = MessageBoxIcon.Exclamation; // отображение значка информации else if (sender — informationButton) iconType = MessageBoxIcon.Information; else // только одна опция слева отображает вопросительный знак iconType = MessageBoxIcon.Question; } // конец метода iconType_CheckedChanged // отображение MessageBox и кнопки, нажатой пользователем protected void displayButton_Click ( object sender, System.EventArgs e) { DialogResult result = MessageBox.Show("This is Your Custom MessageBox.", "Custom MessageBox", buttonType, iconType, 0, 0); // проверка результата диалогового окна и его отображение в метке switch (result) ( case DialogResult.OK: displayLabel.Text = "OK was pressed."; break;
Концепции графического пользовательского интерфейса: часть 1 287 110 case DialogResult.Cancel: 111 displayLabel.Text = "Cancel was pressed."; 112 break; 113 114 case DialogResult.Abort: 115 displayLabel.Text = "Abort was pressed."; 116 break; 117 118 case DialogResult.Retry: 119 displayLabel.Text = "Retry was pressed."; 120 break; 121 122 case DialogResult.Ignore: 123 displayLabel.Text = "Ignore was pressed."; 124 break; 125 126 case DialogResult.Yes: 127 displayLabel.Text = "Yes was pressed."; 128 break; 129 130 case DialogResult.No: 131 displayLabel.Text = "No was pressed."; 132 break; 133 134 } // конец switch 135 136 } // конец метода displayButton_Click 137 138 } // конец класса RadioButtonTest Для сохранения набора опций, выбранных пользователем, создаются и инициализируются объекты iconType и buttonType (строки 34—36). Объект iconType — это перечисление MessageBoxIcon, которое может иметь значе- ния Asterisk, Error, Exclamation, Hand, Information, Question, Stop и Warning. В данном примере используют- ся ТОЛЬКО Error, Exclamation, Information и Question. Объект buttonType— это перечисление MessageBoxButton со значениями AbortRetrylgnore, OK, OKCancel, RetryCancel, YesNo и YesNoCancel. Имя указывает на то, какие кнопки появятся в окне MessageBox. В данном примере используются все значения перечисления MessageBoxButton. Создаются два класса GroupBox, по одному для каждого перечисления. Они имеют заголовки Button Туре и Icon. Одна метка используется для подсказки пользователю (promptLabei), а другая — для отображения нажа- той кнопки после показа специального подготовленного MessageBox (displayLabei). Еше имеется кнопка Display (displayButton). Для опций перечисления создаются переключатели с надлежащим образом установ- ленными метками. Переключатели объединены в группы, следовательно, из каждой группы можно выбрать только одну опцию. Для обработки событий для всех переключателей в groupBoxl существует один обработчик событий, а для всех переключателей в дгоирвох2 — другой. При выборе каждого переключателя генерируется событие CheckedChanged. Помните, что для указания обработчика для события следует пользоваться разделом событий i окне Properties. Создайте новый обработчик события CheckedChanged для одного из переключателей в buttonTypeGroupBox и переименуйте его в buttonType_CheckedChanged. Затем задайте обработчики события CheckedChanged для всех переключателей в buttonTypeGroupBox и переименуйте его в метод buttonType_CheckedChanged. Создайте вто- рой обработчик события CheckedChanged для переключателя в iconTypeGroupBox и переименуйте его в iconType_CheckedChanged. И, наконец, задайте обработчик события CheckedChanged для переключателей в iconTypeGroupBox как метод iconType_CheckedChanged. Оба обработчика событий сравнивают объект sender с каждым переключателем для определения того, какой из них отмечен. В зависимости от выбранного переключателя изменяется либо iconType, либо buttonType (стро- ки 46—93). Обработчик Click для displayButton (строки 96—136) создает MessageBox (строки 99—101). Некоторые из опций MessageBox задаются iconType и buttonType. Результатом окна сообщения становится перечисление
288 Гпава 9 DialogResult СО значениями Abort, Cancel, Ignore, No, None, OK, Retry или Yes. Оператор switch в строках 104— 136 проверяет результат и соответствующим образом устанавливает displayLabel.Text. Результат работы программы представлен на рис. 9.15. г Тип значка Exclamation Тип кнопки OKCancel г Тип значка Information кнопки AbortRetrylgnore Р Тип значка Question [Custom MessageBox I tour Cistern Мамаево» ! '» 1 Тип кнопки YesNoCancel Тип кнопки YesNo Рис. 9.15. Демонстрация переключателей: а — форма; б — окно сообщения поля выбора переключателей OKCancel и Exclamation; в — окно сообщения поля выбора переключателей ОК и Error; г — окно сообщения поля выбора переключателей AbortRetrylgnore и Information; д — окно сообщения поля выбора переключателей YesNoCancel и Question; е — окно сообщения поля выбора переключателей YesNo и Error; ж— окно сообщения поля выбора переключателей Retrylgnore и Exclamation 9.8. Рамки изображений В рамках изображений (класс PictureBox) отображаются графические изображения. Изображение, заданное объектом класса image, может быть представлено в растровом формате (BMP), в форматах GIF, JPEG, в виде значка или метафайла. (Графические изображения и мультимедиа рассматриваются в главе 13.) Очень широко распространены графические форматы GIF (Graphics Interchange Format, формат графического обмена) и JPEG (Joint Photography Experts Group, объединенная группа экспертов в области фотографии). Свойство Image задает для использования объект image, а свойство SizeMode задает режим показа изображения (Normal, Stretchlmage, AutoSize или Centerimage). В табл. 9.12 описываются важные свойства и события класса PictureBox.
Концепции графического пользовательского интерфейса: часть 1 289 Таблица 9.12. Свойства и события класса PictureBox Свойства и события класса PictureBox Описание, делегат и аргументы события Общие свойства Image Изображение, показываемое в элементе управления PictureBox SizeMode Перечисление, управляющее размерами и местоположением изображения: Normal (по умолчанию), Stretchimage (растянутое изображение), AutoSize (автоматическая установка размеров) и Centerimage (изображение по центру) размещают изображение в верхнем ле- вом углу графического поля, a Center Image размещает изображение посередине Если изо- бражение слишком большое, то опции его урезают. Значение Srretchlmage меняет разме- ры изображения, чтобы оно уместилось в графическом поле AutoSize меняет размеры изображения для его удержания Общее событие (Делегат EventHandler, аргументы события EventArgs) Click Вызывается при щелчке пользователем кнопкой мыши на элементе управления. Создается по умолчанию при двойном щелчке на данном элементе управления в режиме проектирова- ния В программе, представленной в листинге 9.6, используется рамка изображения imagePictureBox для показа од- ного из растровых изображений— imageO.bmp, imagel.bmp или image2.bmp. Они есть в каталоге images (как обычно находящемся в каталоге bin/debug проекта), где размещен и исполняемый файл. Всякий раз при щелчке по imagePictureBox изображение меняется. Метка promptLabel в верхней части формы отображает текст "Click On Picture Box to View Images" ("Щелкните на графическом поле для просмотра изображений"). : Листинг 9.6. Использование PictureBox для показа изображении ________________________________________...л..;.....„....._....................................... ...................................; 1 // Листинг 9.6: PictureBoxTest.es 2 // Использование PictureBox для показа изображений 3 4 using System; 5 using System.Drawing; 6 using System.Collections; 7 using System.ComponentModel; 8 using System.Windows.Forms; 9 using System.Data; 10 using System.10; 11 12 /// форма для показа изображений при щелчке на них кнопкой мыши 13 public class PictureBoxTest : System.Windows.Forms.Form 14 { 15 private System.Windows.Forms.PictureBox imagePictureBox; 16 private System.Windows.Forms.Label promptLabel; 17 18 private int imageNum = -1; 19 20 /// главная точка входа для приложения 21 [STAThread] 22 static void Main() 23 { 24 Application.Run(new PictureBoxTest()); 25 } 26 27 // изменение изображения при любом щелчке на PictureBox 28 private void imagePictureBox_Click( 29 object sender, System.EventArgs e) 30 { 31 imageNum = (imageNum, +1) % 3; // imageNum от 0 до 2 32 19 Зак. 3333
290 Гпава 9 33 // создание объекта Image из файла, отображение в PictureBox 34 imagePictureBox.Image = Image.FromFile( 35 Directory. GetCurrent Directory () + \\imagesWimage + 36 xmageNum + ".bmp"); 37 ) 38 39 } // конец класса PictureBoxTest Для реагирования на щелчки мышью пользователя необходимо обрабатывать событие click (строки 28—37). Внутри обработчика событий используем целое число (imageNum) для сохранения изображения, которое нужно показать. Затем Image задается свойство image компонента imagePictureBox. Класс image рассматривается в главе 13, здесь же представлен общий обзор метода FromFile, принимающего строку (путь к файлу изображе- ния) и создающего объект image. Для нахождения изображений используем класс Directory (пространство имен System.io, указанное в стро- ке 10), метод getCurrentDirectory (строка 35). Он возвращает текущий каталог исполняемого файла (обычно это bin\Debug) в виде строки. Для доступа к подкаталогу images возьмем текущий каталог и добавим "Wimages", за которым следует \\ и имя файла. Две косые черты используются потому, что для печати одной косой черты необходима управляющая последовательность. Во избежание знака перехода можно было использовать символ @ (т. е. @"\" напечатает одну косую черту, по- тому что одну косую черту не обязательно переходить другой). Для добавления нужного числа используем imageNum, поэтому загрузить МОЖНО imageO, imagel или image2. Целое ЧИСЛО imageNum остается в диапазоне ОТ 0 до 2 из-за вычисления по модулю (строка 31). Наконец, к имени файла добавляем ".bmp". Таким образом, если нужно загрузить imageO, строка имеет следующий вид: CurrentDir\images\imageO.bmp, где CurrentDir— ката- лог исполняемого файла. Результат работы программы представлен на рис. 9.16. Рис. 9.16. Демонстрация рамок изображений: а — загружено изображение imageO.bmp; б — загружено изображение imagel .bmp; в — загружено изображение image2.bmp 9.9. Обработка событий мыши В данном разделе рассматривается обработка событий мыши: щелчков кнопок, нажатий с удержанием и пе- ремещений указателя. События мыши генерируются при взаимодействии манипулятора ’’мышь” с элементом управления. Их можно обрабатывать для любого элемента управления GUI, являющегося производным класса System.Windows.Forms.Control. Информация событий мыши передается с помощью класса MouseEventArgs, и делегата для создания обработчиков событий мыши — MouseEventHandler. Каждый метод обработки событий мыши должен принять в качестве аргументов object и MouseEventArgs. Событие Click, рассмотренное выше, использует делегат EventHandler и аргументы события EventArgs. Класс MouseEventArgs содержит информацию о событии мыши, например, координаты х и у указателя мыши, нажатие кнопок, количество щелчков и количество желобков, через которое повернулось колесо мыши. Обра- тите внимание, что координаты х и у объекта MouseEventArgs соотносятся с элементом управления, вызвавшим то или иное событие. Точка (0, 0) расположена в левом верхнем углу элемента управления. Различные события мыши и свойства класса MouseEventArgs представлены в табл. 9.13. В листинге 9.7 события мыши используются для рисования на форме. Где бы пользователь ни провел указате- лем мыши (т. е. переместил мышь с удержанием нажатой кнопки), на форме изображается черта.
Концепции графического пользовательского интерфейса: часть 1 291 Таблица 9.13. События мыши, делегаты и аргументы событий События мыши Описание, делегаты и аргументы событий События мыши (Делегат EventHandler, аргументы событий EventArgs) MouseEnter Имеет место, если указатель мыши входит в область действия элемента управления MouseLeave Имеет место, если указатель мыши выходит за пределы области действия элемента управления События мыши (Делегат MouseEventHandler, аргументы событий MouseEventArgs) MouseDown Имеет место, если кнопка мыши нажата, в то время как указатель находится в области действия элемента управления MouseHover Имеет место, если указатель мыши зависает над областью действия элемента управления MouseMove Имеет место, если указатель мыши двигается, находясь в области действия элемента управле- ния MouseUp Имеет место, если кнопка мыши отпускается, когда указатель находится над областью действия элемента управления Свойства класса MouseEventArgs Button Нажатая кнопка мыши (left (левая), right (правая), middle (средняя) или попе (отсутствует)) Clicks Количество нажатий кнопки мыши X Координата х события, относящегося к элементу управления Y Координата у события, относящегося к элементу управления 1 // Листинг 9.7: Painetr.cs 2 // Использование мыши для рисования на форме 3 4 using System; 5 using System.Drawing; 6 using System.Collections; 7 using System.ComponentModel; 8 using "System.Windows.Forms; 9 using System.Data; 10 11 III создание формы в качестве поверхности для рисования 12 public class Painter : System.Windows.Forms.Form 13 { 14 bool shouldPaint = false; // определение процесса рисования 15 16 III основная точка входа для приложения 17 [STAThread] 18 static void Main() 19 { 20 Application.Run(new Painter()); 21 ) 22 23 // рисование после нажатия кнопки мыши 24 private void Painter_MouseDown( 25 object sender, System.Windows.Forms.MouseEventArgs e) 26 { 27 shouldPaint = true; 28 } 29 30 // прекращение рисования после отпускания кнопки мыши 31 private void Painter_MouseUp ( 32 object sender, System.Windows.Forms.MouseEventArgs e)
292 Гпава 9 33 { 34 shouldPaint = false; 35 } 36 37 // рисование окружности при движении мыши 38 // при нажатой кнопке 39 protected void Painter_MouseMove( 40 object sender, System.Windows.Forms.MouseEventArgs e) 41 { 42 if (shouldPaint) 43 { 44 Graphics graphics = CreateGraphics(); 45 graphics.FillEllipse( 46 new SolidBrush(Color.BlueViolet), 47 e.X, e.Y, 4, 4); 48 ) 49 50 } // конец Painter_MouseMove 51 52 } // конец класса Painter В строке 14 программа создает переменную shouldPaint, которая определяет, следует выполнять рисование в форме или нет. Рисование должно осуществляться только при нажатой кнопке мыши. В обработчике события MouseDown переменной shouldPaint присвоено значение true (строка 27). Как только кнопка мыши отпускается, программа прекращает процесс рисования: переменной shouldPaint присваивается значение false в обработ- чике события Mouseup. При перемещении мыши с нажатой кнопкой всегда генерируется событие MouseMove. Это событие будет гене- рироваться многократно со скоростью, заданной операционной системой. В рамках обработчика события Painter_MouseMove (строки 39—48) программа выполняет рисование только в том случае, если shouldPaint равно true (т. е. кнопка мыши нажата). В строке 44 для формы создается объект Graphics, предоставляющий методы для рисования различных фигур. Метод FillEllipse (строки 45—47) рисует окружность в любой точке, в которую перемещается курсор (при нажатой кнопке). Первый параметр к методу FillEllipse — это объект SolidBrush, определяющий цвет нарисованной фигуры. Новый объект SolidBrush создается передачей в конст- руктор значения Color. Структура Color содержит многочисленные ранее определенные константы цвета: вы- брана была Color.BlueViolet (строка 46). SolidBrush заполняет область эллипса, расположенной внутри огра- ничивающего прямоугольника. Последний определяется координатами х и у левого верхнего угла, высоты и ширины. Эти четыре параметра являются последними четырьмя аргументами к методу FillEllipse-. Координа- ты х и у— это местоположение события мыши; их можно получить из аргументов событий мыши (е.хие.у). Для изображения окружности устанавливаются равные значения высоты и ширины ограничивающего прямо- угольника; в данном случае они равны 4 пикселам. Результат работы программы представлен на рис. 9.17. Рис. 9.17. Демонстрация событий мыши: а — вид формы после загрузки; б — нарисован "круг”; е — нарисована рожица 9.10. Обработка событий клавиатуры В данном разделе описывается обработка событий клавиатуры. События генерируются при нажатии и отпус- кании клавиш. Эти события могут обрабатываться любыми элементами управления, наследующими из System.Windows.Forms.Control. Существуют два типа событий клавиатуры. Первый— это событие Keypress, возникающее при нажатии клавиши, представляющей символ ASCII (определенный свойством Keychar класса
Концепции графического пользовательского интерфейса: часть 1 293 KeyPressEventArgs). Стандарт ASCII — это набор из 128 буквенно-цифровых символов. (Полный список пред- ставлен в приложении 6.) С помощью события KeyEvent нельзя определить, нажата ли модифицирующая клавиша (<Shift>, <Alt> и <Ctrl>). Для определения таких действий следует обработать события KeyUp и KeyDown, формирующие второй тип событий клавиатуры. Класс KeyEventArgs содержит информацию о специальных модифицирующих клави- шах. Значение перечисления Key клавиши может быть возвращено с предоставлением информации о широком спектре буквенных клавиш ASCII. Модифицирующие клавиши часто используются вместе с мышью для выбора или выделения информации. Делегаты для двух классов — KeyPressEventHandler (класс аргументов события KeyPressEventArgs) и KeyEventHandler (класс аргументов события KeyEventArgs). В табл. 9.14 представлена важная информация о событиях клавиатуры. Таблица 9.14. События клавиатуры, делегаты и аргументы событий События клавиатуры Описание, делегаты и аргументы событий События клавиатуры (Делегат KeyEventHandler, аргументы событий KeyEventArgs) KeyDown Имеет место при нажатии клавиши KeyUp Имеет место при отпускании клавиши События клавиш (Делегат KeyPressEventHandler, аргументы событий KeyPressEventArgs) KeyPress Имеет место при нажатии клавиши. Повторяется многократно при нажатой клавише со ско- ростью, определенной операционной системой Свойства класса KeyPressEventArgs - KeyChar Возвращает символ ASCII для нажатой клавиши Handled Обработано или нет событие KeyPress Свойства класса KeyEventArgs Alt Указывает на нажатие клавиши <Alt> Control Указывает на нажатие клавиши <Ctrl> Shift Указывает на нажатие клавиши <Shift> Handled Указывает, обработано событие или нет KeyCode Возвращает код клавиши как перечисление Keys. Информация модифицирующей клавиши не включается. Используется для проверки определенной клавиши KeyData Возвращает код клавиши как Перечисление Key вместе с информацией модифицирующей клавиши. Используется для определения всей информации о нажатой клавише KeyValue Возвращает код клавиши как int, а не в виде перечисления Keys. Используется для полу- чения числового представления нажатой клавиши Modifiers Возвращает перечисление Keys для любой нажатой модифицирующей клавиши (<Alt>, <Ctrl> и <Shift>). Используется только для определения информации модифицирующей клавиши В листинге 9.8 продемонстрировано использование обработчиков событий клавиатуры с отображением нажатой клавиши. Форма этой программы содержит две метки (элемент управления Label). В одной метке отображена нажатая клавиша, а информация о модификации — в другой метке. Изначально две метки (с именами charLabel и keylnfoLabel) — пустые. События KeyDown и KeyPress передают различную информацию, поэтому форма (KeyDemo) обрабатывает оба. • 1 // Листинг 9.8: KeyDemo.cs 2 // Отображение информации о нажатой пользователем клавише 3 4 using System; 5 using System.Drawing;
294 Глава 9 6 using System.Collections; 7 using System.ComponentModel; 8 using Sys tern. Windows. Forms; 9 using System.Data; 1.0 11 // форма для отображения нажатой клавиши 12 If информация выводится в две метки 13 public class KeyDemo : System.Windows.Forms.Form 14 { 15 private System.Windows.Forms.Label charLabel; 16 private System.Windows.Forms.Label keylnfoLabel; 17 18 private System.ComponentModel.Container components = null; 19 20 /// главная точка входа для приложения 21 [STAThread] 22 static void Main() 23 { 24 Application.Run (new KeyDemo()); 25 } 26 27 // отображение символа нажатой клавиши 28 protected void KeyDemo_KeyPress( 29 object sender, System.Windows.Forms.KeyPressEventArgs e) 30 { 31 charLabel.Text = "Key pressed: " + e.KeyChar; 32 } 33 34 // отображение модифицирующих клавиш, их кода, данных и значений 35 private void KeyDemo_Key_Down( 36 object sender, System.Windows.Forms.KeyEventArgs e) 37 { 38 keylnfoLabel.Text = 39 "Alt: " + (e.Alt ? "Yes" : "Wo") + ’\n' +- 40 "Shift: " + (e.Shift ? "Yes" : "No") + '\n' + 41 "Ctrl: " + (e.Control ? "Yes" : "No") + '\n' + 42 "KeyCode: " + e.KeyCode + '\n* + 43 "KeyData: " + e.KeyData + '\n' + 44 "KeyValue: " + e.KeyValue; 45 ) 46 47 // очистить метки при отпускании клавиши 48 private void KeyDemo_KeyUp( 49 object sender, System.Windows.Forms.KeyEventArgs e) 5'0 { 51 keylnfoLabel.Text = ""; 52 charLabel.Text = ""; 53 } 54 55 ) // конец класса KeyDemo Обработчик события Keypress (строки 28—32) осуществляет доступ к свойству KeyChar объекта KeyPressEven- tArgs. При этом нажатая клавиша возвращается как char и отображается в charLabel (строка 31). Если нажатая клавиша не являлась символом ASCII, тогда событие Keypress не запустится, и charLabel останется пустым. ASCII — это распространенный формат кодирования для букв, цифр, знаков пунктуации и других символов. Он не поддерживает функциональные клавиши (например, <F1>) и модифицирующие клавиши (<AIt>, <Ctrl> и <Shift>). Обработчик события KeyDown (строки 35—45) отображает больше информации из объекта KeyEventArgs. Дан- ный обработчик осуществляет проверку на предмет нажатия клавиш <Alt>, <Ctrl> и <Shift> (строки 39—41) с помощью свойств Alt, Control и shift, каждое из которых возвращает bool. Затем обработчик отображает свойства KeyCode, KeyData И KeyValue;
Концепции графического пользовательского интерфейса: часть 1 295 Свойство Keycode возвращает перечисление Keys, которое преобразуется в строку с помощью метода Tostring. Свойство Keycode возвращает нажатую клавишу, но не предоставляет никакой информации о модифицирующих клавишах. Таким образом, прописная и строчная буквы "а" представлены как клавиша <А>. Свойство KeyData также возвращает перечисление Keys, но включает данные о модифицирующих клавишах. Следовательно, если на входе — "А", то свойство KeyData показывает, что были нажаты клавиши <А> и <Shift>. Наконец, свойство Keyvalue возвращает код для клавиши, которая была нажата, как целое число. Это целое число является виртуальным кодом клавиш Windows, предоставляющее целое значение для широкого спектра клавиш, а также для кнопок мыши. Виртуальный код клавиш Windows полезен при проверке нажатия клавиш, не поддерживаемых форматом ASCII (например, клавиши <F12>). Обработчик события Кеуир очищает обе метки при отпускании клавиши (строки 48—53). Как видно по данным на выходе, клавиши, не поддерживаемые форматом ASCII, не отображаются в верхней метке charLabel, потому что не было сгенерировано событие Keypress. Событие KeyDown еще имеет место, и keyinfoLabel отображает информацию о клавише. Перечисление Key можно использовать для проверки нажатия определенных клавиш путем сравнения кода нажатой клавиши со специфическим Keycode. В документации Visual Studio .NET пред- ставлен полный список перечислений Keys. Результат работы программы представлен на рис. 9.18.. 1 <'уь. 1.ч ’ АН; No Shift Yes Ctrl: No KeyCode- Shitt^ey ® KeyData.Shitty Shift ; Key pressed.'1 Ait NO Shift'No CM No. j ft K’S*. OemQuotes .key Data CerriQuotes ?KeyValue 222 , Рис. 9.18. Демонстрация событий клавиатуры а — нажата клавиша <Н>; б — нажата клавиша <Enter>; в — нажата клавиша <Shift>; г — нажата клавиша <’> Замечание по технологии программирования______________________________________________ Для того чтобы элемент управления реагировал на нажатие клавиши (например, клавиши <Enter>), обработайте событие клавиши и проверьте ее. Для того чтобы нажатие клавиши <Enter> ассоциировалось со щелчком кнопки мыши на форме, задайте свойство формы AcceptButton. 9.11. Резюме Графический пользовательский интерфейс (GUI) дает возможность визуального взаимодействия с программа- ми. Именно от GUI зависит, как будет выглядеть программа и какие впечатления возникнут от работы с ней. Предлагая различные приложения с постоянным набором интуитивно понятных элементов управления пользо- вательских интерфейсов, GUI дают пользователям возможность сконцентрироваться на более продуктивном использовании программ. GUI строятся из компонентов GUI (иногда их называют оконными элементами управления). Компонент GUI — это визуальный объект, с которым пользователь взаимодействует с помощью мыши или клавиатуры. Когда пользователь взаимодействует с элементом управления, генерируется событие. Событие может запускать мето- ды, реагирующие на действия пользователя. События основаны на понятии делегатов. Делегаты выступают в роли посредников между объектами, генерирующими события, и методами, их обрабатывающими. Общий процесс проектирования Windows-приложений включает создание формы Windows, задание ее свойств, добавление элементов управления, задание свойств последних и конфигурирование обработчиков событий. Все формы, компоненты и элементы управления являются классами. Информация, необходимая программисту для регистрации события, представляет собой класс EventArgs (для определения обработчика событий) и делегат EventHandler (для регистрации обработчика события). Visual Stu- dio .NET обычно может зарегистрировать обработчик событий от имени программиста. Элемент управления TextBox (текстовое поле) — это область для одной строки, в которую можно вводить текст. Запаролированное текстовое поле скрывает каждый введенный символ специальным символом (например, *). Button (кнопка) — это элемент управления, который пользователь нажимает для выполнения определенного действия. Кнопки обычно реагируют на событие click (щелчок, однократное нажатие).
296 Гпава 9 Элементы управления GroupBox (группы) и Panel (панели) помогают расположить другие элементы управления в GUI. Основное различие между этими классами заключается в том, что группы могут отображать текст в заго- ловке, а панели — иметь полосы прокрутки. В C# имеются два типа так называемых кнопок состояния, имеющих значения on/off (или true/false): CheckBox (флажок) и RadioButton (переключатель). Флажок представляет собой маленький белый квадрат, либо пустой, либо отмеченный "галочкой". Переключатель объединяется в группы, в которых за один раз можно выбрать (отметить) только один переключатель. Для создания новой группы переключатели для нее должны быть до- бавлены в контейнер (например, GroupBox или Panel). Переключатели и флажки вызывают событие CheckChanged. События мыши (щелчки кнопками, нажатия на них и перемещения указателя мыши) обрабатываются для любо- го элемента управления GUI, являющегося производным от System.Windows.Forms.Control. События мыши используют класс MouseEventArgs (с соответствующим делегатом MouseEventHandler) и EventArgs (с соответст- вующим делегатом EventHandler). Класс MouseEventHandler содержит информацию о координатах х и у указателя мыши, нажатии кнопок мыши, количестве нажатий и количестве желобков, через которое повернулось колесо мыши. События клавиатуры генерируются при нажатии и отпускании клавиш. Эти события обрабатываются любым элементом управления, наследующего из System.Windows.Forms.Control. Событие KeyPress может возвращать char для любой клавиши ассоциированной с ASCII-символом. События кеуир и KeyDown проверяют наличие специальных модифицирующих клавиш (с помощью KeyEventArgs). Деле- гаты: KeyPressEventHandler (класс KeyPressEventArgs) И KeyEventHandler (класс keyEventArgs).
ГЛАВА 10 / (ш Концепции графического пользовательского интерфейса: часть 2 Заявляю, что не я контролировал события, но честно признаюсь, что события управляли мной. Авраам Линкольн Хороший символ — это лучший аргумент и посланец для убеждения тысяч. Ральф Уолдо Эмерсон Поймай реальность в краске! Пол Сезанн Но что за блеск я вижу на балконе? Там брезжит свет, Джульетта, ты — как день! Уильям Шекспир Если актер войдет через дверь, то в этом нет ничего особенного. Но если он входит через окно, то это уже ситуация. Билли Уайлдер Темы данной главьг О создание меню, вкладок и многодокументных (MDI) программ; □ использование элементов управления Listview и Treeview для отображения информации; □ использование гиперссылок с элементом управления LinkLabel; □ отображение списков с помощью элементов управления ListBox и comboBox; к □ создание кнопок, меток, списков, текстовых полей, панелей и манипуляции ними; □ создание пользовательских элементов управления. 10.1. Введение Данная глава продолжает исследование графического пользовательского интерфейса — GUI. Приступим к рас- смотрению более сложных тем с использованием широко применяемого компонента GUI — меню, обеспечи- вающего пользователя несколькими организованными в логическом порядке опциями. Читатели научатся соз- давать меню с инструментальными средствами, имеющимися в Visual Studio .NET. В главе будут представлены элементы управления LinkLabel— мощные компоненты GUI, дающие пользователю возможность щелкнуть кнопкой мыши для перехода к одному из нескольких мест назначений. Здесь рассматриваются компоненты GUI, инкапсулирующие в себе более мелкие компоненты GUI. Мы проде- монстрируем манипуляции списками значений посредством элемента управления ListBox, а также комбиниро- вание нескольких флажков в элементе управления CheckedListBox. С помощью элементов управления ComboBox будут созданы так называемые раскрывающиеся списки. Мы увидим, как можно иерархически отобразить дан- ные с помощью элемента управления Treeview. В этой же главе представлены два важных компонента GUI — вкладки и многодокументные окна. Эти компоненты позволяют разработчикам создавать реальные программы со сложными графическими пользовательскими интерфейсами. Большинство описываемых в книге компонентов GUI включены в Visual Studio .NET. Мы покажем процесс создания пользовательских элементов управления с их добавлением в панель Toolbox. Методики, описанные в настоящей главе, формируют основу создания сложных GUI и специальных элементов управления.
298 Глава 10 10.2. Меню Меню применяются для представления в Windows-приложениях групп связанных между собой команд Несмот- ря на то, что эти команды зависят от самой программы, некоторые из них, например Open и Save, имеются во многих программных приложениях. Меню являются неотъемлемой частью GUI, потому что они дают пользова- телям возможность выполнять операции без "перегрузки” GUI. На рис. 10.1 в развернутом меню представлены различные команды (называемые пунктами меню), а также подменю (меню в меню). Обратите внимание, что меню верхнего уровня показаны в левой части рисунка, а подменю, или пункты меню, — в правой. Меню, содержащее пункты, считается для них родительским. Пункт меню, содержащий подменю, считается для последнего родительским. Рис. 10.1. Развернутое меню (слева) и отмеченная команда меню (справа) Всем пунктам меню могут быть присвоены "горячие" клавиши вызова, доступ к которым осуществляется нажа- тием клавиши <Alt> и клавиши с подчеркнутой буквой (например, нажатие комбинации клавиш <AIt>+<F> вы- зовет открытие меню File). Не принадлежащие к верхнему уровню меню также могут иметь "горячие" клавиши вызова (комбинации с клавишами <Ctrl>, <Shift>, <Alt>, <F1>, <F2>, клавиш с буквами и т. д.). Слева от неко- торых пунктов меню могут ставиться метки ("галочки"), указывающие на то, что та или иная функция активизи- рована. Для создания меню откройте панель элементов Toolbox и перетяните элемент управления MainMenu на форму. При этом в верхней части формы будет создана строка меню, под которой будет размещена пиктограмма MainMenu. Для выбора MainMenu щелкните кнопкой мыши на пиктограмме. Такая настройка известна как Menu Designer в Visual Studio .NET и дает пользователю возможность создания и редактирования меню. Меню подоб- ны любым другим элементам управления; они имеют свойства, доступ к которым осуществляется через окно Properties или Menu Designer (рис. 10.2), и события, доступ к которым происходит через выпадающие меню Class Name и Method Name. Замечание о "впечатлениях и ощущениях"_______________________________________________________ Кнопки также имеют "горячие” клавиши доступа Введите символ & непосредственно перед символом (для кото- рого нужно создать "горячую" клавишу). После этого для нажатия кнопки пользователю достаточно нажать кла- вишу <Alt> и символ подчеркивания. Для добавления записей в меню щелкните мышью на текстовом поле Туре Неге и введите текст, который дол- жен появиться в меню. Каждая запись в меню принадлежит к типу Menuitem из пространства имен System.Windows.Forms. Само меню имеет тип MainMenu. После нажатия программистом клавиши <Enter> добав- ляется пункт меню. После этого появляются еще текстовые поля Туре Неге, позволяющие добавить пункты ниже или сбоку от первоначального пункта меню (см. рис. 10.2). Для создания "горячей" клавиши доступа вве- дите символ & перед символом, который должен быть подчеркнут. Например, для создания пункта меню File введите &Fiie. Сам символ & отобразится, если ввести &&. Для добавления других "горячих" клавиш" (например, <Ctrl>+<F9>) задайте свойство shortcut класса MainMenu. Разработчики могут удалить пункт меню выделением его мышью и нажатием клавиши <Delete>. Разделитель- ные черты вставляются щелчком правой кнопки мыши на меню и выбором команды Insert Separator или вво- дом символа — в качестве текста меню.
Концепции графического пользовательского интерфейса: часть 2 299 Поместите символ & перед той буквой, которая должна быть подчеркнута Menu Designer Значок MainMenu Рис. 10.2. Menu Designer При выборе пункта меню генерируется событие click. Для создания пустого обработчика событий войдите в режим редактирования кода, дважды щелкните мышью на Menuitem в режиме проектирования. В меню также могут отображаться заголовки открытых окон в многодокументном интерфейсе (Multiple Document Interface, MDI) — см. разд. 10.9. Свойства и события меню в общем виде перечислены в табл. 10.1. Таблица 10.1. Свойства и события классов MainMenu HMenuItem Свойства и события MainMenu и Menuitem Описание, делегат и аргументы события Свойства MainMenu Menuitems RightToLeft Список пунктов меню, содержащихся в MainMenu * Текст отображается справа налево. Полезно для языков, в которых принято чтение справа налево (например, в арабском) Свойства Menu Item Checked Указывает на отметку пункта меню (в соответствии со свойством RadioCheck). По умол- чанию — False; это означает, что пункт меню не отмечен Index Задает положение пункта в родительском меню Menuitems Перечисление пунктов подменю для конкретного пункта меню MergeOrder Установка положения пункта меню когда родительское меню объединяется (сливается) с другим меню MergeType Принимает значение перечисления MenuMerge. Указывает, как родительское меню объ- единяется с другим меню. Возможные значения: Add, Mergeitems, Remove и Replace RadioCheck Указывает, появляется ли выбранный пункт меню в виде переключателя (черный кру- жок), либо отображает контрольную метку в виде флажка. True создает переключатель, False отображает флажок. По умолчанию — False Shortcut Задает "горячую" клавишу для пункта меню (например, комбинация клавиш <Ctrl>+<F9> может быть эквивалентна щелчку кнопки мыши на определенном пункте) Showshortcut Указывает, показана ли "горячая" клавиша рядом с текстом пункта меню. По умолча- нию — True: "горячая" клавиша отображена Text Задает текст пункта меню. Для создания "горячей" клавиши доступа <Alt> символу предшествует & (например, &File для обозначения File) Общее событие (Делегат EventHandler, аргументы события EventArgs) Click Генерируется при щелчке кнопкой мыши на пункте меню или использовании "горячей" клавиши По умолчанию создается при двойном щелчке в режиме проектирования
300 Глава tO Замечание о "впечатлениях и ощущениях" ________________________________________________ Довольно удобно добавлять после пункта меню символ многоточия (...) для открытия диалогового окна (напри- мер, Save As...). Пункты меню, выполняющие операцию сразу без подсказок пользователю (например, Save), не должны иметь символа многоточия. Рассмотрим пример. Классом MenuTest (листинг 10.1) в форме создается простое меню. Форма имеет меню File верхнего уровня с пунктами About (появляется окно сообщения) и Exit. Сюда же входит меню Format, с по- мощью которого можно изменить форматирование текста в метке. Меню Format имеет подменю Color и Font, изменяющие цвет и гарнитуру шрифта текста в метке. Замечание о “впечатлениях и ощущениях"_______________________________________________________ Использование обычных "горячих" клавиш Windows (например, <Ctrl>+<F> для операций поиска (Find) и <Ctrl>+<S> для сохранения (Save)) упрощает изучение программы. Начнем с перетягивания элемента управления Menuitem из панели Toolbox в форму. Затем с помощью Menu Designer создадим общую структуру меню. В меню File имеются пункты About (aboutMenuitem, строка 21) и Exit (exitMenultem, строка 22); меню Format (formatMenu, строка 25) имеет два подменю. Первое подменю — Color (colorMenuItem, строка 30) содержит пункты Black (blackMenultem, строка 29), Blue (blueMenuItem, строка 30), Red (redMenuItem, строка 31) и Green (greenMenuItem, строка 32). Второе подменю— Font (fontMenuitem, строка 40) содержит пункты Times New Roman (timesMenuitem, строка 35), Courier (courierMenultem, строка 36), Comic Sans (comicMenuitem, строка 37), разделительную черту (separatorMenuitem, строка 42), Bold (boldMenultem, строка 38) и Italic (italicMenuItem, строка 39). 1 // Листинг 10.1: MenuTest.es 2 // Использование меню для изменения цвета гарнитуры шрифта и стилей 3 4 using System; 5 using System.Drawing; 6 using System.Collections; 7 using System.ComponentModel; 8 using System.Windows.Forms; 9 using System.Data; . « 10 11 public class MenuTest : System.Windows.Forms.Form 12 { 13 // отображения метки 14 private System.Windows.Forms.Label displayLabel; 15 16 // главное меню (содержит пункты File и Format) 17 private System.Windows.Forms.MainMenu mainMenu; 18 19 // меню File 20 private System.Windows.Forms.Menuitem fileMenuItem; 21 private System.Windows.Forms.Menuitem aboutMenuitem; 22 private System.Windows.Forms.Menuitem exitMenultem; 23 24 // меню Format 25 private System.Windows.Forms.Menuitem formatMenuItem; 26 27 // подменю Color 28 private System.Windows.Forms.Menuitem colorMenuItem; 29 private System.Windows.Forms.Menuitem blackMenultem; 30 private System.Windows.Forms.Menuitem blueMenuItem; 31 private System.Windows.Forms.Menuitem redMenuItem; 32 private System.Windows.Forms.Menuitem greenMenuItem; 33 34 // подменю Font 35 private System.Windows.Forms.Menuitem timesMenuitem; 36 private System.Windows.Forms.Menuitem courierMenultem; 37 private System.Windows.Forms.Menuitem comicMenuitem;
Концепции графического пользовательского интерфейса: часть 2 301 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 private System. Windows. Forms. Menuitem boldMenUItem; private System.Windows.Forms.Menuitem itaiicMenuItern; private System.Windows.Forms.Menuitem fontMenuItem; private System.Windows.Forms.Menuitem separatorMenuItern; [STAThread] static void Main() { Application.Run(new menuTest()); } 11 отображение MessageBox private void aboutMenuItem_Click( object sender, System.EventArgs e) { MessageBox.Show( "This is an example\nof using menus.", "About”, MessageBoxButtons.OK, MessageBoxIcon.Information); } 11 выход из программы private void exitMenuItem_Click( object sender, System.EventArgs e) { Application.Exit(); } // сбросить цвет private void ClearColorO { // сбросить все контрольные метки blackMenuItern.Checked = false; blueMenuItem.Checked = false; redMenuItem.Checked = false; greenMenuItern.Checked - false; 1 // обновление состояния меню и отображение черного цвета private void blackMenuItem_Click( object sender, System.EventArgs e) { // сброс контрольных меток для пунктов меню Color ClearColor(); 11 установка черного цвета displayLabel.ForeColor - Color.Black; blackMenuItem.Checked = true; ) // обновление состояния меню и отображение синего цвета private void blueMenuItem_Click( object sender, System.EventArgs e) { 11 сброс контрольных меток для пунктов меню Color ClearColor(); // установка синего цвета displayLabel.ForeColor = Color.Blue; blueMenuItem.Checked = true; }
302 Глава W 101 // обновление состояния меню и отображение красного цвета 102 private void redMenuItem_Click( 103 object sender, System.EventArgs e) 104 { 105 // сброс контрольных меток для пунктов меню Color 106 ClearColor(); 107 108 // установка красного цвета 109 displayLabel.ForeColor = Color.Red; 110 redMenuItem.Checked = true; 111 } 112 113 // обновление состояния меню и отображение зеленого цвета 114 private void greenMenu!tem_Click( 115 object sender, System.EventArgs e) 116 { 117 // сброс контрольных меток для пунктов меню Color 118 ClearColor(); 119 120 // установка зеленого цвета 121 displayLabel.ForeColor = Color.Green; 122 greenMenuItem.Checked » true; 123 ) 124 125 // сброс типов гарнитур 126 private void ClearFont() 127 { 128 // сброс всех контрольных меток 129 timesMenuItem - false; 130 courierMenuItem = false; 131 comicMenuItem = false; 132 } 133 134 // обновление состояния меню и установка гарнитуры Times 135 private void timesMenuItem_Click( 136 object sender, System.EventArgs e) 137 { 138 // сброс контрольных меток для пунктов меню Font 139 ClearFont(); 140 141 // установка гарнитуры Times New Roman 142 timesMenuItem.Checked = true; 143 displayLabel.Font = new Font( г 144 "Times New Roman", 14, displayLabel. Font. Style.); 145 } 146 147 // обновление состояния меню и установка гарнитуры Courier 148 private void courierMenuItem_Click( 149 object sender, System.EventArgs e) 150 { 151 // сброс контрольных меток для пунктов меню Font 152 ClearFont(); 153 154 // установка гарйитуры Courier 155 courierMenuItem.Checked = true; 156 displayLabel.Font = new Font( 157 "Courier New", 14, displayLabel.Font.Style); 158 } 159 160 Il обновление состояния меню и установка гарнитуры Comic Sans MS 161 private void comicMenuItemjClick( 162 object sender, System.EventArgs e)
Концепции графического пользовательского интерфейса: часть 2 303 163 { 164 // сброс контрольных меток для пунктов меню Font 165 ClearFont(); 166 167 // установка гарнитуры Comic Sans 168 comicMenuitem.Checked = true; 169 displayLabel.Font = new Font ( 170 "Comic Sans MS", 14, displayLabel.Font.Style); 171 } 172 173 // переключение контрольной метки и стиля bold 174 private void boldMenuItem_Click( 175 object sender, System.EventArgs e) 176 { 177 // переключение контрольной метки 178 boldMenultem.Checked = !boldMenultem.Checked; 179 180 // использование XOR для переключения bold с сохранением всех 180a // прочих стилей 181 displayLabel.Font - new Font( 182 displayLabel.Font.FontFamily, 14, 183 displayLabel.Font.Style Л FontStyle.Bold); 184 } 185 186 // переключение контрольной метки и стиля italic 187 private void italicMenu!tem_Click( 188 object sender, System.EventArgs e) 189 { 190 11 переключение контрольной метки 191 italicMenuItem.Checked = !italicMenuItem.Checked; 192 193 // использование XOR для переключения italic с сохранением 193a // всех прочих стилей 194 displayLabel.Font = new Font( 195 displayLabel.Font.FontFamily, 14, 196 displayLabel.Font.Style Л Fontstyle.Italic); 197 } 198 199 } // конец класса MenuTest При щелчке кнопкой мыши на пункте About в меню File отображается окно сообщения MessageBox (стро- ки 54—57). Пункт меню Exit закрывает приложение через static-метод Exit класса Application (строка 64). Класс Application содержит методы static, применяемые для управления исполнением программы. Метод Exit закрывает программное приложение. Пункты подменю Color (Black, Blue, Red, Green) сделаны взаимоисключающими: за один раз пользователь может выбрать только один цвет (далее этот процесс описывается вкратце). Для указания такого поведения пользователю свойства RadioCheck пункта меню были установлены на True. При этом появляется переключа- тель (вместо флажка), когда пользователь выбирает пункт цветового меню. Каждый пункт меню Color имеет собственный обработчик событий. Обработчик для цвета Black — blackMenuitem Click (строки 78—87). Обработчики событий для цветов Blue, Red и Green — blueMenuItem_Click (строки90—99), redMenu!tem_Click (строки 102—111) и greenMenuItem_Click (стро- ки 114—123), соответственно. Каждый пункт меню Color должен быть взаимоисключающим, чтобы каждый обработчик событий вызывал метод ciearcolor (строки 68—75) до присвоения его соответствующему свойству Checked значения True. Метод Clearcolor задает свойству Checked класса Menuitem каждого цвета значение False, что эффективно предотвращает выбор более одного пункта за один раз. Замечание по технологии программирования_________________________________________________ Взаимное исключение пунктов меню не применяется принудительно главным меню (Main Menu), даже когда свойство RadioCheck имеет значение True. Данное поведение должно программироваться вручную.
304 Глава 10 Замечание о "впечатлениях и ощущениях"______________________________________ Установите свойство RadioCheck для отображения желаемого поведения пунктов меню. Используйте переклю- чатели (свойство RadioCheck имеет значение True) для указания взаимоисключающих пунктов меню. Исполь- зуйте флажки (свойство RadioCheck имеет значение False) для пунктов меню, не имеющих логических ограни- чений. Меню Font содержит три пункта для типов гарнитур (Courier, Times New Roman и Comic Sans) и два пункта для стилей шрифта (Bold и Italic). Между пунктами типов гарнитур и стилей гарнитур вводится разделительная черта для указания разграничения: типы гарнитур — взаимоисключающие, а стили — нет. Это означает, что объект Font может указать за один раз только одну гарнитуру, которая может иметь несколько стилей (напри- мер, шрифт может быть полужирным и курсивным одновременно). Пункты типа гарнитур установлены на ото- бражение флажков. Так же, как и в меню Color, в обработчиках событий взаимное исключение необходимо программировать вручную. Обработчики событий для пунктов типов гарнитур TimesRoman, Courier и ComicSans — timesMenuitem click (строки 135—145), courierMenuItem Click (строки 148—158) и comicMenuItem_Click (строки 161—171), соот- ветственно. Поведение этих обработчиков событий похоже на поведение обработчиков для пунктов меню Color. Каждый обработчик события сбрасывает свойства Checked для всех пунктов типов гарнитур вызовом метода ClearFont (строки 126—132), после чего устанавливает свойство Checked пункта, сгенерировавшего со- бытие, на True. При этом осуществляется принудительное взаимное исключение пунктов типов гарнитур. Обработчики событий пунктов Bold и Italic (строки 174—197) используют побитовую операцию исключающе- го "ИЛИ" (XOR). Для каждого стиля гарнитуры операция исключающего "ИЛИ" (л) изменяет текст для включе- ния стиля, либо, если данный стиль уже применен, для его удаления. Поведение переключения, обеспеченное операцией Л, представлено в главе 9. Как описано в главе 9, данная операция позволяет добавлять и удалять записи в меню с минимальными изменениями структуры кода. Результат работы программы представлен на рис. 10.3. ’ Use the Fonnat nienu to change the appearance oi tin? text ffe’ Format Use the > ormatmenu to change the appearance of this f text. Рис. 10.3. Демонстрация меню: a — исходное состояние приложения; б — выбор команды Format | Font | Bold; в — результат выделения текста полужирным начертанием; г — выбор команды Format | Color | Red; д — результат форматирования и выбор команды File | About; е — диалоговое окно About ФТК> is an вхвпрЬ of using menus. 10.3. Элемент управления LinkLabel Элемент управления LinkLabel отображает ссылки на другие объекты, например, на файлы или Web-страницы (рис. 10.4). LinkLabel появляется в виде подчеркнутого текста (по умолчанию синего цвета). При перемещении мыши по ссылке ее курсор принимает изображение руки; это похоже на поведение гиперссылок в Web- страницах. Ссылка может изменять цвет для указания на то, что она новая, что ее уже посещали, либо на то, что она в данный момент активна. При щелчке мыши на LinkLabel генерируется событие LinkClicked (табл. 10.2). Класс LinkLabel является производным от класса Label и, следовательно, наследует всю функциональность класса Label.
Концепции графического пользовательского интерфейса: часть 2 305 LinkLabel в форме Изображение руки показывает, что указатель мыши находится над LinkLabel Рис. 10.4. Элемент управления LinkLabel в исполняющейся программе Таблица 10.2. Свойства и события LinkLabel Свойства и события класса LinkLabel Описание, делегат и аргументы события Общие свойства ActiveLinkColor При нажатии указывает цвет активной ссылки. По умолчанию — красный LinkArea Указывает, какая часть текста в LinkLabel рассматривается как часть ссылки LinkBehavior Указывает поведение ссылки, например, ее внешний вид при помещении на нее курсора мыши LinkColor Указывает первоначальный цвет всех ссылок до их посещения По умолчанию — синий Links Перечисление объектов LinkLabel.Link, являющихся ссылками, содержащимися в LinkLabel LinkVisited При значении True ссылка появляется так, будто было осуществлено ее посещение (цвет изменяется на заданный свойством VisitedLinkColor). По умолчанию — False Text Указывает текст элемента управления UseMnemonic При значении True, тогда символ & в свойстве Text выполняет роль задания "горячей" клавиши (аналогично "горячей" клавише <Alt> в меню) VisitedLinkColor f Указывает цвет посещенных ссылок. По умолчанию — Color. Purple Общее событие (Делегат LinkLabelLinkClickedEventHandler, аргументы события LinkLabelLink Cl ickEventArgs) LinkClicked Генерируется при щелчке по ссылке. Создается по умолчанию при двойном щелчке на элементе управления в режиме проектирования В классе LinkLabeiTest (листинг 10.2) используются три объекта LinkLabel для привязки к диску С:\, к Web- странице Deitel (www.deitel.com) и к приложению Notepad, соответственно. Для описания предназначения каж- дой из этих ссылок задаются свойства Text— driveLinkLabel (строка 14), deitelLinkLabel (строка 15) и notepadLinkLabel (строка 16) класса LinkLabel. Замечание о "впечатлениях и ощущениях" ____________________________________________________ Несмотря на то, что другие элементы управления могут выполнять операции, сходные с операциями LinkLabel (например, открытие Web-страницы), LinkLabel указывает на то, что данную ссылку можно просмотреть- обыч- ная метка или кнопка не всегда проводят эту идею 1 // Листинг 10.2: LinkLabeiTest.cs 2 // Использование LinkLabels для создания гиперссылок 3 4 using System; 5 using System.Drawing; 6 using System.Collections; 7 using System.ComponentModel; 8 using System.W indows.Forms; 9 using System.Data; 10 11 public class LinkLabeiTest : System.Windows.Forms.Form 12 { 13 // метки ссылок на диск С:, www.deitel.com и Notepad 14 private System.Windows.Forms.LinkLabel driveLinkLabel; 15 private System.Windows.Forms.LinkLabel deitelLinkLabel; 16 private System.Windows.Forms.LinkLabel notepadLinkLabel; 17 20 Зак. 3333
306 Глава 10 18 [STAThread] 19 static void Main() 20 { 21 Application.Run•new LinkLabelTest()); 22 } 23 24 // просмотр диска C:\ 25 private void driveLinkLabel_LinkClicked (object sender, 26 System.Windows.Forms.LinkLabelLinkClickedEventArgs e) 27 { 28 driveLinkLabel.LinkVisited - true; 29 System.Diagnostics.Process.Start (”C:\\"); 30 } 31 32 // загрузка www.deitel.com в Web-браузер 33 private void deitelLinkLabel_LinkClicked (object sender, 34 System.Windows.Forms.LinkLabelLinkClickedEventArgs e) 35 { 36 deitelLinkLabel.LinkVisited = true; 37 System.Diagnostics.Process.Start( 38 "lExplore", http://www.deitel.com); 39 } 40 41 11 запуск приложения Notepad 42 private void notepadLinkLabel_LinkClicked( 43 object sender, 4 4 System.Windows.Forms.LinkLabelLinkClickedEventArgs e) 45 { 46 * notepadLinkLabel.LinkVisited = true; 47 48 // программа вызвана, как в меню запуска; 49 //не требуется указания полного пути 50 System.Diagnostics.Process.Start("notepad"); 51 } 52 53 } // конец класса LinkLabelTest Обработчики событий для экземпляров LinkLabel вызывают static-метод start класса Process (пространство имен System. Diagnostics). Этот метод позволяет исполнять из данного приложения другие программы. Метод start может принимать в качестве аргументов либо файл для открытия (строку), либо название приложения для запуска и его аргументы командной строки (две строки). Аргументы метода start могут иметь ту же форму, как будто они предоставлены входными данными для команды Run в Windows. Для открытия файла, имеющего распознаваемый Windows-тип, просто вставьте полное имя файла. Для открытия этого файла операционная сис- тема Windows должна иметь возможность использовать приложение, к которому относится расширение этого файла. Для событий driveLinkLabel класса LinkClicked обработчик просматривает диск С:\ (строки 25—20). В стро- ке 28 свойству LinkVisited присваивается значение True. При этом цвет ссылки изменяется с синего на фиоле- товый (цвета LinkVisited можно сконфигурировать в окне Properties в среде разработки Visual Studio .NET). Затем обработчик событий передает "С: \" в метод start (строка 29), который открывает окно Windows Explorer. Обработчик событий LinkClicked класса deitelLinkLabel (строки 33—39) открывает Web-страницу www.deitel.com в Internet Explorer. Это достигается передачей строки "lExplore" и адреса Web-страницы (стро- ки 37 и 38), который открывает Internet Explorer. В строке 36 свойству LinkVisited присваивается значение True. Обработчик событий LinkClicked объекта notepadLinkLabel открывает указанное приложение Notepad (стро- ки 42—51). В строке 46 ссылка отображается как посещенная. В строке 50 аргумент "notepad" передается в метод start, который вызывает notepad.exe. Обратите внимание, что в строке 50 расширение ехе не требуется: система Windows может самостоятельно определить, является ли переданный в метод start аргумент испол- няемым файлом. Результат работы программы представлен на рис. 10.5.
Концепции графического пользовательского интерфейса: часть 2 307 б д Рис. 10.5. Использование элемента управления LinkLabel: а — выбирается первая гиперссылка; б — открытое окно Windows Explorer; в — выбирается вторая гиперссылка; а — открытое окно Internet Explorer; д — выбирается третья гиперссылка; е — открытое окно Notepad 10.4. Элементы управления List Box vt CheckedListBox Элемент управления ListBox дает пользователю возможность просмотра и выбора нескольких пунктов списка. Элементы управления ListBox являются статичными элементами GUI; это означает, что пользователи не могут вводить в список новые варианты. Элемент управления CheckedListBox расширяет ListBox вводом флажков
308 Глава 10 рядом с каждым элементом списка. Это дает возможность создания пометок сразу на нескольких пунктах, как в элементе управления CheckBox (также можно выбирать одновременно несколько пунктов из ListBox, но не по умолчанию). На рис. 10.6 показаны примеры списка и списка с флажками. В обоих элементах управления появ- ляется полоса прокрутки, если все пункты элемента не отображаются одновременно. В табл. 10.3 приведены общие свойства, методы и события класса ListBox. Выбранные пункты Отмеченный пункт ListBox CheckedListBox Полосы прокрутки Рис. 10.6. Элементы управления ListBox и CheckedListBox в форме Таблица 10.3. Свойства, методы и события класса ListBox Свойства и события класса ListBox Описание, делегат и аргументы события Общие свойства Items Перечисление набора пунктов в рамках ListBox MultiColumn Указывает, может ли ListBox разбить список на несколько столбцов. Множественные столбцы используются во избежание вертикальных полос прокрутки Selectedlndex Возвращает номер выбранного пункта. Если пользователь выбирает несколько пунк- тов, то данный метод произвольно возвращает один из выбранных индексов; если ни один из пунктов не выбран, тогда метод выдает -1 Selectedindices Возвращает набор указателей всех выбранных пунктов Selectedltem Возвращает ссылку на текущий выбранный пункт (если выбрано несколько пунктов, то возвращается пункт с самым низким порядковым номером) Selectedltems Возвращает набор текущих выбранных пунктов SelectionMode Определяет количество пунктов, которые можно выбрать, и средство выбора множе- ственных пунктов. Значения None, One, MultiSimple (допускается множественная выборка) и MultiExtended (множественная выборка допускается через комбинацию клавиш со стрелками, щелчков кнопки мыши и клавиш <Shift> и <Control>) Sorted Указывает, появляются ли пункты в алфавитном порядке. Значение True выполняет сортировку в алфавитном порядке; по умолчанию — False Общий метод GetSelected Принимает указатель и возвращает True, если выбран соответствующий пункт Общее событие (Делегат EventHandler, аргументы события EventArgs) SelectedindexChanged Генерируется при изменении выбранного указателя. Создается по умолчанию при двойном щелчке на элементе управления в режиме проектирования Свойство selectionMode определяет количество пунктов, которое можно выбрать. Это свойство имеет следую- щие возможные значения: None, One, MultiSimple и MultiExtended (из перечисления SelectionMode); различия этих настроек представлены в табл. 10.3. Событие SelectedindexChanged имеет место, когда пользователь вы- бирает новый пункт.
Концепции графического пользовательского интерфейса: часть 2 309 ListBox и CheckedListBox имеют свойства Items, Selectedltem и Selectedlndex. Свойство Items возвращает все объекты в списке в виде коллекции. Коллекции— это распространенный способ отображения списков objects в .NET Framework. Многие компоненты GUI.NET (например, ListBox) используют коллекции для ото- бражения списков внутренних объектов (например, пунктов, содержащихся в ListBox). Более подробно коллек- ции рассматриваются в главе 20. Свойство Selectedltem возвращает текущий выбранный пункт. Если пользова- тель может выбрать несколько пунктов, используйте коллекцию Seiecteditems для возвращения всех выбран- ных пунктов в виде коллекции. Свойство Selectedlndex возвращает номер выбранного пункта; если пунктов несколько, тогда пользуйтесь свойством Selectedindices. Если ни один пункт не выбран, тогда свойство Selectedlndex возвращает -1. Метод Getselected принимает указатель и возвращает True, если выбран соот- ветствующий пункт. Для добавления пунктов в ListBox или CheckedListBox в их коллекцию Items следует добавить объекты. Это можно сделать вызовом метода Add для добавления строки в коллекцию Items ListBox или CheckedListBox. На- пример, можно записать myListBox. Items .Add ("myListltem") для добавления String myListltem в ListBox myListBox. Для добавления нескольких объектов можно несколько раз воспользоваться методом Add или методом AddRange для добавления массива объектов. Классы ListBox и CheckedListBox используют метод Tostring каждого представленного объекта для определения метки записи соответствующего объекта в списке. Это позволяет разработчикам добавлять различные объекты в ListBox или CheckedListBox, которые потом могут быть возвращены посредством свойств Selectedltem и Seiecteditems. С другой стороны, пункты в ListBox и CheckedListBox можно добавлять визуально путем изучения свойства items в окне Properties. При нажатии кнопки... открывается String Collection Editor — текстовая область, в ко- торую можно ввести добавляемые пункты; каждый пункт должен появиться на отдельной строке (рис. 10.7). После этого Visual Studio .NET добавляет эти строки в коллекцию Items в методе InitializeComponent. Рис. 10.7. Редактор коллекции строк String Collection Editor 10.4.1. Элемент управления ListBox Класс ListBoxTest (листинг 10.3) дает пользователю возможность добавления, удаления и сброса пунктов из ListBoxdispxayListBox (строка 14). Класс ListBoxTest использует TexBoxinputTextBox (строка 17) для обеспе- чения пользователя возможностью ввода нового пункта. При нажатии кнопки addButton (строка 20) в displayListBox появляется новый пункт. Точно так же при выборе пользователем пункта и нажатии removeButton (строка 21) этот пункт удаляется. Элемент управления clearButton (строка 22) удаляет все записи 11 displayListBox. Пользователь останавливает работу приложения нажатием кнопки exitButton (строка 23). Обработчик событий addButton_Click (строки 33—38) вызывает метод Add коллекции items в ListBox. Данный метод принимает в качестве пункта строку для ее добавления в displayListBox. В этом случае строка является пользовательским текстом ввода или inputTextBox.Text (строка36). После добавления пункта txtinput.Text удаляется (строка 37). Обработчик события removeButton Click (строки 41—48) вызывает метод Remove коллекции item. Сначала обработчик removeButton_Click использует свойство Selectedlndex для проверки выбранного указателя. Если Selectedlndex не равен -1 (строка 45), тогда обработчик удаляет пункт, соответствующий выбранному указателю. Обработчик события clearButton_Click (строки 51—55) вызывает метод Clear коллекции Items (строка 54). При этом все записи в displayListBox удаляются. Наконец, обработчик события exitButtonClick (строки 58— 62) прерывает исполнение приложения с помощью метода Application.Exit (строка 61).
310 Глава 10 -« ч . ...v.-- .'.Л ” aS Листинг 10.3. Список, использованный для добавления, удаления и сброса пунктов списка »• • • jtj-.r ... • J I.-"- , < >, <* • . . ь ч,.- ; " - * -• ............--------- 1 2 3 4 5 б 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 // Листинг 10.3: LinkBoxTest.es // Программа для добавления, удаления и сброса пунктов списка using System; using System.Drawing; using System.Collections; using Systera.ComponentModel; using System.Windows.Forms; using System.Data; public Class LinkBoxTest : System.Windows.Forms.Form { // содержит список элементов, введенных пользователем private System.Windows.Forms.ListBox displayListBox; // текстовое поле для пользовательских входных данных private System.Windows.Forms.TextBox inputTextBox; // кнопки добавления, удаления, сброса и редактирования пунктов private System.Windows.Forms.Button addButton; private System.Windows.Forms.Button removeButton; private System.Windows.Forms.Button clearButton; private System.Windows.Forms.Button exitButton; [STAThread] static void Main() { Application.Run(new ListBoxTest()); } // добавление нового пункта (текста из поля ввода) //и сброс поля ввода private void addButton_Click( object sender, System.EventArgs e) ( displayListBox.Items.Add(inputTextBox.text); inputTextBox.Clear(); } // удаление выбранного пункта private void removeButton_Click( object sender, System.EventArgs e) { // удаление только выбранного пункта if (displayListBox.Selectedlndex ! = -1) displayListBox.Items.RemoveAt( displayListBox.Selectedlndex); } // сброс всех пунктов private void clearButtonClick( object sender, System.EventArgs e) { displayListBox.Items.Clear( ) // выход из приложения private void exitButton_Click( object sender, System.EventArgs e)
Концепции графического пользовательского интерфейса: часть 2 311 60 { 61 Application.Exit(); 62 ) 63 64 } // конец класса ListBoxTest Результат работы программы представлен на рис. 10.8. б г Рис. 10.8. Демонстрация элемента управления ListBox: а —добавление пункта в список; б — удаление выбранного пункта; в — удаление всех пунктов списка; г — результат после нажатия кнопки Clear 10.4.2. Элемент управления CheckedListBox Класс CheckedListBox является производным от класса ListBox и включает в себя флажок рядом с каждым пунктом списка. Как и в ListBox, пункты можно добавлять посредством методов Add и AddRange, либо через редактор набора строк String Collection Editor. CheckedListBox подразумевает выбор нескольких пунктов, и единственными возможными значениями для свойства SelectionMode являются SelectionMode. None и SelectionMode.One. SelectionMode.One обеспечивает множественную выборку, потому что флажки подразуме- вают отсутствие логических ограничений на пункты: пользователь может выбрать их сколько угодно. Следова- тельно, здесь имеется единственная альтернатива: предоставить пользователю множественную выборку, либо вообще никакой. При этом поведение CheckedListBox будет поддерживаться согласованным с поведе- нием CheckBox. Программист не может задать два последних значения SelectionMode — MultiSimple и MultiExtended, потому что None и One обрабатывают только режимы логической выборки. Общие свойства и события CheckedListBox представлены в табл. 10.4. Таблица 10.4. Свойства, методы и события CheckedListBox Свойства, методы и события класса CheckedListBox Описание, делегат и аргументы события Общие свойства (Все свойства и события ListBox наследуются CheckedListBox) Checkedltems Перечисление коллекции отмеченных пунктов. Отличается от выделенных пунк- тов, которые подсвечиваются (но не обязательно отмечаются). В любое время отмеченным может быть только один пункт
312 Глава 10 Таблица 10.4 (окончание) Свойства, методы и события класса CheckedListBox Описание, делегат и аргументы события Checkedindices Возвращает указатели для отмеченных пунктов. Это — не то же самое, что вы- деленные указатели SelectionMode Определяет количество отмеченных пунктов. Возможные значения: One (допус- кает создание множественной выборки) или None (не допускает размещения флажков) z Общий метод GetItemChecked Принимает указатель и возвращает значение True, если соответствующий пункт отмечен Общее событие (Делегат ItemCheckEventHandler, аргументы события ItemCheckEventArgs) ItemCheck Генерируется, когда пункт отмечается или отметка снимается Свойства ItemCheckEventArgs - Currentvalue Указывает, отмечен или не отмечен текущий пункт. Возможные значения: Checked, Unchecked и Indeterminate Index Возвращает указатель измененного пункта NewValue Указывает новое состояние пункта Распространенная ошибка программирования_________________________________________________ IDE выводит сообщение об ошибке, если программист делает попытку установить свойство SelectionMode в окне Properties для элемента управления CheckedListBox на MultiSimple или MultiExtended; если это зна- чение задается программистом в коде, то во время выполнения возникает ошибка. Событие ItemCheck генерируется всякий раз при выборе или отключении пользователем пункта CheckedListBox. Свойства Currentvalue и NewValue аргумента события возвращают значения Checkstate для текущего и нового состояний пункта, соответственно. Сравнение этих значений позволяет определить, отмечен или отключен пункт CheckedListBox. Элемент управления CheckedListBox сохраняет свойства Seiecteditems и Selectedlndices (он наследует их из класса ListBox). Однако он также включает в себя свойства Checkedltems и Checkedindices, возвращающие информацию об отмеченных пунктах и указателях. В листинге 10.4 классом CheckedListBoxTest используются объекты CheckedListBox и ListBox для отображения пользовательской подборки книг. CheckedListBox под именем inputCheckedListBox (строки 14 и 15) дает поль- зователю возможность выбрать несколько наименований. В редакторе String Collection Editor были добавлены наименования (пункты) для некоторых книг от Deitel: "C++", "Java", "VB", "Internet & WWW", "Perl", "Python", "Wireless Internet” и "Advanced Java" (акроним НТР обозначает "How to Program" — как программировать). Элемент управления ListBox под именем displayListBox (строка 18) отображает выбор пользователя. На рис. 10.9 CheckedListBox демонстрируется слева, a ListBox — справа. । Листинг 10.4. Список и список с флажками, использованные для отображения выборки пользователя 1 // Листинг 10.4: CheckedListBoxTest.cs 2 // Использование списков с флажками для добавления в них пунктов 3 4 using System; 5 using System.Drawing; 6 using System.Collections; 7 using System.ComponentModel; 8 using System.Windows.Forms; 9 using System.Data; 10 11 public class CheckedListBoxTest : System.Windows.Forms.Form 12 { 13 // список имеющихся наименований книг 14 private System.Windows.Forms.CheckedListBox 15 inputCheckedListBox; 16
Концепции графического пользовательского интерфейса: часть 2 313 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 // список выборки пользователя private System.Windows.Forms.ListBox displayListBox, [STAThread] static void Main() { Application.Run(new CheckedListBoxTest()); } // пункт для изменения, // добавления или удаления из displayListBox private void inputCheckedListBox_ItemCheck( object sender, System .Windows. Forms. ItemCheckEventArgs e) { // получение ссылки на выбранный пункт string item = inputCheckedListBox.Selectedltem.TiString() ; // если пункт отмечен, добавить в список, // в противном случае удалить из списка if (e.NewValue == Checkstate.Checked) displayListBox.Items.Add(item); else displayListBox.Items.Remove(item) } // конец метода inputCheckedListBox_Click ) // конец класса CheckedListBox Когда пользователь отмечает пункт (или сбрасывает флажок) в inputCheckedListBox, система генерирует собы- тие itemcheck. Обработчик inputCheckedListBox_itemCheck (строки 28—43) обрабатывает это событие. Опера- тор if/else (строки 38—41) определяет, отметил пользователь пункт в CheckedListBox или снял отметку. В строке 38 используется свойство NewValue для проверки, отмечен пункт или нет (Checkstate.Checked). Если пользователь отмечает пункт, тогда строка 39 добавляет отмеченную запись в ListBoxdispiayListBox. Если пользователь сбрасывает флажок, тогда в строке 41 удаляется соответствующий пункт из displayListBox. Результат работы программы представлен на рис. 10.9. Рис. 10.9. Демонстрация элемента управления CheckedListBox: а — исходное состояние приложения; б — отмечены первые три пункта; в — сброшен флажок во втором пункте списка; а — отмечен флажок последнего пункта списка
314 Глава 10 10.5. Элемент управления ComboBox Элемент управления ComboBox (комбинированный список) объединяет в себе функции текстового поля с рас- крывающимся списком. Раскрывающийся список — это компонент GUI, содержащий список, из которого мож- но выбирать различные значения. Обычно он появляется как текстовое поле с указывающей вниз стрелкой справа. По умолчанию пользователь может вводить текст в текстовое поле, либо нажать на стрелку для про- смотра списка ранее определенных пунктов. При выборе пользователем элемента из этого списка данный эле- мент отображается в текстовом поле. Если список содержит больше элементов, чем может быть, то появляется полоса прокрутки. Максимальное количество пунктов, которое может содержаться в раскрывающемся списке, задается свойством MaxDropDownitems. На рис. 10.10 показан пример ComboBox в трех разных состояниях. Щелкните на стрелке для просмотра раскрывающегося Выбранный пункт раскрывающегося списка изменяет текст в текстовом поле Рис. 10.10. Пример комбинированного списка Как и в случае с элементом управления ListBox, разработчик может программным способом добавлять объекты в коллекцию items с помощью методов Add и AddRange, либо визуально— через String Collection Editor. В табл. 10.5 представлены общие свойства и события класса ComboBox. Таблица 10.5. Свойства и события класса ComboBox События и свойства ComboBox Описание, делегат и аргументы события Общие свойства DropDownStyle Определяет тип комбинированного списка. Значение Simple указывает, что часть текста можно редактировать, и данная часть списка всегда видима. Значение DropDown (по умол- чанию) означает, что часть текста можно редактировать, но пользователь должен щелк- нуть по кнопке со стрелкой, чтобы просмотреть часть списка. Значение DropDownList указывает, что данную часть текста редактировать нельзя, и для просмотра части списка пользователь должен щелкнуть по кнопке со стрелкой Items MaxDropDownitems Коллекция пунктов в элементе управления ComboBox Указывает максимальное количество пунктов (от 1 до 100), которые могут отобразиться в раскрывающемся списке Если количество пунктов превышает максимальное число ото- бражаемых пунктов, то появляется полоса прокрутки Selectedlndex Возвращает указатель текущего выбранного пункта. Если текущих выбранных пунктов нет, то возвращается значение -1 Selecteltem Возвращает ссылку на текущий выбранный пункт Sorted Указывает на сортировку пунктов по алфавиту. При значении True пункты появляются в алфавитном порядке Значение по умолчанию — False Общее событие (Делегат EventHandler, аргументы события Event Args) SelectedlndexChanged Генерируется, когда выбранный пункт изменяется (например, ставится или снимается флажок) Создается по умолчанию при двойном щелчке на элементе управления в режи- ме проектирования Замечание о "впечатлениях и ощущениях"__________________________________________________ Пользуйтесь элементом управления ComboBox для экономии свободного пространства GUI. Преимущество за- ключается в том, что, в отличие от ListBox, пользователь не может просмотреть имеющиеся пункты без про- крутки.
Концепции графического пользовательского интерфейса: часть 2 315 Свойство DropDownstyle определяет тип комбинированного списка. Стиль Simple не отображает стрелку. Вме- сто этого рядом с элементом управления появляется полоса прокрутки, дающая пользователю возможность сде- лать выбор из списка. Пользователь также может ввести необходимое значение вручную. Стиль DropDown (по умолчанию) отображает раскрывающийся список при нажатии на указывающую вниз стрелку. Пользователь может ввести новый пункт в ComboBox. Последний стиль — DropDownList, отображающий раскрывающийся список, но не разрешающий пользователю вводить новый пункт. Раскрывающийся список экономит свободное пространство GUI, поэтому ComboBox сле- дует использовать при ограниченном свободном пространстве. Элемент управления ComboBox имеет свойства Items, Selectedltem и Selectedlndex, схожие с соответствующи- ми свойствами в ListBox. В ComboBox можно выбрать максимум один пункт (если ничего не выбрано, тогда Selectedlndex---1). При смене выбранного пункта генерируется событие SelectedindexChanged. Класс ComboBoxTest (листинг 10.5) дает пользователям возможность выбрать фигуру для рисования: пустую или заполненную окружность, эллипс, квадрат или сектор с помощью ComboBox. Комбинированный список данном примере редактировать нельзя, поэтому пользователь не может ввести свой, специальный пункт. Замечание о "впечатлениях и ощущениях"____________________________________________________ Делайте списки (например, комбинированные) редактируемыми, только если программа спроектирована на при- нятие элементов вводимых пользователем В противном случае пользователь может ввести настраиваемый пункт, после чего не сможет его отредактировать. 1 // Листинг 10.5: ComboBoxTest.cs 2 // Использование комбинированного списка для выбора фигур 3 4 using System; 5 using System.Drawing; 6 using System.Collections; 7 using System.ComponentModel; 8 using System.Windows.Forms; 9 using System.Data; 10 11 public class ComboBoxTest : System.Windows.Forms.Form 12 { 13 // содержит список фигур (окружность, квадрат, эллипс, сектор) 14 private System.Windows.Forms.ComboBox imageComboBox; 15 16 [STAThread] 17 static void Main() 18 { 19 Appl (.cation.Run (new ComboBoxTest ()); .* 20 } 21 22 // получение указателя, рисование геометрической фигуры 23 private void imageComboBox_SelectedIndexChanged( 24 object sender, System.EventArgs e) 25 { 26 // создание графического объекта, пера (Реп) и кисти (SolidBrush) 27 Graphics myGraphics = base.CreateGraphicsO; 28 29 // создание Pen с помощью цвета DarkRed 30 Pen myPen = new Pen(Color.DarkRed); 31 32 // создание SolidBrush с помощью цвета DarkRed 33 SolidBrush mySoliBrush = 34 new SolidBrush(Color.DarkRed); 35 36 // сбросить настройку области рисования для ее раскрашивания белым цветом 37 myGraphics.Clear(Color.White); 38
316 Глава 10 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 // найти указатель, нарисовать нужную фигуру switch (imageComboBox«Selectedlndex) { case 0: // выбран случай окружности myGraphics.DrawEllipse( myPen 50, 50, 150, 150); break; case 1: // выбран случай прямоугольника myGraphics.DrawRectangle( myPen 50, 50, 150, 150); break; case 2: // выбран случай эллипса myGraphics.DrawEllipse( myPen 50, 85, 150, 115); break; case 3: // выбран случай сектора myGraphics.DrawPie( myPen 50, 50, 150, 150, 0, 45) ; break; case 4: // выбран случай закрашенной окружности myGraphics.FillEllipse( mySolidBrush 50, 50, 150, 150); break; case 5: // выбран случай закрашенного прямоугольника myGraphics.FillRectangle( mySolidBrush 50, 50, 150, 150); break; case 6: // выбран случай закрашенного эллипса myGraphics.DrawEllipse( mySolidBrush 50, 85, 150, 115); break; case 7: // выбран случай закрашенного сектора myGraphics.FillPie ( mySolidBrush 50, 50, 150> 150, 0, 45) ; break; } // конец switch } // конец метода imageComboBox_Selected!ndexChanged 79 } // конец класса ComboBoxTest После создания imageComboBox (строка 14) сделаем его не редактируемым установкой DropDownStyle на Drop- DownList в окне Properties. Затем добавляем в коллекцию Items пункты Circle, Square, Ellipse, Pie, Filled Circle, Filled Square, Filled Ellipse и Filled Pie с помощью редактора String Collection Editor. Всякий раз при выборе пользователем пункта из imageComboBox система генерирует событие SelectedlndexChanged. Обра- ботчик imageComboBox_SelectedlndexChanged (строки 23—77) обрабатывает эти события. Строки 27—34 соз- дают объекты Graphics, Реп и SolidBrush, с помощью которых программа будет выполнять рисование в форме. Объект Graphics (строка 22) обеспечивает рисование пером или кистью в компоненте, с помощью одного из нескольких методов Graphics. Объект Реп используется методами drawEllipse, drawRectangle и drawPie (стро- ки 43—56) для рисования контуров соответствующих фигур. Объект SolidBrush используется методами fillEllipse, fillRectangle и fillPie (строки 59—72) для изображения соответствующих закрашенных фигур. В строке 37 всей форме присваивается белый цвет (white) с помощью метода Clear объекта Graphics. Подроб- нее эти методы рассматриваются в главе 13. Программа рисует геометрическую фигуру, заданную выбранным номером пункта. Оператор switch (стро- ки 40—75) использует imageComboBox.Selectedlndex для определения выбранного пункта. Метод DrawEllipse (строки 43 и 44) класса Graphics принимает Реп, координаты х и у центра, а также ширину и высоту будущего эллипса. Начало координат находится в левом верхнем углу формы; абсцисса увеличивается при перемещении вправо, ордината — вниз. Окружность — это особый случай эллипса (высота и ширина одинаковы). В строках 43 и 44 рисуется окружность. В строках 51 и 52 — эллипс, имеющий разные значения высоты и ширины.
Концепции графического пользовательского интерфейса: часть 2 317 Метод DrawEllipse (строки 47 и 48) класса Graphics принимает Реп, координаты х и у верхнего левого угла, а также ширину и высоту прямоугольника, который нужно изобразить Метод DrawPie (строки 55 и 56) изобража- ет сектор как часть эллипса. Эллипс ограничен прямоугольником. Метод DrawPie принимает Реп, координаты х и у верхнего левого угла прямоугольника, его ширину и высоту, угол начала (в градусах) и угол поворота (в гра- дусах) сектора. Значения углов увеличиваются по часовой стрелке Методы FillEllipse (строки 59—60 и 67— 68), FillRectangle (строки 63 и 64) и FillPie (строки 71 и 72) сходны со своими незаполненными аналогами, за исключением того, что вместо Реп Они принимают SolidBrush. Некоторые из нарисованных фигур показаны на рис. 10.11. Рис. 10.11. Демонстрация комбинированного списка: а — раскрытый список, б — нарисована окружность, в — нарисован закрашенный прямоугольник; а — нарисован сектор 10.6. Элемент управления TreeView Элемент управления Treeview иерархически отображает узлы на дереве. Традиционно, узлы (node) — это со- держащие значения объекты, которые могут обращаться к другим узлам. Узел-родитель содержит узлы- потомки, а последние могут быть родителями для других узлов. Два узла-потомка, имеющие один узел- родитель, называются узлами-братьями (sibling nodes). Дерево (tree) — это коллекция узлов, расположенных по иерархии. Первый родительский узел дерева является корневым узлом (Treeview может иметь много корневых узлов). Например, файловую систему компьютера можно представить в виде дерева. Каталог верхнего уровня (например, С:) будет корнем, каждый подкаталог С: будет узлом-потомком, и каждая дочерняя папка может иметь своих "детей". Элементы управления Treeview полезны для отображения иерархической информации, например, только что упомянутой файловой структуры. Более подробно узлы и деревья рассматриваются в гла- ве 20. На рис. 10.12 показан пример использования элемента управления Treeview на форме. Узел-родитель можно разворачивать или сворачивать нажатием на значок + или - слева. "Бездетные" узлы не имеют значка развертывания и сворачивания. Узлы в Treeview являются экземплярами класса TreeNode. Каждый TreeNode имеет коллекцию Nodes (тип TreeNodeCollection), содержащую список других TreeNode — потомков. Свойство Parent возвращает ссылку на узел-родитель (или null, если узел является корневым). В табл. 10.6 и 10.7 перечислены общие свойства Treeview и TreeNode, а также событие объекта TreeView.
318 Глава 10 Щелкните по значку* для разворачивания узла и отображения узлов-потомков Щелкните по значку- для сворачивания узла и скрытия узлов-потомков Рис. 10.12. Элемент управления Treeview, демонстрирующий дерево Таблица 10.6. Свойства и события Treeview События и свойства TreeView Описание, делегат и аргументы события Общие свойства CheckBoxes Указывает на появление рядом с узлами значков позиций. Значение True отображает значки позиций. Значение по умолчанию — False ImageList Указывает ImageList, используемый для отображения пиктограмм рядом с узлами. ImageList — это коллекция, содержащая некоторое количество объектов Image Nodes Перечисление коллекции TreeNode в элементе управления. Содержит методы Add (до- бавление объекта TreeNode), Clear (удаление всей коллекции) и Remove (удаление кон- кретного узла). При удалении узла-родителя удаляются все его потомки SelectedNode Узел, выбранный в настоящий момент Общее событие (Делегат TreeViewEventHandler, аргументы события TreeViewEventArgs) AfterSelect Генерируется после изменения выбранного узла. Создается по умолчанию при двойном щелчке на элементе управления в режиме проектирования Таблица 10.7. Свойства и методы TreeNode Свойства и методы TreeNode Описание, делегат и аргументы события Общие свойства Checked Указывает, выбран TreeNode или нет. Свойству CheckBoxes в TreeView должно быть при- своено значение True FirstNode Указывает первый узел в коллекции Nodes (т. е. первого потомка в дереве) FullPath Указывает путь узла, начиная с корня дерева Imageindex Задает номер изображения для показа после отмены выделения узла LastNode Задает последний узел в коллекции Nodes (т. е. последнего потомка в дереве) NextNode Следующий узел-брат Nodes Коллекция TreeNodes, содержащаяся в текущем узле (т. е. все потомки текущего узла). Со- держит методы Add (добавление объекта TreeNode), Clear (удаление всей коллекции) и Remove (удаление конкретного узла). При удалении узла-родителя удаляются все его потом- ки PrevNode Предыдущий узел-брат Selectedlmagelndex Задает номер изображения для использования при выборе узла Text Задает текст, отображаемый в TreeView Общие методы Coplapse Свертывание узла Expand Развертывание узла
Концепции графического пользовательского интерфейса: часть 2 319 Таблица 10.7 (окончание) Свойства и методы TreeNode Описание, делегат и аргументы события ExpandAll Развертывание всех потомков узла GetNodeCount Возвращение числа узлов-потомков Для визуального добавления узлов в объект Treeview нажмите кнопку ... рядом со свойством Nodes в окне Properties. Откроется редактор TreeNode Editor с отображением пустого дерева, представляющего Treeview (рис. 10.13). Для создания, добавления или удаления узла имеются соответствующие кнопки. Рис. 10.13. Редактор узлов TreeNode Editor Для добавления узлов через код сначала необходимо создать корневой узел. Создайте новый объект TreeNode и передайте ему строку для отображения. Затем для добавления нового TreeNode в коллекцию Nodes элемента управления Treeview воспользуйтесь методом Add. Таким образом, для добавления корневого узла в Treeview myTreeview запишите myTreeView.Nodes.Add (new TreeNode (RootLabel}) где myTreeView— TreeView, к которому добавляются узлы, a RootLabel — текст для отображения в myTreeView. Для добавления в корневой узел потомков добавьте новые TreeNode в коллекцию Nodes. Надлежащий корневой узел выбирается из Treeview записью myTreeView. Nodes [myIndex] где myindex — номер корневого узла в коллекции Nodes myTreeView. Добавление узлов к узлам-потомкам осу- ществляется тем же способом, что и добавление корневых узлов к myTreeView. Для добавления потомка к кор- невому узлу в указателе myindex запишем myTreeView. Nodes [myindex] .Nodes. Add (new TreeNode (ChildLabel}) Класс TreeViewDirectoryStructureTest (листинг 10.6) использует Treeview для отображения структуры катало- гов в компьютере. Корневой узел — диск С:; каждый подкаталог С: становится потомком. Данная структура сходна с применяющейся в Windows Explorer. Каталоги можно разворачивать и сворачивать щелчками по нахо- дящимся слева символам + или -. Листинг 10.6. Дерево, использованное для отображения каталогов ЫЖда 1 // Листинг 10.6: TreeViewDirectoryStructureTest.cs 2 // Использование TreeView для отображения структуры каталогов 3 4 using System; 5 using System.Drawing;
320 Глава 10 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 using System.Collections; using System.ComponentModel; using System.Windows.Forms; using System.Data; using System.10 public class TreeViewDirectoryStructUreTest^. : System.Windows.Forms.Form { // содержит структуру каталогов диска С: private System.Windows.Ferms.TreeView directoryTreeView; [STAThread] static void Main() { Application.Run() new TreeViewDirectoryStructureTest()); } public void PopulateTreeView( string directoryvalue, TreeNode parentNode) { // получение подкаталогов string!] directoryArray = Directory.GetDirectories(directoryvalue); // заполнение текущего узла подкаталогами try ( if (directoryArray.Length != 0) ( // для каждого подкаталога создание нового TreeNode, // добавление в качестве потомка текущего узла и // рекурсивное заполнение узлов-потомков подкаталогами foreach (string directory in directoryArray) { // создание TreeNode для текущего каталога TreeNode myNode = new TreeNode(directory); // добавление узла•текущего каталога в узел-родитель parentNode.Nodes.Add(myNode); // рекурсивное заполнение каждого подкаталога PopulateTreeView(directory, myNode); ) } // конец if } // исключение catch catch (UnauthorizedAccessException) { parentNode.Nodes.Add("Access denied"); } } // конец PopulateTreeView // вызов системой после загрузки формы private void TreeViewDirectoryStructureTest_Load ( object sender, System.EventArgs e) { // добавление диска С: в directoryTreeView и // вставка его подкаталогов directoryTreeView.Nodes.Add ("С:\\");
Концепции графического пользовательского интерфейса: часть 2 321 70 PopulateTreeView( 71 "С:\\", directoryTreeView.Nodes [0]); 72 } 73 74 } // конец класса TreeViewDirectoryStructure При загрузке TreeViewDirectoryStructureTest система генерирует событие Load, обрабатываемое TreeViewDirectoryStructureTest_Load (строки 64—72). В строке 69 добавляется корневой каталог (с:) в Treeview с именем directoryTreeView. С: — корневой каталог для всей структуры каталогов. В строках 70 и 71 вызывается метод PopulateTreeView (строки 25—61), принимающий каталог и узел-родитель. После этого ме- тод PopulateTreeView создает узлы-потомки, соответствующие подкаталогам переданного в него каталога. Метод PopulateTreeView (строки 25—61) получает список подкаталогов с помощью метода Get Directories класса Directory (пространство имен System. 10) на строках 29 и 30. Метод Get Directories получает строку (текущий каталог) и возвращает массив строк (подкаталоги). Если доступ к каталогу закрыт из соображений защиты, то выдается исключение UnauthorizedAccessException. В строках 56- -59 это исключение захватыва- ется и добавляется узел, содержащий "Access Denied", вместо отображения подкаталогов. При наличии доступных подкаталогов каждая строка в directoryArray используется для создания нового узла- потомка (строка 43). Здесь для добавления каждого узла-потомка в родительский узел применяется метод Add (строка 46). Затем на каждый подкаталог рекурсивно вызывается метод PopulateTreeView (строка 49) и в ко- нечном итоге заполняет всю структуру каталогов. Применяющийся здесь рекурсивный алгоритм вызывает за- держку программы при первоначальной загрузке: она должна создать дерево для всего диска С:. Однако, как только имена каталогов диска добавлены в соответствующую коллекцию Nodes, их можно разворачивать и сво- рачивать без задержки. (В следующем разделе представлен альтернативный алгоритм решения данной задачи.) Результат работы программы представлен на рис. 10.14. в Рис. 10.14. Демонстрация элемента управления TreeView: а — дерево свернуто; б — дерево развернуто в корне; в — развернут узел C:\System Volume Information; a — развернут узел C:\RECYCLER 21 Зак. 3333
322 Глава 10 10.7. Элемент управления ListView Элемент управления Listview похож на ListBox тем, что оба отображают списки, из которых пользователь мо- жет выбрать один или несколько пунктов. Важным различием между этими двумя классами является то, что Listview может многими способами отображать пиктограммы вместе с пунктами списка (управляются свойст- вом ImageList). Свойство MultiSelect определяет возможность выбора нескольких пунктов. Можно добавить кнопки позиций списка присвоением свойству CheckBoxes значения True, что сделает внешний вид Listview похожим на внешний вид CheckedListBox. Свойство view задает топологию класса ListBox. Свойство Activation определяет метод, по которому пользователь выбирает позицию списка. Подробности этих свойств представлены в табл. 10.8. Таблица 10.8. Свойства и события элемента управления Listview События и свойства ListView Описание, делегат и аргументы события Общие свойства Activation Определяет способ активизации пользователем пункта. Данное свойство принимает значе- ние в перечислении itemActivation. Возможные значения — OneClick (активизация од- ним щелчком), TwoClick (активизация двойным щелчком; при выборе цвет пункта меняется) и Standard (активизация двойным щелчком) CheckBoxes Указывает на появление пункта в позициях списка. True отображает кнопки номеров списка. Значение по умолчанию — False LargeImageList Указывает используемый набор изображений ImageList при отображении крупных пикто- грамм Items Возвращает коллекцию Listviewitems в элемент управления MultiSelect Определяет допустимость множественной выборки. Значение по умолчанию — True’ мно- жественная выборка допускается Seiecteditems Перечисляет коллекцию выбранных в данный момент пунктов SmallimageList Задает ImageList при отображении мелких пиктограмм View Определяет внешний вид Listviewitems. Значения Largelcon (показана крупная пикто- грамма, позиции расположены в нескольких столбцах), smalllcon (показана мелкая пикто- грамма), List (показана мелкая пиктограмма; позиции расположены в одном столбце) и Details (то же, что List, но для каждой позиции информация может быть расположена в нескольких столбцах) Общее событие (Делегат EventHandler, аргументы события EventArgs) ItemActivate Генерируется при активизации пункта в Listview. Не указывает, какой именно пункт акти- визирован Listview позволяет определить изображения, используемые в качестве пиктограмм для пунктов Listview. Для отображения изображений необходимо воспользоваться компонентом ImageList. Создайте его перетягиванием на форму из панели Toolbox. После этого щелкните кнопкой мыши в коллекции images в окне Properties для отображения редактора набора изображений Image Collection Editor (рис. 10.15). Здесь разработчик может про- листать и просмотреть изображения, которые он бы хотел добавить в ImageList, содержащий массив изображе- ний. После определения нужных изображений установите свойство SmallimageList класса Listview на новый объект ImageList. Свойство SmallimageList задает список изображений для мелких пиктограмм. Свойство LargelmageList задает ImageList для крупных пиктограмм. Пиктограммы для пунктов Listview выбираются установкой свойства imageindex на номер соответствующего элемента массива. Класс ListviewTest (листинг 10.7) отображает файлы и каталоги в Listview вместе с мелкими пиктограммами, обозначающими каждый файл или каталог. Если доступ к файлу или каталогу закрыт из-за настроек разреше- ний, то появляется окно сообщения. Программа сканирует содержимое каталога вместо индексирования цели- ком всего диска.
Концепции графического пользовательского интерфейса: часть 2 323 Рис. 10.15. Редактор Image Collection Editor для компонента ImageList Листинг 10.7. Элемент управления ListView, отображающий файлы и папки 1 //.Листинг 10.7: ListViewTest.es’ 2 // Отображение каталогов и их содержимого в Listview 3 4 using System; 5 using System.Drawing; 6 using System.Collections; 7 using System.ComponentModel; 8 using System.Windows.Forms; 9 using System.Data; 10 using System.10 11 12 public class ListViewTest : System.Windows.Forms.Form 13 { 14 // отображение меток для текущего местоположения 15 //в дереве каталогов 16 private System.Windows.Forms.Label currentLabel; 17 private System.Windows.Forms.Label displayLabel; 18 19 // отображение содержимого текущего каталога 20 private System.Windows.Forms.ListView browserListView; 21 22 // задание изображений для пиктограмм файла и папки 23 private System.Windows.Forms.ImageList fileFolder; 24 25 // получение текущего каталога 26 string currentDirectory = 27 Directory.GetCurrentDirectory(); 28 । 29 [STAThread] 30 static void Main() 31 { 32 Application.Run (new ListViewTest()); 33 } 34 35 // просмотр каталога, на котором пользователь щелкнул кнопкой 35а // мыши, или переход на один уровень вверх 36 private void browserListView_Click( 37 object sender, System.EventArgs e) 38 { 39 // подтверждение выбора пункта 40 if (browserListView.Selectedltems.Count != 0)
324 Глава 10 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 { // если выбран первый пункт, перейти на один уровень вверх if (browserListView.Items[0].Selected) { // создание для каталога объекта Directoryinfo Directoryinfo directoryobject = new Directoryinfo (currentDirectory); // если каталог имеет "родителя", загрузить его if (directoryobject.Parent != null) LoadFilesInDirectory ( directoryobject.Parent.FullName); ) // выбранный каталог или файл else ( > 11 выбранный каталог или файл string chosen = browserListView.Selectedltems[0].Text; // если выбранный пункт — каталог if (Directory.Exists(currentDirectory + "\\" + chosen)) { // загрузка подкаталога // если в "С:\", то "\" не нужно // иначе выполняется if (currentDirectory. == "С:\\") Load FilesInDirectory( currentDirectory + "\\" + chosen); • else Load FilesInDirectory ( currentDirectory + "W" + chosen) ; } // конец if } // конец else // обновление displayLabel displayLabel.text = currentDirectory; ) // конец if } // конец метода browserListView_Click // отображение файлов/подкаталогов текущего каталога public void LiadFilesInDirectory( string currentDirectoryvalue) { // загрузка и отображение информации о каталоге try { // сброс Listview и установка первого пункта browserListView.Tiems.Clear(); browserListView.Tiems.Add("Go Ip One Level"); // обновление текущего каталога currentDirectory - currentDirectoryvalue; Directoryinfo newCurrentDirectory = new Directoryinfo(currentDirectory); // размещение файлов и каталогов в массивах Directoryinfo[] directoryArray = newCurrentDirectory.GetDirectories();
Концепции графического пользовательского интерфейса: часть 2 325 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 14-6 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 } Fileinfo[] fileArray = newCurrentDirectory.GetFiles(); // добавление имени каталога в ListView f©reach (Directoryinfo dir in directoryArray) { // добавление каталога в Listview Listviewitem newDirectoryltem browserListView.Items.Add (dir.Name); // установка пиктограммы каталога newDirectoryltem.Imageindex = 0; } // добавление имен файлов в ListView foreach (Fileinfo file in fileArray) { // добавление файла в Listview Listviewitem newFileltem = browserListView.Items .Add(file.Name) ; newFileltem. Image Index = 1;// установка пиктограммы файла . ) } // конец try II доступ запрещен catch (UnauthorizedAccessException exception) { MessageBox.Show( "Warning: Some fields may not be " + "visible due to permission settings", "Attention", 0, MessageBoxIcon.Warning); ) } // конец метода LoadFilesInDirectory 11 обработка события загрузки при первом отображении формы private void ListViewTest_Load( object sender, System.EventArgs e) { // установка списка изображений Image folderImage = Image.FromFile( currentDirectory + WimagesWfolder.bmp); Image fileimage = Image.FromFile (currentDirectory + \\imagesWfile.bmp); ’ fileFolder.Images.Add(folderlmage); fileFolder.Images.Add(fileimage); // загрузка -текущего каталога в browserListView LoadFilesInDirectory(currentDirectory); displayLabel.Text = currentDirectory; } // конец метода ListViewTest_Load // конец класса ListViewTest Для отображения пиктограмм рядом с позициями (пунктами) списка необходимо создать ImageList для browserListView (строка 20) класса Listview. Сначала перетяните и опустите ImageList на форму и откройте редактор Image Collection Editor. Создайте два простых растровых изображения: одно для папки (номер элемен- та массива— 0), а другой— для файла (номер элемента массива— 1). Затем установите свойство SmallimageList объекта browserListView в новый ImageList в окне Properties. Разработчики могут создавать
326 Глава 10 такие пиктограммы в любой программе обработки графики, например, в Adobe Photoshop, Jasc PaintShop Pro или Microsoft Paint. Метод LoadFileslnDirectory (строки 87—140) используется для заполнения browserListView переданным ему каталогом (currentDirectoryValue). Он сбрасывает всю информацию в browserListView и добавляет элемент "Go Up One Level". При щелчке пользователем кнопкой мыши на этом элементе программа делает попытку перемещения на один уровень вверх (далее будет рассмотрено — каким образом). Затем метод создает объект Directoryinfo, инициализированный со строкой currentDurectory (строки 99—100). Если нет разрешения на просмотр каталога, то выдается исключение (захватывается в строках 132—138). Метод LoadFileslnDirectory работает не так, как метод PopulateTreeView в предыдущей программе (см. листинг 10.6). Вместо загрузки всех папок на жесткий диск метод LoadFileslnDirectory загружает папки только в текущий каталог. Класс Directoryinfo (пространство имен System, ю) дает возможность просмотра структуры каталогов или ма- нипуляций ею. Метод GetDirectories (строки 103 и 104) возвращает массив объектов Directoryinfo, содержа- щий подкаталоги текущего каталога. Точно так же метод GetFiles (строки 106 и 107) возвращает массив объек- тов класса Fileinfo, содержащий файлы в текущем каталоге. Свойство Name (классов Directoryinfo и Fileinfo) содержит только имя каталога или файла, например, temp, вместо C:\myfolder\temp. Для получения полного имени следует воспользоваться свойством FuiiName. Строки ПО—118 и 121—128 итерируют через подкаталоги и файлы текущего каталога и добавляют их в browserListView. В строках 117 и 127 задаются свойства imageindex вновь созданных позиций. Если позиция является каталогом, то его пиктограмма помещается в пиктограмму каталога (номер 0); если позиция — файл, тогда его пиктограмма помещается в пиктограмму файла (номер 1). Метод browserListview ciick (строки 36—84) срабатывает, когда пользователь щелкает кнопкой мыши на элементе управления browserListView. В строке 40 выполняется проверка на предмет выбора позиций. Если выбор сделан, то в строке 43 определяется, выбрал ли пользователь первый пункт в browserListView. Первый пункт в browserListView всегда Go up one level; если он выбран, то программа делает попытку перехода на один уровень вверх. В строках 46 и 47 для текущего каталога создается объект Directoryinfo. В строке 50 осу- ществляется тестирование свойства Parent, чтобы пользователь не оказался в корне дерева каталогов. Свойство Parent указывает в качестве объекта родительский каталог Directoryinfo; если он не существует, тогда Parent возвращает значение null. Если родительский каталог существует, тогда строки 51 и 52 передают полное имя родительского каталога в метод LoadFileslnDirectory. Если пользователь не выбрал первый пункт в browserListView, тогда строки 56 и 57 позволяют ему продолжать просмотр структуры каталогов. В строках 59 и 60 создается строка chosen, принимающая текст выбранного пункта (первого пункта в коллекции Selecteditems). В строках 63 и 64 проверяется, что пользователь выбрал Рис. 10.16. Демонстрация элемента управления Listview: а — отображены только каталоги; б — отображены каталоги и файлы; в — сообщение о невозможности просмотреть содержимое выбранного каталога
Концепции графического пользовательского интерфейса: часть 2 327 существующий каталог (не файл). Программа объединяет переменные currentDirectory и chosen (новый ката- лог), разделенные косой чертой (\), и передает это значение в метод Exists класса Directory. Метод Exists возвращает True, если его параметр string является каталогом. В этом случае программа передает string в ме- тод LoadFilesInDirectory. Каталог С:\ уже имеет косую черту, поэтому она не нужна при комбинировании currentDirectory и chosen (строка 71). Однако другие каталоги могут включать косую черту (строки 73 и 74). Наконец, displayLabel обновляется новым каталогом (строка 80). Данная программа загружается быстро, потому что она указывает только файлы в текущем каталоге. Это озна- чает, что, вместо продолжительной задержки с самого начала исполнения, возникают кратковременные задерж- ки всякий раз при загрузке нового каталога. Кроме того, изменения структуры каталогов можно показать пере- загрузкой каталога. Предыдущую программу (см. листинг 10.6) следует перезагрузить для отражения всех из- менений в структуре каталогов. Такой вид компромисса— обычное явление в компьютерном мире. При проектировании программных приложений, работающих долгое время, для повышения их производительности разработчики должны выбирать продолжительную задержку в начале исполнения программы. Однако при раз- работке программ, исполняющихся короткое время, программисты часто выбирают короткое время начальной загрузки с небольшими задержками после каждой операции. Результат работы программы представлен на рис. 10.16. 10.8. Элемент управления TabControl Элемент управления Tabcontrol создает окна с вкладками, подобные тем, что представлены в IDE Visual Studio .NET (рис. 10.17). Это дает программисту возможность проектировать интерфейсы, в которых можно уместить очень большое количество различных элементов управления или данных, не затрагивая ценное свободное про- странство экрана. Вкладки Рис. 10.17. Страницы с вкладками в Visual Studio .NET Элемент управления TabControl содержит объекты TabPage, похожие на панели (Panels) и группы (GroupBoxes) в том, что они также могут содержать в себе элементы управления. Сначала программист добавляет управляю- щие элементы в объекты TabPage, после чего добавляет TabPage в TabControl. Одновременно может быть пока- зана только одна страница с вкладками. На рис. 10.18 показан пример окна с TabControl. Программисты могут добавлять TabControl визуально путем их перетягивания и размещения на форме в режи- ме проектирования. Для добавления TabPage щелкните правой кнопкой мыши на TabControl и выберите коман- ду Add Tab (рис. 10.19). Либо, можно щелкнуть кнопкой мыши в коллекции TabPage в окне Properties и доба- вить вкладки в открывающемся диалоговом окне. Для изменения метки задайте свойство Text объекту TabPage. Обратите внимание, что при щелчке по вкладке выбирается Tabcontrol; для выбора TabPage щелкните на управляющем поле под вкладкой. Разработчик может добавить управляющие элементы в TabPage путем перетя-
328 Глава 10 гивания элементов из панели Toolbox. Для просмотра различных страниц TabPage щелкните кнопкой мыши на* соответствующей вкладке (в режиме проектирования или выполнения). Общие свойства и события TabControl представлены в табл. 10.9. TabPage Элементы управления в TabPage Рис. 10.18. Элемент управления TabControl с примером TabPage Рис. 10.19. Объекты TabPage, добавленные в TabControl Таблица 10.9. Свойства и события элемента управления TabControl События и свойства TabControl Описание, делегат и аргументы события Общие свойства ImageList Задает изображения для отображения на вкладке ItemSize Устанавливает размер вкладки MultilLine Указывает на возможность отображения нескольких рядов вкладок Selectedlndex Обозначение указателя текущей выбранной страницы TabPage SelectedTab Указывает текущую выбранную страницу TabPage TabCount Возвращает количество вкладок TabPages Получает коллекцию страниц в рамках TabControl Общее событие (Делегат EventHandler, аргументы события EventArgs) , SelectedindexChanged Генерируется при изменении Selectedlndex (т. е выбирается другая страница TabPage) Каждая страница TabPage генерирует собственное событие Click, когда по ее вкладке выполняется щелчок. Помните, что события элементов управления можно обрабатывать любым обработчиком, зарегистрированным в делегате событий данного элемента управления. Это также применяется к элементам управления, содержащим- ся в TabPage. Для удобства Visual Studio .NET генерирует пустые обработчики событий для таких элементов управления в классе, с которым в данный момент осуществляется работа. Класс UsingTabs (листинг 10.8) использует Tabcontrol для отображения различных опций, имеющих отношение к тексту метки (Color, Size и Message). Последняя страница TabPage отображает сообщение About, а котором описывается использование Telecontrols.
329 Концепции графического пользовательского интерфейса: часть 2 • - .................. Листинг 10.8. Элемент управления TabControl, использованный для отображения различных настроек шрифта - 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 // Листинг 10.8: UsingTabs.cs // Использование TabControl для отображения настроек шрифта using System; using System.Drawing; using System.Collections; using System.ComponentModel; using System.Windows.Forms; using System.Data; public class UsingTabs : System.Windows.Forms.Form { // метка отражает изменения текста private System.Windows.Forms.Label displayLabel; // элемент управления, содержащий страницы TabPage // colorTabPage, sizeTabPage, messageTabPage и aboutTabPage private System.Windows.Forms.TabControl1 optionsTabControl; // страница TabPage, содержащая опции цветов private System.Windows.Forms.TabPage colorTabPage; private System.Windows.Forms.RadioButton greenRadioButton; private System.Windows.Forms.RadioButton redRadioButton; private System.Windows.Forms.RadioButton blackRadioButton; // страница TabPage, содержащая опции гарнитур шрифтов private System.Windows.Forms.TabPage sizeTabPage; private System.Windows.Forms.RadioButton size20RadioButton; private System.Windows.Forms.RadioButton s i zel6RadioButton; private Systenr.Windows. Forms. RadioButton sizel2RadioButton; // страница TabPage, содержащая опции отображения текста private System.Windows.Forms.TabPage messageTabPage; private System.Windows.Forms.RadioButton doodbyeRadioBut ton; private System.Windows.Forms.RadioButton helloRadioButton; // страница TabPage, содержащая сообщение "about" private System.Windows.Forms.TabPage aboutTabPage; private System.Windows.Forms.Label messageLabel; [STAThread] static void Main() { Application.Run(new Usingtabs()); } // обработчик событий переключателя Black private void blackRadioButton_CheckedChanged( object sender. System.EventArgs e) { displayLabel.ForeColor = Color.Black; }
330 Глава 10 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 11 обработчик событий переключателя Red private void redRadioButton_CheckedChanged( object sender, System.EventArgs e) { displayLabel.ForeColor = Color.Red; 1 11 обработчик событий переключателя Green private void greenRadioButton_CheckedChanged( object sender, System.EventArgs e) { displayLabel.ForeColor = Color.Green; } // обработчик событий переключателя 12 point private void sizel2RadioButton_CheckedChanged( object sender, System.EventArgs e) { displayLabel.Font = new Font (displayLabel.Font.Name, 12); 1 // обработчик событий переключателя 16 point private void sizel6RadioButton_CheckedChanged( object sender, System.EventArgs e) { displayLabel.Font new Font (displayLabel.Font.Name, 16); } // обработчик событий переключателя 20 point private void size20RadioButton_CheckedChanged( object sender, System.EventArgs e) { displayLabel.Font = new Font (displayLabel.Font.Name, 20); } // обработчик событий переключателя сообщения "Hello!" private void helloRadioButton_CheckedChanged( object sender, System.EventArgs e) { displayLabel.Text = "Hello!"; } /7 обработчик событий переключателя сообщения "Goodbye!" private void goodByeRadioButton_CheckedChanged( object sender, System.EventArgs e) { displayLabel.Text « "Goodbye!"; } } // конец класса UsingTabe Замечание по технологии программирования______________________________________________ TabPage может выполнять роль контейнера для одной логической группы переключателей и обеспечивает их взаимное исключение Для размещения нескольких групп переключателей в рамках одной страницы TabPage программист должен объединить переключатели панелях (Panels) или группах (checkBox), имеющихся в TabPage. Принадлежащий классу TabControl объект optionsTabControl (строки 18 и 19) и классам TabPage объекты colorTabPage (строка 22), sizeTabPage (строка 30), messageTabPage (строка 39) и aboutTabPage (строка 46) соз- даются в режиме проектирования (как описывалось ранее). Объект colorTabPage содержит три переключателя
Концепции графического пользовательского интерфейса: часть 2 331 для цветов — черного (blackRadioButton, строки 26 и 27), красного (redRadioButton, строка 25) и зеленого (greenRadioButton, строки 23 и 24). Обработчик события CheckChanged для каждой кнопки обрабатывает цвет текста в displayLabel (строки 59, 66 и 73). Объект SizeTabPage класса TabPage имеет три переключателя, соот- ветствующие размерам гарнитур шрифта,— 12 (sizel2RadioButton, строки 35 и 36), 16 (sizel6RadioButton, строки 33 и 34) и 20 (size20RadioButton, строки 31 И 32), которые изменяют размер шрифта displayLabel в строках 80—81, 88—89 и 96—97, соответственно. Объект messageTabPage класса TabPage имеет два переключа- теля: для сообщений "Hello!” (helloRadioButton, строки 42 и 43) и "Goodbye!" (goodbyeRadi©Button, строки 40 и 41). Эти два переключателя определяют текст в объекте displayLabel (строки 104 и 111, соответственно). Последняя страница TabPage (aboutTabPage, строка 46) содержит метку (messageLabel, строка 47), описываю- щую предназначение элементов управления страницами с вкладками. Результат работы программы представлен на рис. 10.20. Рис. 10.20. Демонстрация элемента управления TabControl: а — первая вкладка; б— вторая вкладка; в — третья вкладка; а — четвертая вкладка 10.9. Многодокументный интерфейс Windows В предыдущих главах рассматривалось построение только приложений с однодокументным интерфейсом (sin- gle document interface, SDI). Такие программы (в их число входят Notepad или Paint) поддерживают лишь одно открытое окно или документ. Как правило, возможности однодокументных приложений ограничены: например, Notepad и Paint имеют ограниченные возможности редактирования графики и текстов. Для редактирования нескольких документов пользователь должен создавать дополнительные экземпляры однодокументного приложения. Программы с многодокументным интерфейсом (multiple document interface, MDI), такие как PaintShop Pro и Adobe Photoshop, предоставляют пользователям возможность одновременного редактирования нескольких до- кументов. MDI-программы — более сложные; так, PaintShop Pro и Photoshop обладают гораздо большими воз- можностями редактирования графики, нежели Paint. До сих пор авторы не отмечали, что все созданные в книге программы являются однодокументными. В данном разделе различия между этими двумя типами программ рассматриваются подробно. Окно приложения многодокументной программы называется родительским, а все окна в приложении — окна- ми-потомками (рис. 10.21). Несмотря на то, что многодокументное приложение может иметь множество окон- потомков, каждое из них имеет только одно родительское окно. Более того, активным в каждый конкретный момент времени может быть только одно окно-потомок. Последние не могут быть "родителями", и их нельзя переместить за пределы родительского окна. В противном случае, окно-потомок ведет себя как любое другое
332 Глава 10 окно (в том, что касается закрытия, свертывания, изменения размеров и т. д.). Функциональность одного окна- потомка может отличаться от функциональности других окон-потомков родительского окна. Например, в одном окне-потомке можно редактировать графические изображения, в другом — редактировать текст, а в третьем может графически отображаться сетевой трафик, но при этом все эти окна-потомки могут принадлежать к од- ному многодокументному "родителю". Для создания многодокументной формы добавим новую форму (Form) и присвоим ее свойству isMDicontainer значение True. Внешний вид формы изменяется (рис. 10.22). Рис. 10.21. Многодокументное родительское окно и многодокументные окна-потомки Рис. 10.22. Формы: а — SDI; б — MDI После этого создается класс формы-потомка для добавления в основную форму. Для этого щелкните правой кнопкой мыши на проекте в окне Solution Explorer, выберите команду Add Windows Form... и присвойте фай- лу имя. Для добавления в родительскую форму "потомка" необходимо создать объект новой формы-потомка; установите ее свойство MdiParent в родительскую форму и вызовите метод show. Код для создания потомка обычно расположен внутри обработчика событий, создающего новое окно в ответ на действие пользователя. Выбор меню (например, File, в котором выбирается команда New, за которой следует команда Window) — об- щий метод создания новых окон-потомков. Свойство MdiChildren формы представляет собой массив ссылок на Form-потомка. Это полезно, если родитель- ское окно задумает проверить состояние всех своих "детей" (например, сохранена ли в них информация перед закрытием "родителя"). Свойство ActiveMdiChild возвращает ссылку на активное окно-потомка; оно возвраща- ет null, если активных окон-потомков нет. Другие особенности многодокументных окон представлены в табл. 10.10. Таблица 10.10. События и свойства многодокументной родительской формы и многодокументной формы-потомка События и свойства MDI-формы Описание, делегат и аргументы события Общие свойства MDI-потомка IsMdiChild Указывает, является ли форма MDI-потомком. При значении True, форма — MDI-потомок (свойство только для чтения) MdiParent Задает MDI форму-потомка Общие свойства MDI-родителя Act iveMdiChiId Возвращает объект Form, являющийся текущим активным MDI-потомком (возвращает null, если активных потомков нет) IsMdiContainer Указывает, может ли форма быть многодокументной. При значении True форма может быть многодокументной родительской формой Значение по умолчанию — False MdiChildren Возвращает MDI-потомков в виде массива форм Общий метод LayoutMdi Определяет отображение форм-потомков на многодокументной родительской форме. В качестве параметра принимает перечисление MdiLayout с возможными значениями Arrangelcons (упорядочить значки), Cascade (каскадом), TileHorizontal (упорядочить по горизонтали) и Tilevertical (упорядочить по вертикали)
Концепции графического пользовательского интерфейса: часть 2 333 Таблица 10.10 (окончание) События и свойства MDI-формы Описание, делегат и аргументы события Общее событие (Делегат EventHandler, аргументы события EventArgs) MdiChiIdAct ivatе Генерируется при закрытии или активизации MDI-потомка Окна-потомки можно сворачивать, разворачивать и закрывать независимо друг от друга и от родительского ок- на. На рис. 10.23, а изображены два свернутых окна-потомка, а на рис. 10.23, б— развернутое окно-потомок. При сворачивании или закрытии родительского окна окна-потомки также сворачиваются или закрываются. Об- ратите внимание, что строка заголовка на рис. 10.23, б— Parent Window - (Child]. При свертывании или раз- вертывании окна-потомка в строке его заголовка отображается пиктограмма восстановления, возвращающая окно-потомок в предыдущее состояние (к тем же размерам, что окно имело до свертывания или развертывания). Кнопки родительского окна Кнопки окна-потомка Кнопки развернутого окна-потомка . Заголовок родительского окна сообщает о развернутом окне-потомке б Рис. 10.23. Окна-потомки а — свернутые; б — развернутые Родительская форма и форма-потомок могут иметь разные меню, которые объединяются всякий раз при активи- зации окна-потомка. Для задания порядка объединения меню программисты могут задать свойства MergeOrder и MergeType для каждого Menuitem (см. табл. 10.1). MergeOrder определяет порядок, В котором ПОЯВЛЯЮТСЯ пункты меню (Menuitem), когда два меню объединяются. Пункты меню с более низким значением MergeOrder появляют- ся первыми. Например, если Menul имеет пункты File, Edit и Window (и их порядки — 0, 10 и 20), a Menu2 имеет пункты Format и View (с порядками 7 и 15), тогда объединенное меню будет содержать пункты в порядке File, Format, Edit, View и Window. Каждый экземпляр Menuitem имеет собственное свойство MergeOrder. Может случиться так, что в какой-либо точке программного приложения объединятся два Menuitem с одинаковым значением MergeOrder. Свойство MergeType разрешает такой конфликт следованием порядку, в котором отображены два меню. Свойство MergeType принимает значение перечисления MenuMerge и определяет, какие пункты меню будут ото- бражаться при слиянии двух меню. Пункт меню со значением Add добавляется в панель родительского меню как новый (сначала идут пункты родительского меню). Если меню формы-потомка имеет значение Replace, то во время слияния оно сделает попытку занять место соответствующего меню родительской формы. Меню со зна- чением Mergeitems объединяет свои пункты с пунктами соответствующего родительского меню (если родитель- ское меню и меню-потомок изначально занимают одинаковое пространство, то их подменю будут объединены в одно большое меню). Пункт меню-потомка со значением Remove исчезает, когда это меню объединяется с таким же родительским меню. Значение Mergeitems действует пассивно: если родительское меню имеет MergeType, отличающийся от MergeType меню-потомка, тогда настройка меню-потомка определяет результат слияния. Когда окно-потомок закрывается, первоначальное меню родительского меню восстанавливается. О хорошем стиле программирования_________________________________________________________ При создании многодокументных программных приложений включайте пункт меню со свойством MdiList, уста- новленным на True. Этим обеспечится быстрый выбор окна-потомка без необходимости его поиска в родитель- ском окне.
334 Глава 10 Замечание по технологии программирования_______________________________________________ Установите свойство MergeType пунктов родительского меню на значение Mergeitems. Это позволит добавить в окно-потомок большую часть пунктов меню, в соответствии с его настройками. Пункты родительского меню, ко- торые должны остаться, обязаны иметь значение Add, а те, которые следует убрать, должны иметь значение Remove. В C# имеется свойство, упрощающее отслеживание, какие из окон-потомков открыты в контейнере MDI. Свой- ство MdiList класса Menuitem определяет, отображает ли Menuitem список открытых окон-потомков. Этот спи- сок появляется в нижней части меню после разделительной черты (первое изображение на рис. 10.24). Когда открывается новое окно-потомок, в список добавляется запись. Если открыто девять или более окон-потомков, тогда список включает опцию More Windows..., дающую пользователю возможность выбрать окно из списка с помощью полосы прокрутки. Множественные пункты меню Menuitems могут иметь свой набор свойств MdiList; каждый отображает список открытых окон-потомков. Рис. 10.24. Пример свойства MdiList класса Menuitem Контейнеры MDI позволяют разработчикам упорядочить расположение окон-потомков. Окна-потомки MDI- приложении можно упорядочить вызовом метода LayoutMdi родительской формы. Этот метод принимает пере- числение MdiLayout, которое может иметь значения Arrangelcons, Cascade, TileHorizontal и Tilevertical. Упорядоченные окна полностью заполняют родительское окно, не накладываясь друг на друга; такие окна мож- но упорядочить по горизонтали (значение TileHorizontal) или по вертикали (значение Tilevertical). Каскад- ные окна (значение Cascade) "наползают" друг на друга; все такие окна имеют одинаковые размеры, и, по воз- можности, у всех видны строки заголовков. Значение Arrangelcons упорядочивает пиктограммы для любых свернутых окон-потомков. Если свернутые окна разбросаны по родительскому окну, то значение Arrangelcons аккуратно располагает их в левом нижнем углу родительского окна. На рис. 10.25 проиллюстрированы значения перечисления MdiLayout. Класс UsingMDi (листинг 10.9) демонстрирует работу с многодокументными окнами. Класс usingMDi использует три экземпляра класса child (листинг 10.10), каждый из которых содержит PictureBox (рамку изображения) и изображение обложки книги. Родительская многодокументная форма имеет меню, дающее пользователям воз- можность создания и упорядочения форм-потомков. В родительской многодокументной форме (листинг 10.9) содержатся два меню верхнего уровня. Первое из этих меню— File (fileMenultem, строка 13)— содержит подменю Exit (exitMenultem, строка 18) и New (newMenuitem, строка 14), состоящие из пунктов для каждого окна-потомка. Второе меню — Format (formatMenuitem, строка 19) — предоставляет опции для упорядочения MDI-потомков, а также список активных MDI-потомков.
Концепции графического пользовательского интерфейса: часть 2 335 б Рис. 10.25. Значения перечисления MdiLayout: а — Arrangelcons; б— Cascade; в — TileHorizontal; г — Tilevertical В окне Properties свойству isMdiContainer формы присваивается значение True, что делает форму многодоку- ментным родителем. Кроме того, свойству MdiList меню formatMenultem также присваивается значение True. Это позволяет formatMenultem составить список активных многодокументных окон-потомков. • ' ---- . ............ • -• vv.-:.- - . ; Листинг 10.9. Класс многодок. ментных одительских окон и окон-потомков (Предоставлено Prentice Hall, Inc.) : .. . .. - ..............................- 1 // Листинг 10.9: UsingMDI.cs 2 // Демонстрация многодокументных родительских окон и окон-потомков 3 using System; 4 using System.Drawing; 5 using System.Collections; 6 using System.ComponentModel; 7 using System.Windows.Forms; 8 using System.Data; 9 10 public class UsingMDI : System.Windows.Forms.Form 11 { 12 private System.Windows.Forms.MainMenu mainMenul; 13 private System.Windows.Forms.Menuitem fileMenuItem; 14 private System.Windows.Forms.Menuitem newMenuItem; 15 private System.Windows.Forms.Menuitem childlMenuItem; 16 private System.Windows.Forms.Menuitem child2MenuItern; 17 private System.Windows.Forms.Menuitem child3MenuItern; 18 private System.Windows.Forms.Menuitem exitMenultem; 19 private System.Windows.Forms.Menuitem formatMenultem; 20 private System.Windows.Forms.Menuitem cascadeMenuItem; 21 private System.Windows.Forms.Menuitem 22 tileHorizontalMenuItem; 23 private System.Windows.Forms.Menuitem 24 tileVerticalMenuItem; 25
336 Глава 10 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 •78 79 80 81 82 83 84 85 86 87 88 [STAThread] static void Main() { Application.Run (new UsingMDI()); } // создание Child 1 при щелчке кнопкой мыши на меню private void childlMenu!tem_Click( object sender, System.EventArgs e) { // создание нового потомка Child formChild = new Child("Child 1", "WimagesWcsharphtpl.jpg); formChild.MdiParent = this; // установка родителя formChild.Show(); // отображение потомка 1 // создание Child 2 «при щелчке кнопкой мыши на меню private void child2Menu!tem_Click( object sender, System.EventArgs e) { // создание нового потомка Child. formChild = new Child ("Child 2", "WimagesWvbnethtp2.jpg) ; formChild.MdiParent « this; // установка родителя formChild.Show(); // отображение потомка } // создание Child 3 при щелчке кнопкой мыши на меню private void child3MenuItem_Click( object sender, System.EventArgs e) { // создание нового потомка Child formChild « new Child("Child 3", "WimagesWpythonhtpl. jpg); formChiId.MdiParent = this; // установка родителя formChild.Show(); // отображение потомка } // выход из приложения private void exitMenu!tem_Click( object sender, System.EventArgs e) { Application.Exit(); ) // установить топологию каскадного упорядочения private void cascadeMenuItem_Click( object sender, System.EventArgs e) { this.LayoutMdi (MdiLayout.Cascade); ) // установка упорядочения TileHorizontal private void tileHorizontalMenu!tem_Click( object sender, System.EventArgs e) { this.LayoutMdi (MdiLayout.TileHorizontal); } // установка упорядочения TileVertical private void tileVerticalMenu!tem_Click( object sender, System.EventArgs e)
Концепции графического пользовательского интерфейса: часть 2 337 89 { ' 90 this.LayoutMdi (MdiLayout.TileVertical); 91 } , 92 93 } // конец класса UsingMDI Команда меню Cascade (cascadeMenultem, строка 20) имеет обработчик событий (cascadeMenultem, строки 73— 77), упорядочивающий окна-потомки каскадом. Обработчик событий вызывает метод LayoutMdi с аргументом Cascade из перечисления MdiLayout (строка 76). Команда меню Tile Horizontal (tileHorizontalMenuitem, строки 21 и 22) имеет обработчик события (mnuitmTileHorizontal ciick, строки 80—84), упорядочивающий окна-потомки по горизонтали. Обработчик события вызывает метод LayoutMdi с аргументом TileHorizontal из перечисления MdiLayout (строка 83). Наконец, команда меню Tile Vertical (mnuitmTileVertical, строки 23 и 24) имеет обработчик события (mnuitmTileVerticaljzlick, строки 87—91), упорядочивающий окна-потомки по вертикали. Обработчик собы- тия вызывает метод LayoutMdi с аргументом Tilevertical из перечисления MdiLayout (строка 90). Для определения класса-потомка для MDI-приложения щелкните правой кнопкой на проекте в окне Solution Explorer и выберите сначала команду Add, а потом команду Add Windows Form.... Присвойте новому классу имя Child (листинг 10.10). Затем добавим PictureBox (picDisplay, строка 11) для формирования child. Конструктор активизирует метод InitializeComponent (строка 17) и инициализирует заголовок формы (строка 19) и изображение, которое будет показано в PictureBox (строки 22 и 23). Родительская многодокументная форма (см. листинг 10.9) создает новые экземпляры класса Child каждый раз при выборе пользователем нового окна-потомка в меню File. Обработчики событий в строках 33—63 создают новые формы-потомки, содержащие изображения обложек книг Deitel and Associates, Inc. Каждый обработчик событий создает новый экземпляр формы-потомка, устанавливает ее свойство MdiParent на родительскую фор- му и вызывает метод show для отображения потомка. 1 // Листинг 10.10: Child.cs 2 // Окно-потомок,MDI-родителя 3 using System; 4 using System.Drawing; 5 using System.Collections; 6 using System.ComponentModel; 7 using System. Windows.Forms; 8 using System. IO; 9 10 public class Child : System.Windows.Forms.Form 11 ( 12 private System.Windows.Forms.PictureBox pictureBox; 13 14 public Child(string title, string fiieName) 15 { 16 // требуется для поддержки Windows Form Designer 17 InitializeComponent(); 18 19 Text = title; // ввод текста заголовка 20 21 // ввод изображения для показа в PictureBox 22 pictureBox.Image = Image.FromFile( 23 Directory.GetCurrentDirectory() + filename); 24 } 25 } * Результат работы приложения представлен на рис. 10.26. 22 Зак. 3333
338 Глава 10 Не РоппЛ i WbMnijMDJ Рис. 10.26. Демонстрация MDI-приложения: а — меню в родительской форме; б — первое окно-потомок; в — выбор команды Cascade; г — расположение окон каскадом а 10.10. Визуальное наследование В главе 6 рассматривалось создание классов путем наследования из других классов. В C# также можно исполь- зовать механизмы наследования для создания форм, отображающих GUI, потому что формы являются класса- ми, производными от класса System.Windows.Forms.Form. Визуальное наследование позволяет создать новую форму путем наследования из другой формы. Производный класс Form содержит функциональность базового класса Form, включая любые свойства, методы, переменные и управляющие элементы базового класса. Произ- водный класс также наследует от базового класса все визуальные аспекты: размеры, расположение компонен- тов, расстояния между компонентами GUI, цвета и шрифты. Визуальное наследование позволяет разработчикам достичь визуальной согласованности программных прило- жений через многократное использование кода. Например, компания может определить базовую форму, содер- жащую логотип продукта, неизменный цвет фона, предопределенную панель меню и другие элементы. После этого программисты могут пользоваться базовой формой на всем протяжении разработки приложения в целях соблюдения единообразия и поддержания неизменной торговой марки продукта. Класс Visualinheritance (листинг 10.11) — форма, используемая в качестве базового класса для демонстрации визуального наследования. GUI содержит две метки (одну с текстом "Bugs, Bugs, Bugs", а другую — с текстом "Copyright 2002, by Bug2Bug.com") и одну кнопку Learn More. При нажатии пользователем этой кнопки акти- визируется метод learnMoreButton_Ciick (строки 22—29). Он отображает поле сообщения, в котором пред- ставлен информативный текст.
Концепции графического пользовательского интерфейса: часть 2 339 1 // Листинг 10.11: Visuallnheritance.cs 2 // Базовая форма для использования с визуальным наследованием 3 using System; 4 using System.Drawing; 5 using System.Collections; 6 using System.ComponentModel; 7 using System.Windows.Forms; 8 using System.Data; 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 public class Visuallnheritance : System.Windows.Forms.Form { private System.Windows.Forms.Label bugsLabel; private System.Windows.Forms.Button learnMoreButton; private Systern.Windows.Forms.Label labell; [STAThread] static void Main() { Application.Run,new Visuallnheritance()); ) private void learnMoreButton_Click(object sender, System.EventArgs e) { MessageBox.Show( "Bugs, Bugs, Bugs is a product of Bug2Bug.com", "Learn More", MessageBoxButtons.OK, MessageBoxIcon.Information) ’J } Рис. 10.27. Базовая форма в режиме проектирования Форма в режиме проектирования представлена на рис. 10.27. Перед извлечением формы из класса visuallnheritance необходимо упаковать класс visuallnheritance в dll-библиотеку. Щелкните правой кнопкой мыши на проекте visuallnheritance в окне Solution Explorer и выберите команду Properties. В Common Properties | General измените Output Туре на Class Library. Затем постройте проект для создания dll-библиотеки, содержащей класс Visuallnheritance. Для создания производной формы посредством визуального наследования создайте пустой проект. В меню Project выберите команду Add Inherited Form.... Откроется окно Add New Item. Выберите Inherited Form в области шаблонов. При нажатии кнопки Open отобразится Inheritance Picker. Данный инструмент позволяет программистам быстро создать форму, наследующую из какой-либо другой формы. Нажмите кнопку Browse и выберите dll-файл для класса visuallnheritance. Обычно dll-файл расположен в каталоге bin\Debug каталога проекта visuallnheritance. Нажмите кнопку ОК. Теперь дизайнер форм должен отобразить унаследованную форму (рис. 10.28). Класс VisualinheritanceTest (листинг 10.12) является производным от класса Visuallnheritance. GUI содер- жит компоненты, унаследованные из класса visuallnheritance, а также кнопку Learn The Program, добавлен- ную в класс VisualinheritanceTest. При нажатии пользователем этой кнопки активизируется метод learnProgramButton Click (строки 15—22). Он отображает простое окно сообщений. 1 // Листинг 10.12: VisualinheritanceTest.cs 2 // Производная форма, использующая визуальное наследование 3 using System; 4 using System.Collections; 5 using System.ComponentModel; 6 using System.Drawing; 7 using System.Windows.Forms; 8
340 Глава 10 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 public class VisuallnhentanceTest : Visuallnheritance. Visuallnheritance { private System.Windows.Forms.Button learnProgramButton; // активизация при нажатии кнопки Learn the Program private void learnProgramButton_Click(object sender, System.EventArgs e) { MessageBox.Show( "This program was created by Deitel & Associates", "Learn the Program", MesssageBoxButtons.OK, MessageBoxIcon.Information); ) public static void Main (string[] args) { Application.Run (new VisuallnheritanceTest()); ) Рис. 10.28. Визуальное наследование посредством дизайнера форм Производный класс не может изменять эти элементы а Производный класс может изменять этот элемент Рис. 10.29. Демонстрация класса VisuallnheritanceTest с дополнительной кнопкой в форме: а — вид формы; 6 — сообщение после нажатия кнопки Learn More; в — сообщение после нажатия кнопки Learn the Program в
Концепции графического пользовательского интерфейса: часть 2 341 На рис. 10.29 показано, что компоненты, их расположение и функциональность базового класса visual inheritance (см. рис. 10.27) унаследованы классом visualinheritanceTest. Если пользователь нажимает кнопку Learn More, обработчик событий базового класса learnMoreButton_ciick отображает окно сообщения MessageBox. 10.11. Элементы управления, определенные пользователем .NET Framework дает программистам возможность создания пользовательских элементов управления, насле- дующих из различных классов. Эти элементы управления появляются в панели Toolbox, и их можно добавлять в формы, панели и группы так же, как добавляются кнопки, метки и другие предопределенные элементы управле- ния. Самый простой способ создания специального элемента управления — извлечь класс из существующего элемента управления Windows Forms, например, из Label. Это полезно, если программист хочет включить функциональность существующего элемента управления, а не реализовывать его повторно в дополнение к включению новой функциональности. Например, можно создать новый тип метки, поведение которой не будет отличаться от поведения нормального элемента Label, но которая будет выглядеть по-другому. Это делается наследованием из класса Label и подменой метода OnPaint. Замечание о “впечатлениях и ощущениях"________________________________________________ Для изменения внешнего вида любого элемента управления подмените метод OnPaint. Все элементы управления содержат метод OnPaint, вызываемый системой, когда компонент необходимо пере- рисовать (например, при изменении его размера). Методу OnPaint передается объект PaintEventArgs, содержа- щий графическую информацию: свойство Graphics — это графический объект, используемый для рисования, а свойство ciipRectangle определяет прямоугольные границы элемента управления. Всякий раз при генерирова- нии системой события Paint базовый класс элемента управления захватывает его. Метод OnPaint элемента управления вызывается посредством полиморфизма. Реализация базового класса OnPaint не вызывается, поэто- му ее необходимо вызвать явно из реализации OnPaint до исполнения кода специального рисования. С другой стороны, если программисту не нужно, чтобы базовый класс самостоятельно выполнял рисование, то обращать- ся к реализации метода OnPaint не следует. Для создания нового элемента управления, состоящего из существующих элементов управления, используйте класс Usercontrol. Элементы управления, добавляемые в специальный элемент, называются составляющими элементами. Например, программист может создать usercontrol, состоящий из кнопки, метки и текстового поля, выполняющих те или иные функции (например, при нажатии кнопки текст метки будет изменяться на со- держащийся в текстовом поле). Usercontrol выполняет роль контейнера для добавляемых в него элементов управления. Usercontrol содержит составляющие элементы, поэтому не определяет их отображение. Метод OnPaint нельзя подменить в специальных элементах управления: их внешний вид можно менять только обра- боткой события Paint каждого составляющего элемента. Обработчику события Paint передается объект PaintEventArgs, который можно использовать для рисования геометрических фигур (линий, прямоугольников и т. д.) на составляющих элементах управления. С помощью другой методики программисты могут создавать абсолютно новые элементы управления путем на- следования из класса Control. Данный класс не определяет никакого особого поведения: эта задача ложится на плечи программиста. Вместо этого, класс Control управляет пунктами, связанными со всеми элементами управ- ления, например, событиями и установкой размеров. Метод OnPaint должен содержать обращение к методу OnPaint базового класса, вызывающему обработчики события Paint. После этого программист должен добавить в подмененный метод OnPaint код для специальной графики. Данная методика обеспечивает потрясающую гиб- кость, но требует тщательного планирования. Все три подхода в общем виде представлены в табл. 10.11. Таблица 10.11. Создание специального элемента управления Методики специальных * элементов управления и свойства PaintEventArgs Описание Наследование из элемента Добавление функциональности существующему элементу управления. При подмене управления Windows Forms метода OnPaint вызывает базовый класс OnPaint. Можно вносить дополнения к внешнему виду первоначального элемента управления, но не менять его дизайн Создание Usercontrol Создание Usercontrol, состоящего из нескольких существующих элементов управ- ления (с объединением их функциональности). Метод OnPaint специальных эле- ментов управления не подменяется. Можно вносить дополнения к внешнему виду первоначального элемента управления, но не менять его дизайн
342 Глава 10 Таблица 10.11 (окончание) Методики специальных элементов управления и свойства PaintEventArgs Описание Наследование из класса Control Определение абсолютно нового элемента управления. Подмена метода OnPaint, вызов метода OnPaint базового класса и включение методов для рисования эле- мента управления. Модно настраивать внешний вид и функциональность элемента управления Свойства PaintEventArgs Используйте данный объект внутри метода OnPaint или Paint для рисования на элементе управления Graphics Указывает графический объект элемента управления. Используется для рисования на элементе управления ClipRectangle Задает прямоугольник, указывающий границы элемента управления В листинге 10.13 показано создание элемента управления "часы". Это— Usercontrol, состоящий из метки и таймера: всякий раз при генерировании таймером события метка обновляется для отображения текущего вре- мени. Таймеры (Timer, пространство имен System.windows.Forms)— это невидимые компоненты, расположенные на форме и генерирующие события Tick через конкретный интервал. Интервал задается свойством interval ком- понента Timer, определяющим количество миллисекунд между событиями. По умолчанию таймеры отключены. Итак, создайте форму, отображающую специальный элемент управления— ClockUserControl (листинг 10.13). Создайте класс usercontrol для проекта выбором Project | Add | User Control.... При этом откроется диалого- вое окно, в котором можно указать тип добавляемого элемента управления; пользовательские элементы управ- ления уже выбраны. После этого файлу присваивается имя (и класс) ClockUserControl. Пустой объект ClockUserControl появится в виде серого прямоугольника. Этот элемент управления можно рассматривать как Form, поэтому можно добавлять элементы управления (с помощью панели Toolbox) и задавать свойства (с помощью окна Properties). Однако вместо создания прило- жения (в классе Control нет метода Main) создадим просто новый элемент управления, состоящий из других элементов. Добавляем в класс Usercontrol объекты Label (displayLabel,‘строка 15) и Timer (clockTimer, стро- ка 14). Задаем интервал таймера в 100 миллисекунд и обновляем текст displayLabel каждым событием (стро- ки 18—24). Обратите внимание, что clockTimer должен быть активизирован присвоением свойству Enabled значения True в окне Properties. Структура DateTime (пространство имен System) содержит член Now, являющийся значением текущего времени. Метод ToLongTimestring преобразует Now в строку, содержащую текущие час, минуту и секунду (вместе с AM или РМ). Это используется для задания свойства Text класса displayLabel в строке 22. По окончании создания элемент управления "часы" появится в виде пункта в панели элементов. Для использо- вания его можно просто перетянуть в Windows-приложение проекта и запустить это приложение. Объект ClockUserControl имеет белый фон (для выделения на форме). На рис. 10.30. показан результат ciockExample, ЯВЛЯЮЩИЙСЯ простой формой, содержащей ClockUserControl. Листинг 10.13 За данный программистом элемент управления, отображающий текуще*. время к. ..z-ч ~J .... ... ------------------ ™&_. —.„...„-.«.„..V. ,j 1 // Листинг 10.13: ClockUserControl.cs 2 // Специальный элемент управления с таймером и меткой 3 4 5 б 7 8 9 10 11 12 13 14 15 16 using System; using System.Collections; using System.ComponentModel; t using System.Drawing; using System.Data; using System.Windows.Forms; public class ClockUserControl : System.Windows.Forms.UserControl { private System.Windows.Forms.Timer clockTimer; private System.Windows.Forms.Label displayLabel;
Концепции графического пользовательского интерфейса: часть 2 343 17 // обновление метки при каждом такте 18 private void clockTimer_Tick( 19 object sender, System.EventArgs e) 20 { 21 // получение значения текущего времени, преобразование в строку 22 displayLabel.Text = DateTime.Now.ToLongTimeString() ; 23 24 } // конец метода clockTimer_Tick 25 26 } // конец класса ClockUserControl Рис. 10.30. Элемент управления "часы": а — исходное состояние: б— обновленное состояние Приведенные выше действия полезны, когда необходимо определить специальный элемент управления для соз- даваемого проекта. Visual Studio .NET дает разработчикам возможность предоставления "своих” элементов управления коллегам. Для создания элемента управления Usercontrol, который можно экспортировать в другие программные приложения, выполните следующее: 1. Создайте новый проект Windows Control Library. 2. В проекте добавьте элементы управления и функциональность в Usercontrol (рис. 10.31). Рис. 10.31. Создание специального элемента управления Рис. 10.32. Диалоговое окно свойств проекта 3. Постройте проект. Visual Studio .NET создает в выходном каталоге dll-файл для Usercontrol. Этот файл — не исполняемый: классы control не имеют метода Main. Выберите Project | Properties для поиска выходного каталога и выходного файла (рис. 10.32). 4. Создайте новое Windows-приложение. 5. Импортируйте usercontrol. В новом Windows-приложении щелкните правой кнопкой мыши на Toolbox и выберите команду Customize Toolbox.... В открывшемся диалоговом окне выберите вкладку .NET Frame- work Components. Найдите dll-файл, размещенный в выходном каталоге библиотеки элементов управления проекта Windows. Установите флажок элемента управления и нажмите кнопку ОК (рис. 10.33). 6. В панели элементов появится usercontrol, который можно добавить в форму так, будто она представляет собой любой другой элемент управления (рис. 10.34).
344 Глава 10 Рис. 10.33. Специальный элемент управления, добавленный в панель инструментов а Рис. 10.34. Специальный элемент управления: а — в панели Toolbox; б — добавленный в форму Совет по тестированию и отладке_____________________________________ Классы элементов управления не имеют метода Main: они не могут исполняться сами по себе. Для тестирова- ния функциональности добавьте их в пример Windows-приложения и запустите из него. Многие из наиболее успешных современных коммерческих программ обладают очень простыми в использова- нии графическими пользовательскими интерфейсами. Из-за требования к созданию дружественных пользовате- лю интерфейсов, способность к созданию сложных GUI является одним из важных навыков программиста. К счастью, Visual Studio .NET обеспечивает интегрированную среду разработки, заметно упрощающую задачу создания GUI. В этой и предыдущей главах представлены основные методики добавления различных компонен- тов GUI в программы. В следующей главе обсуждается более "кулуарная" тема — организация многозадачной обработки. Во многих языках программирования можно создавать множественные потоки, позволяющие не- скольким процессам выполняться одновременно. С изучения многозадачности и управления ею в C# читатели начнут осваивать более сложные типы программных средств. 10.12. Резюме Меню представляют группы связанных между собой команд для приложений Windows и обеспечивают взаимо- действие пользователя с программой без ненужного "загромождения" GUI. Элемент управления LinkLabel используется для отображения ссылок на другие объекты, например, на файлы или Web-страницы. При щелчке кнопкой мыши на LinkLabel генерируется событие LinkClicked. Элемент управления ListBox дает пользователю возможность просмотра и выбора нескольких пунктов из спи- ска. Элемент управления CheckedListBox расширяет ListBox установкой флажка рядом с каждым элементом списка. Данный элемент позволяет выбрать несколько пунктов без каких бы то ни было логических ограниче-
Концепции графического пользовательского интерфейса: часть 2 345 ний. Событие SelectedlndexChanged имеет место, когда пользователь выбирает новый пункт в CheckedListBox. Свойство Selectedltem возвращает выбранный пункт. Selectedlndex возвращает номер выбранного пункта. Элемент управления ComboBox объединяет особенности TextBox с раскрывающимся списком. Пользователь мо- жет либо выбрать опцию из списка, либо ввести ее вручную (если позволяет программа). Свойство DropDownStyle определяет тип ComboBox. Элемент ComboBox имеет свойства Items (коллекция), Selectedltem и Selectedlndex, похожие на соответствующие свойства ListBox. Элемент управления Treeview может иерархически отображать узлы в дереве. Узлом называется один элемент дерева. Узлы содержат данные, а также ссылки на другие узлы. Родительский узел содержит узлы-потоки, кото- рые сами могут быть родительскими узлами. Каждый узел имеет коллекцию Nodes, содержащую список потом- ков узла. Элемент управления Listview похож на ListBox, за исключением того, что в нем разными способами, наряду с пунктами, могут отображаться пиктограммы. Для задания изображений разных пиктограмм программистам следует пользоваться компонентом imageList. Элемент TabControl создает окна с вкладками. Это дает программистам возможность логической организации информации и помогает экономить свободное пространство экрана. Элементы Tabcontrol содержат объекты TabPage, которые, в свою очередь, также могут содержать элементы управления. Каждый элемент TabPage гене- рирует свое событие Click при щелчке на его вкладке. Программы с многодокументным интерфейсом (MDI) дают пользователям возможность открывать в рамках главного окна приложения сразу несколька документов (окон). Каждое окно в рамках MDI-приложения называ- ется окном-потомком, а окно приложения — родительским окном. Окна-родители и окна-потомки приложения могут иметь разные меню, сливающиеся (объединяющиеся) при выборе окна-потомка. .NET Framework позволяет программистам создавать специальные элементы управления. Для создания нового элемента, состоящего из уже существующих элементов, следует пользоваться классом Usercontrol. Для созда- ния нового элемента управления "с нуля" нужно пользоваться механизмом наследования из класса control. В данной главе класс usercontrol для проекта создан выбором команды Project | Add User Control.... Этот эле- мент управления можно рассматривать как форму Windows.
ГЛАВА 11 (ш Организация многозадачной обработки Касанье паука, как тонко Он чувствует свой эфемерный дом! И в нем живет... Александр Поуп Человек с часами может ответить на вопрос "Который час?"; человек с двумя часами никогда не может сказать это точно. Пословица Учись трудиться и ждать. Генри Уодсворт Лонгфелло Самое общее определение красоты... Многообразие в Единстве. Сэмюэл Тейлор Кольридж Темы данной главы: □ понятие многозадачности; П оценка влияния многозадачности на повышение производительности программы; О процесс создания потоков, управления ими и уничтожения; □ жизненный цикл потока; О синхронизация потоков; □ приоритеты и планирование потоков; □ понимание роли ThreadPool при эффективной многозадачной обработке. 11.1. Введение Было бы прекрасно, если бы каждое действие приходилось выполнять один раз и сразу хорошо, но, как правило, так бывает далеко не всегда. Человеческое тело выполняет огромное множество операций параллельно или од- новременно (этот термин будет употребляться в данной главе). Например, дыхание, кровообращение и пищева- рение могут происходить одновременно. Все органы ощущений — зрения, осязания, обоняния, вкуса и слуха работают одновременно. Компьютеры тоже выполняют множество операций одновременно. Настольные персо- нальные компьютеры могут одновременно компилировать программу, отправлять файл на печать и получать электронную почту. Звучигсмешно, но большинство языков программирования не дают разработчикам программных средств воз- можности задавать одновременные операции. Они, скорее, предоставляют самый простой набор управляющих структур, позволяющих выполнять одну операцию, по завершении которой можно переходить к выполнению следующей. Исторически, тип параллелизма, выполняемого современными компьютерами, изначально реализо- вался как "примитивы” операционной системы, доступные только для "системных программистов" с большим опытом работы. Язык программирования Ada, разработанный в Министерстве обороны США, сделал примитивы параллелизма широко доступными для военных подрядчиков, создававших оперативные системы управления для военного ведомства. Однако Ada не был популярным языком в учебных заведениях и в коммерческих структурах. Библиотека классов .NET Framework обеспечивает доступность примитивов параллелизма для разработчиков программных средств. Программист указывает, какие приложения содержат "потоки управления", где каждый поток направлен на часть программы, которая может выполняться одновременно с другими потоками; данная возможность называется многозадачностью. Многозадачность доступна для всех языков программирования .NET, включая С#, Visual Basic и Visual C++.
Организация многозадачной обработки 347 Замечание по технологии программирования ____________________________________________ Библиотека классов .NET Framework включает возможности многозадачности в пространстве имен System.Threading. Этим стимулируется применение многозадачности в большей части сообщества разработ- чиков программных приложений. В данной главе рассматриваются многие области применения параллельного программирования. При загрузке в программы файлов большого объема, например, аудио- или видеоклипов из World Wide Web, пользователи не хотят дожидаться окончания загрузки, чтобы начать воспроизведение. К решению этой проблемы можно под- ключить множественные потоки: один поток скачивает клип, а другой воспроизводит. Тогда эти операции (или задачи) смогут выполняться одновременно. Во избежание обрывистого воспроизведения потоки синхронизи- руются так, что поток воспроизведения не начинается до тех пор, пока в памяти не накопится достаточного объ- ема материала для его поддержания. Другим примером многозадачности в C# является автоматический "сбор мусора". В С и C++ ответственность за своевременное освобождение динамически распределяемой памяти возлагается на программиста. В C# имеется поток "сбора мусора", который и освобождает динамически распределяемую память при отсутствии в ней не- обходимости. Совет по повышению производительности________________________________________________ Одной из причин многолетней популярности языков С и C++ является то, что применявшиеся в них методики управления памятью были намного эффективнее, чем способы, применявшиеся в других языках со "сборщиками мусора" На самом деле, управление памятью в C# часто намного оперативнее, нежели ° С или C++1 О хорошем стиле программирования________________________________.____________________ Если объект программе больше не требуется, установите ссылку на объект на null. При этом сборщик мусора на самом раннем этапе определит, что данный объект можно отправить "в корзину". При наличии других ссылок на данный объект отправить его "в корзину" нельзя. Написание многозадачных программ может оказаться довольно непростым делом. Несмотря на то, что челове- ческий мозг может выполнять одновременно несколько операций, многим людям сложно "перепрыгивать" с одного "поезда" размышлений на другой, идущий параллельно. Чтобы понять, почему многозадачность сложно запрограммировать и осмыслить, проведите следующий эксперимент. Откройте три книги на первой странице и попытайтесь одновременно читать все три. Прочтите несколько слов из первой книги, потом несколько слов — из второй, из третьей, потом снова — из первой и т. д. После такого эксперимента сложность многозадачности станет понятной: каково переходить от одной книги к другой, быстро читать, запоминать место в каждой, дви- гать книгу ближе, чтобы видеть текст, отодвигать книги, которые в данный момент не читаются... и при этом еще пытать понять, о чем идет речь? Совет по повышению производительности________________________________________________ Проблема однозадачных программных приложений заключается в том, что продолжительные операции должны завершаться до того, как могут начаться следующие В многозадачном приложении потоки могут использовать процессор (или несколько процессоров) совместно с тем, чтобы множество задач выполнялось параллельно 11.2. Состояния потоков: жизненный цикл потока В любой момент жизненного цикла потока говорится, что он находится в одном из нескольких состояний по- тока (рис. 11.1 )1 2. В данном разделе рассматриваются эти состояния и переходы между ними. Двумя важнейши- ми классами многозадачных приложений являются классы Thread и Monitor (пространство имен System.Threading). В разделе также обсуждаются несколько методов классов Thread и Monitor, вызывающих переходы из одного состояния в другое. Новый поток начинает жизненный цикл в состоянии Unstarted (Не начатый). Поток остается в этом состоянии до тех пор, пока программа не вызовет метод start класса Thread, помещающий поток в состояние Started (На- чатый) (иногда это состояние называется Ready (Готов) или Runnable (Готовый к запуску) и сразу возвращает управление вызывающему потоку. После этого поток, активизировавший start, вновь начатый поток (Started) и все прочие потоки в программе выполняются одновременно. 1 См. http.7/msdn.microsoft.com/library/default.asp?url=/library/en-us/dndotnet/htinl/dotnetperftechs.asp. 2 Когда данная книга была отправлена в печать (имеется в виду оригинал книги — Ред.), корпорация Microsoft изменила названия состояний потока "Начатый" (Started) и "Заблокированный" (Blocked) на "Выполняющийся" (Running) и "Ожидание-объединение" (Waitsleepjoin) соответственно.
348 Глава 11 Рис. 11.1. Жизненный цикл потока Поток Started с самым высоким приоритетом входит в состояние Running (т. е. начинает выполняться), когда операционная система присвоит потоку процессор (приоритеты потоков рассматриваются в разд. 11.3). Когда поток Started впервые получает процессор и становится потоком Running, этот поток исполняет свой делегат Threadstart, задающий операции, которые данный поток будет выполнять в течение своего жизненного цикла. Когда программа создает новый Thread, она указывает делегат Threadstart в качестве аргумента конструктору Thread. Делегат Threadstart должен быть методом, возвращающим void и не принимающим никаких аргу- ментов. Поток Running входит в состояние Stopped (или Dead— мертвый) при прекращении исполнения делегата Threadstart. Обратите внимание, что программа может "насильно" поместить поток в состояние Stopped вызо- вом метода Abort класса Thread в соответствующем объекте Thread. Метод Abort выводит в поток исключение ThreadAbortException, что, как правило, прекращает исполнение потока. Когда поток находится в состоянии Stopped, и ссылок на объект потока нет, тогда "сборщик мусора" может удалить из памяти объект потока. Поток входит в состояние Blocked (Заблокирован), когда поток выдает запрос на ввод/вывод. Операционная система блокирует исполнение потока до тех пор, пока не закончит ввод/вывод, которого ожидает поток. В этой точке поток возвращается в состояние Started и может возобновить исполнение. Поток Blocked не может ис- пользовать процессор, даже при его наличии. Существуют три пути, по которым поток Running входит в состояние WaitSleepJoin. Если поток сталкивается с кодом, который он пока не может исполнить (обычно из-за того, что не удовлетворено условие), поток может вызвать метод Wait класса Monitor для вхождения в состояние WaitSleepJoin. После входа в это состояние поток возвращается в состояние Started, когда другой поток активизирует метод Pulse или PulseAii класса Monitor. Метод Pulse перемещает очередной ожидающий поток назад в состояние Started. Метод PulseAii перемещает все ожидающие потоки назад в состояние Started. Поток Running может вызвать метод sleep класса Thread для вхождения в состояние WaitSleepJoin на период, измеряемый миллисекундами, указанный как аргумент к sleep. Спящий (ожидающий) поток возвращается в состояние Started по истечении заданного для него времени ожидания. Потоки в режиме ожидания не могут использовать процессор, даже при его наличии. Любой поток, входящий в состояние WaitSleepJoin вызовом метода Wait класса Monitor, либо вызовом метода sleep класса Thread, также оставляет состояние WaitSleepJoin и возвращается в состояние Started, если ожи- дающий метод interrupt класса Thread вызывается другим потоком в программе. Если поток не может продолжать исполнение (данный феномен называется зависимым потоком) до прекраще- ния исполнения другого потока, тогда зависимый поток вызывает метод Join другого потока для "объединения" этих двух потоков. Когда два потока "объединены", тогда зависимый поток оставляет состояние WaitSleepJoin, когда другой поток завершает исполнение (входит в состояние Stopped). При вызове метода Suspend класса Running Thread, поток Running входит в состояние Suspended. Поток Suspended (Приостановленный) возвращается в состояние Started, когда другой поток в программе активизирует метод Resume потока Suspended.
Организация многозадачной обработки 349 11.3. Приоритеты потоков и их планирование Каждый поток имеет приоритет в диапазоне от Threadpriority.Lowest до Threadpriority.Highest. Эти два значения происходят от перечисления Threadpriority (пространство имен System.Threading). Перечисление включает в себя значения Lowest, BelowNormal, Normal, AboveNormal и Highest. По умолчанию каждый поток имеет приоритет Normal. Операционная система Windows поддерживает концепцию, называемую квантованием времени, позволяющую потокам с одинаковым приоритетом использовать процессор совместно. Без квантования времени исполнение каждого потока в наборе потоков с одинаковой степенью приоритета завершается (если только поток не выхо- дит из состояния Running и не входит в состояние WaitSleepJoin, Suspended или Blocked) до того, как равные ему по приоритету потоки получат шанс к исполнению. С квантованием времени каждый поток получает краткий промежуток процессорного времени, называемый квантом, во время которого поток может исполниться. По истечении кванта, даже если поток не закончил исполнение, процессор у него "отбирается” и передается сле- дующему потоку с таким же приоритетом, если он есть. В задачу планировщика потоков входит поддержание в любое время исполнения потока с самым высоким при- оритетом и, при наличии нескольких потоков с самым высоким приоритетом, обеспечение исполнения таких потоков в течение кванта круговым способом (т. е. время для таких потоков может квантоваться). На рис. 11.2 показана многоуровневая очередь приоритетов для потоков. Также на рисунке продемонстрировано, что в од- нопроцессорном компьютере потоки А и В исполняются в течение кванта круговым способом до тех пор, пока не завершится исполнение обоих. Это означает, что поток А получает квант времени на исполнение. Затем квант получает поток В. Дальше, А получает другой квант, В получает другой квант. Процесс продолжается до завершения исполнения одного потока. Потом процессор "бросает" всю свою мощь на оставшийся поток (если нет другого потока с таким же приоритетом в состоянии Started). Затем завершается поток С. Каждый из пото- ков D, Е и F исполняются за квант времени круговым способом др своего завершения. Данный процесс продол- жается до тех пор, пока все потоки не завершат свое исполнение. Обратите внимание, что в зависимости от опе- рационной системы новые потоки с более высоким уровнем приоритета могут задерживать — иногда на неоп- ределенное время— исполнение потоков с более низким уровнем приоритета. Такое откладывание на неопределенное время часто обозначается более прозрачным словом "зависание". Bsssss Приоритет Highest Приоритет AboveNormal Приоритет Normal г Приоритет Lowest Приоритет BelowNormal Рис. 11.2. Планирование приоритетов потоков Приоритет потока можно задать с помощью свойства Priority, принимающего значения от перечисления Threadpriority. Если аргумент не является ни одной из допустимых констант приоритета потоков, то имеет место исключение ArgumentException. Поток исполняется до тех пор, пока не иссякнет, не превратится в Blocked для ввода/вывода (или по другой причине), не обратится к sleep, не вызовет метод Wait или Join класса Monitor, не будет опережен потоком более высокого приоритета, либо пока не истечет его квант. Поток с более высоким уровнем приоритета, неже- ли поток Running, может стать потоком Started (и, следовательно, опередит поток Running), если поток выходит из режима ожидания, если завершается ввод/вывод для потока, являющегося для данного ввода/вывода Blocked, если на объект, на который был вызван wait, вызывается Pulse или PuiseAll, либо если завершается поток, с которым был объединен (join) поток с более высоким уровнем приоритета. В листинге 11.1 представлены базовые методики поточной обработки, включая построение объекта Thread и использование static-метода Sleep класса Thread. Программа создает три потока для исполнения, каждый со
350 Глава 11 свойством Normal по умолчанию. Каждый поток отображает сообщение, указывающее на то, что поток будет находиться в режиме ожидания произвольный промежуток времени от 0 до 5000 миллисекунд, после чего вой- дет в этот режим. По выходе из режима ожидания каждый поток отобразит свое имя, укажет на то, что режим ожидания окончен, остановит собственное исполнение и войдет в состояние Stopped. Читатели увидят, что ме- тод Main (т. е. основной поток исполнения) прекращает свое исполнение до того, как прекратит исполнение про- граммное приложение. Программа состоит из двух классов— ThreadTester (строки 8—41), создающего три потока, и Messageprinter (строки 44—73), определяющего метод Print, содержащий операции, которые будет выполнять каждый поток. Объект класса Messageprinter (строки 44—73) управляет жизненным циклом каждого из трех потоков, созда- ваемых методом Main класса ThreadTester. Класс Messageprinter состоит из переменной экземпляра sleepTime (строка 46), static-переменной random (строка 47), конструктора (строки 50—54) и метода Print (строки 57— 71). Переменная sleepTime сохраняет произвольное целочисленное значение, выбранное при вызове конструк- тора объекта Messageprinter. Каждый поток, управляемый объектом Messageprinter, находится в режиме ожи- дания промежуток времени, заданный соответствующей переменной sleepTime объекта Messageprinter. Конструктор Messageprinter (строки 50—54) инициализирует sleepTime на произвольное целое число от 0 до 5001 (не включительно). Метод Print начинается с получения ссылки на текущий исполняющийся поток (строка 60) через static- свойство CurrentThread класса Thread. Именно текущий исполняющийся поток активизирует метод Print. За- тем, в строках 63 и 64 отображается сообщение, указывающее имя текущего исполняющегося потока и объяв- ляющее, что данный поток будет находиться в режиме ожидания определенное количество миллисекунд. Обра- тите внимание, что в строке 64 используется свойство Name текущего исполняющегося потока для получения имени потока (задается в методе Main при создании каждого потока). В строке 66 активизируется static-метод Thread класса sleep для размещения потока в состоянии WaitSleepJoin. В этой точке поток теряет процессор, и система дает возможность исполнения другому потоку. Когда поток выходит из режима ожидания, он повторно входит в состояние Started, пока система не присвоит ему процессор. Когда объект Messageprinter снова вхо- дит в состояние Running, в строке 69 выдается имя потока в сообщении, указывающем, что поток вышел из ре- жима ожидания, и исполнение метода Print прекращается. Метод Main класса ThreadTester (строки 10—39) создает три объекта класса Messageprinter в строках 14, 19 и 24 соответственно. В строках 15—16, 20—21 и 25—26 создаются и инициализируются три объекта Thread. В строках 17, 22 и 27 задается свойство Name каждого потока Thread, которое используется для целей вывода. Обратите внимание, что конструктор каждого Thread получает в качестве аргумента делегат Threadstart. Пом- ните, что делегат Threadstart задает операции, которые поток выполняет в течение своего жизненного цикла. В строке 16 указывается, что делегатом для потока threadl будет метод Print объекта, на который ссылается printerl. Когда поток threadl впервые входит в состояние Running, он активизирует метод Print printerl для выполнения задач, определенных в теле метода Print. Таким образом, threadl распечатает свое имя, отобразит промежуток времени, в течение которого он будет находиться в режиме ожидания, пробудет в этом режиме это время, выйдет из режима ожидания и сообщит, что он окончен. В этой точке исполнение метода Print прекра- тится. Поток завершает выполнение своей задачи, когда прекращается исполнение метода, определенного деле- гатом Threadstart класса Thread; этот поток будет помещен в состояние Stopped. Когда thread2 и thread3 впервые входят в состояние Running, они активизируют методы Printer thread2 и threads соответственно. По- токи thread2 и threads выполняют те же задачи, что и threadl, выполнением методов Print объектов, на кото- рые ссылаются thread2 и threads (каждый из которых имеет собственное произвольно выбранное время ожи- дания). В строках 33—35 активизируется метод start класса Thread для размещения потоков в состоянии Started (ино- гда этот процесс называется запуском потока). Метод start сразу возвращается из каждого инициирования, после чего в строке 37 выдается сообщение, указывающее, что потоки были запущены, и поток исполнения Main останавливается. Впрочем, исполнение самой программы не прерывается, потому что еще имеются действую- щие потоки (т. е. потоки были Started, но еще не достигли состояния Stopped). Программа будет продолжать исполнение до окончания исполнения ее последнего потока. Когда система присваивает потоку процессор, по- ток входит в состояние Running и вызывает метод, указанный делегатом Threadstart потока. В данной про- грамме каждый поток активизирует метод Print соответствующего объекта Messageprinter для выполнения вышеописанных задач. Листинг 11.1. Потоки в режиме ожидания и при распечатке 1 // Листинг 11.1: ThreadTester.cs 2 // Распечатка нескольких потоков на разных интервалах времени 3
Организация многозадачной обработки 351 4 5 6 7 8 9 10 11 12 13 13а 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 using System; using Syste,Threading; // ThreadTester демонстрирует базовые концепции поточной обработки class ThreadTester { static void Main (string[] args) { // Создание каждого потока и присвоение имени. Использование // метода Printer класса MessagePrinter как аргумента делегата // Threadstart MessagePrinter printerl = new MessagePrinter(); Thread threadl = new Thread(new Threadstart(printerl.Print)); threadl.Name = "threadl"; MessagePrinter printer2 = new MessagePrinter(); Thread thread2 = new Thread(new Threadstart(printer2.Print)); threadl.Name = "thread2"; MessagePrinter printer3 = new MessagePrinter(); Thread thread3 = new Thread(new Threadstart(printers.Print)); threadl.Name = "threads"; t J Console.WriteLine("Starting threads"); // вызов метода Start каждого потока для помещения // каждого потока в состояние Started threadl.Start(); thread2.Start(); thread3.Start(); Console.WriteLine("Threads started\n"); } // конец метода Main } // конец класса ThreadTester // Метод Print данного класса, используемый для управления потоками class MessagePrinter { private int sleepTime; private static Random random = new RandomO; // конструктор для инициализации объекта MessagePrinter public MessagePrinter() { /7 выбор произвольного времени ожидания от 0 до 5 секунд sleepTime » random.Next(5001); ) // метод Print управляет потоком, распечатывающим сообщения public void Print() { // получение ссылки на текущий исполняющийся поток Thread current = Thread.CurrentThread; // ввод потока в режим ожидания на время sleepTime Console.WriteLine( current.Name + " going to sleep for " + sleepTime);
352 Глава 11 66 Thread.Sleep (sleepTime); 67 68 // распечатка имени потока 69 Console.WriteLine(current.Name + " done sleeping"); 70 71 } // конец метода Print 72 73 } // конец класса MessagePrinter Результат выполнения программы: Starting threads Threads started threadl going to sleep for 1977 thread2 going to sleep for 4513 thread3 going to sleep for 1261 thread3 done sleeping threadl done sleeping thread2 done sleeping Starting threads Threads started threadl going to sleep for 1466 thread2 going to sleep for 4245 threads going to sleep for 1929 threadl done sleeping threads done sleeping thread2 done sleeping Совет по тестированию и отладке_________________________________________________ Присвоение потокам имен помогает при отладке многозадачной программы. Отладчик Visual Studio .NET имеет окно Threads, отображающее имя каждого потока и позволяющее просматривать исполнение любого потока в программе. Обратите внимание, что приблизительные данные на выходе программы показывают каждый поток и время ожидания потока перед вхождением в режим ожидания. Как правило, поток с наименьшим временем ожидания выходит из этого режима первым, после чего сообщает, что время ожидания окончено, и прекращает исполне- ние. В разд. 11.7 будут рассмотрены аспекты многозадачности, которые могут удержать поток с наименьшим временем ожидания от выхода из этого режима первым. 11.4. Синхронизация потоков и класс Monitor Довольно часто случается так, что множественные потоки исполнения манипулируют совместно используемы- ми данными. Если поток с доступом к коллективным данным просто их считывает, тогда нет необходимости задавать доступ к данным одновременно только одним потоком. Однако, когда данные совместно используются многими потоками и модифицируются одним или несколькими из этих потоков, результаты могут быть непред- сказуемыми. Если один поток находится в процессе обновления данных, а другой в это время также пытается их обновить, то в данных отобразится обновление, выполненное вторым по счету. Если данные являются частью массива или какой-либо иной структуры, в которой потоки могут одновременно обновлять отдельные части этих данных, то, возможно, что эти части данных отобразят информацию из одного потока, а другие части — информацию из другого. Когда такое происходит, программа испытывает сложности при определении, случая корректного обновления данных. Эту проблему можно решить предоставлением одному потоку за раз эксклюзивного доступа к коду, управляю- щему совместно используемыми данными. В этот промежуток времени все остальные потоки, "нацеленные" на эти данные, будут находиться в режиме ожидания. По завершении манипуляций данным потоком с эксклюзив- ным доступом наступает очередь одного из потоков в ждущем режиме. Таким образом, каждый поток, полу- чающий доступ к данным, исключает одновременный доступ к ним другого потока. Этот феномен называется взаимны# исключением или синхронизацией потоков.
Организация многозадачной обработки 353 Для выполнения синхронизации в C# применяются мониторы .NET Framework. Класс Monitor предоставляет методы блокирования объектов для реализации синхронизированного доступа к совместно используемым дан- ным. Блокирование объекта означает, что одновременный доступ к данным может быть предоставлен только одному потоку. Когда потоку нужно получить эксклюзивное управление объектом, поток активизирует метод Enter класса Monitor для получения блокировки на этот объект данных. Каждый объект имеет SyncBlock, под- держивающий состояние блокирования этого объекта. Методы класса Monitor пользуются данными в SyncBlock объекта для определения состояния блокирования этого объекта После получения блока для объекта поток мо- жет манипулировать его данными. Пока объект заблокирован, недоступны все прочие потоки, делающие по- пытки получения блокировки на данный объект (т. е. они входят в состояние Blocked). Когда потоку, заблокиро- вавшему совместно используемый объект, блокировка больше не нужна, этот поток активизирует метод Exit класса Monitor для снятия блокировки. При этом SyncBlock совместно используемого объекта обновляется с указанием на то, что блокировка для данного объекта снова свободна. В этой точке, при наличии потока, ранее заблокированного от получения эксклюзивного права на совместно используемый объект, получает его и начи- нает обрабатывать объект. Если все потоки с доступом к объекту сделают попытку получения блокировки объ- екта до выполнения с ним манипуляций, то только один из них получит право работы с объектом. Это помогает поддерживать целостность данных. Распространенная ошибка программирования___________________________________________________ Убедитесь, что весь код, обновляющий совместно используемый объект, блокирует объект перед его обновле- нием. В противном случае, поток, вызывающий метод, который не блокирует объект, может сделать этот объект неустойчивым даже при приобретении блокировки объекта другим потоком. Распространенная ошибка программирования___________________________________________________ Взаимоблокировка имеет место, когда ожидающий поток (назовем его threadl) не может приступить к исполне- нию, потому что ждет, пока к нему приступит другой поток (назовем его thread2). Поток thread2 также не может приступить к исполнению, ожидая, пока его начнет threadl. Два потока ждут друг друга, поэтому операции, по- зволяющие каждому потоку продолжать исполнение, никогда не возникают. В C# имеется еще одно средство манипулирования блокировкой объекта — ключевое слово lock. При вводе lock перед блоком кода (заключенным в скобки), как в выражении lock (objectReference) { // здесь должен быть код, требующий синхронизации ) получим блокировку на объект, на который ссылается objectReference в скобках, objectReference — это та же ссылка, что обычно передается в методы Enter, Exit, Pulse и PulseAii класса Monitor. При прекращении ис- полнения по каким-либо причинам блока lock C# снимает блокировку с объекта, на который ссылается objectReference. Подробно lock описывается в разд. 11.7. Если поток определяет, что он не может выполнить задачу на блокированном объекте, тогда этот поток может вызвать метод wait класса Monitor и передать в качестве аргумента объект, на котором поток будет ожидать возможности выполнения поставленных перед ним задач. Вызов метода Monitor.wait из потока снимает блоки- ровку, которую поток имеет на объекте, принимаемом wait в качестве аргумента, и помещает этот поток в со- стояние WaitSleepJoin для этого объекта. Поток в состоянии WaitSleepJoin для объекта оставляет состояние WaitSleepJoin, когда отдельный поток активизирует метод Pulse или PulseAii класса Monitor с объектом в ка- честве аргумента. Метод Pulse переводит первый ожидающий поток объекта из состояния WaitSleepJoin в со- стояние Started. Метод PulseAii переводит все потоки в состоянии WaitSleepJoin объекта в состояние Started. Перевод в состояние Started позволяет потоку (или потокам) подготовиться к продолжению исполнения. Между потоками, ожидающими получения блокировки для объекта, и потоками, ожидающими в состоянии WaitSleepJoin объекта, существует различие: эти потоки вызвали метод Wait класса Monitor с объектом в каче- стве аргумента. Потоки, ожидающие получения блокировки, входят в состояние Blocked и ожидают в этом со- стоянии, пока не будет доступна блокировка объекта. После этого один из блокированных потоков может полу- чить блокировку объекта. Все методы класса Monitor — Enter, Exit, Wait, Pulse и PulseAii получают в качестве аргумента ссылку на объект — как правило, ключевое слово this. Распространенная ошибка программирования___________________________________________________ Поток в состоянии WaitSleepJoin не может повторно войти в состояние Started для продолжения исполнения, по- ка отдельный (другой) поток не активизирует метод Pulse или PulseAii класса Monitor с соответствующим объектом в качестве аргумента Если этого не происходит, тогда ожидающий поток будет ждать вечно, и будет иметь место взаимоблокировка. 23 Зак. 3333
354 Глава 11 Совет по тестированию и отладке_______________________________________________________ Когда совместно используемым объектом с помощью мониторов манипулируют несколько потоков, убедитесь, что при вызове одним потоком метода Wait класса Monitor для входа в состояние WaitSleepJoin для совместно используемого объекта другой поток, так или иначе, вызовет метод Pulse класса Monitor для перевода потока, ожидающего на совместно используемом объекте, обратно в состояние Started. Если несколько потоков могут ожидать совместно используемый объект, тогда отдельный (другой) поток может вызвать метод PulseAll клас- са Monitor в качестве "гарантии", что все ожидающие потоки имеют другую возможность выполнения постав- ленных перед ними задач. Совет по повышению производительности ________________________________________________ Синхронизация, предназначенная для достижения корректности многозадачных программ, может замедлять скорость их исполнения в результате непроизводительных издержек монитора и частого перевода потоков в со- стояния Running, WaitSleepJoin и Started} Хотя, кому нужны высокоэффективные многозадачные программы, ра- ботающие некорректно? 11.5. Отношение "производитель/потребитель" без синхронизации потоков В отношении "производитель/потребитель" часть "производитель" приложения генерирует данные, а часть "потребитель"— использует их. В многозадачном отношении "производитель/потребитель" поток- производитель вызывает метод производства для генерирования данных и помещает его в коллективно ис- пользуемую область памяти, называемую буфером. Поток-потребитель вызывает метод потребления для счи- тывания данных. Если производитель, ожидающий помещения очередной порции данных в буфер, определяет, что потребитель еще не считал из буфера предыдущие данные, тогда поток-производитель должен вызвать ме- тод wait; в противном случае, потребитель никогда не получит предыдущих данных, и для данного приложения они будут утеряны. Когда поток-производитель считывает сообщение, он должен вызывать метод Pulse, чтобы ожидающий производитель мог начать исполнение. Если поток-потребитель обнаруживает буфер пустым или выясняет, что предыдущие данные уже считаны, тогда он должен вызвать метод wait; в противном случае, по- требитель может считать "мусор" из буфера, либо обработать элемент предыдущих данных более одного раза. Результатами такого положения дел в обоих случаях станут логические ошибки в приложении. При помещении производителем очередной порции данных в буфер производитель должен вызвать метод Pulse, чтобы поток- потребитель мог начать исполнение. Рассмотрим вариант появления логических ошибок при не синхронизированном доступе к данным нескольких потоков. Предположим отношение "производитель/потребитель", где поток-производитель записывает последо- вательность чисел (например, 1,..., 4) в совместно используемый (общий) буфер — ячейку памяти, коллективно используемую несколькими потоками. Поток-потребитель считывает эти данные из общего буфера и отобража- ет их. На выходе программы показаны значения, записываемые (создаваемые) производителем, и значения, считываемые (получаемые) потребителем. В листинге 11.2 показаны производитель и потребитель, осуществ- ляющие доступ к одной совместно используемой ячейке (int-переменной buffer) памяти без синхронизации. Доступ к этой ячейке осуществляют поток-производитель и поток-потребитель: производитель записывает дан- ные, потребитель считывает их. Необходимо, чтобы каждое значение, записываемое потоком-производителем в совместно используемую ячейку, считывалось потоком-потребителем только один раз. Однако потоки в этом примере не синхронизированы. Следовательно, данные могут быть утеряны, если производитель помещает но- вые данные в слот до того, как потребитель получит предыдущие данные. Также, данные могут быть повторены некорректно, если производитель получает их еще раз до создания производителем очередного элемента. Для демонстрации таких случаев поток-потребитель в приведенном примере поддерживает сумму всех считанных значений. Поток-производитель создает значения от 1 до 4. Если потребитель считывает каждое произведенное значение один и только один раз, тогда общая сумма будет равна 10. Однако при многократном исполнении этой программы будет видно, что общая сумма редко равна 10 (если вообще такая сумма имеет место). При этом поток-производитель и поток-потребитель в примере между выполнением поставленных задач находятся в режиме ожидания произвольные интервалы времени — до трех секунд. Поэтому неизвестно точно, ни когда поток-производитель сделает попытку записи нового значения, ни когда поток-производитель сделает попытку считывания этого значения. 1 // Листинг 11.2: Unsynchronized.cs 2 // Демонстрация нескольких потоков, модифицирующих общий объект 3 // без синхронизации 4
Организация многозадачной обработки 355 5 6 7 8 9 10 11 Ila 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 56a 57 58 59 60 61 62 63 64 65 using System; using System.Threading; // класс представляет одну совместно используемую переменную int public class HoldlntegerUnsynchronized { // буфер, совместно используемый потоком-производителем и // потоком-потребителем private int buffer = -1; // свойство Buffer public int Buffer { get { Console.WriteLine(Thread.CurrentThread.Name + " reads " + buffer); return buffer; } set { Console.WriteLine(Thread.CurrentThread.Name + " writes " + value); buffer = value; } } // конец свойства Buffer } 7/ конец класса HoldlntegerUnsynchronized // метод Produce класса Producer управляет потоком, сохраняющим // значения от 1 до 4 в sharedLocation class Producer { private HoldlntegerUnsynchronized sharedLocation; private Random randomSleepTime; // конструктор public Producer( HoldlntegerUnsynchronized shared, Random random) { sharedLocation = shared; randomSleepTime random; } // сохранение значений 1-4 в объекте sharedLocation public void Produce() { // режим ожидания — произвольный интервал времени // до 3000 мсек, затем установка свойства Buffer объекта // sharedLocation for (int count == 1; count <= 4; count++) { Thread.Sleep(randomSleepTime.Next(1, 3000)); sharedLocation.Buffer = count; } Console.WriteLine(Thread.CurrentThread.Name + " done producing. \nTerminating " + Thread.CurrentThread.Name +".");
356 Глава 11 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 } // конец метода Produce } // конец класса Producer // Метод Consume класса Consumer управляет потоком, повторяющимся //4 раза, и считывает значение из sharedLocation class Consumer { private HoldlntegerUnsynchronized sharedLocation; private Random randomSleepTime; // конструктор public Consumer( HoldlntegerUnsynchronized shared, Random random) { sharedLocation = shared; randomSleepTime = random; 1 // четырехкратное считывание значения sharedLocation public void Consume() { int sum = 0; // режим ожидания — произвольный интервал времени до // 3000 мсек, затем добавление значения свойства Buffer объекта // sharedLocation к сумме for (int count =1; count <= 4; count++) { Thread.Sleep (randomSleepTime.Next (1, 3000)); sum += sharedLocation.Buffer; } Console.WriteLine (Thread.CurrentThread.Name + ’’ read values totaling: " + sum + ".\nTerminating " + Thread.CurrentThread.Name + ’’."); } // конец метода Consume } // конец класса Consumer // данный класс создает поток-производитель и поток-потребитель class SharedCell { // создание потока-производителя и потока-потребителя // и их запуск static void Main(string[] args) { // создание объекта, совместно используемого потоками HoldlntegerUnsynchronized holdinteger = new HoldlntegerUnsynchronized() // объект Random, используемый каждым потоком Random random = new RandomO; // создание объектов Producer и Consumer Producer producer = new Producer (holdinteger, random);. Consumer consumer = new Consumer(holdinteger, random);
Организация многозадачной обработки 357 128 // создание потоков для производителя и потребителя и задание 129 // делегатов для каждого потока 130 Thread producerThread = 131 new Thread(new Threadstart(producer.Produce)); 132 producerThread.Name = "Producer"; 133 134 Thread consumerThread = 135 new Thread(new Threadstart(consumer.Consume)); 136 consumerThread.Name = "Consumer"; 137 138 // запуск каждого потока 139 producerThread.Start(); 140 consumerThread.Start(); 141 142 } 11 конец метода Main 143 144 } // конец класса SharedCell Результат работы программы: Consumer reads -1 Producer writes 1 Consumer reads 1 Consumer reads 1 Consumer reads 1 Consumer read values totaling: 2. Terminating Consumer. Producer writes 2 Producer writes 3 Producer writes 4 Producer done producing. Terminating Producer. Producer writes 1 Producer writes 2 Consumer reads 2 Producer writes 3 Consumer reads 3 Producer writes 4 Producer done producing. Terminating Producer. Consumer reads 4 Consumer reads 4 Consumer read values totaling: 13. Terminating Consumer. Producer writes 1 Consumer reads 1 Producer writes 2 Consumer reads 2 Producer writes 3 Consumer reads 3 Producer writes 4 Producer done producing. Terminating Producer. Consumer reads 4 Consumer read values totaling: 10. Terminating Consumer. Программа состоит из четырех классов HoldlntegerUnsynchronized (строки 9—34), Producer (строки 37—70), Consumer (строки 73—106) и SharedCell (строки 109—144). Класс HoldlntegerUnsynchronized (строки 9—35) состоит из переменной экземпляра buffer (строка 12) и свой- ства Buffer (строки 15—33), предоставляющего процедуры доступа get и set. Процедуры доступа свойства
358 Глава 11 Buffer не синхронизируют доступ к переменной экземпляра buffer. Обратите внимание, что каждое средство доступа использует static-свойство CurrentThread класса Thread для получения ссылки на текущий испол- няющийся поток, после чего использует свойство Name этого потока для получения его имени. Класс Producer (строки 39—69) состоит из переменной экземпляра sharedLocation (строка 41), переменной экземпляра randomSleepTime (строка 42), конструктора (строки 45—50) для инициализации этих переменных экземпляра и метода Produce (строки 53—67). Конструктор инициализирует переменную экземпляра sharedLocation для ссылки на объект HoldlntegerUnsynchronized, полученный из метода Main в качестве аргу- мента shared. Поток-производитель в этой программе выполняет задачи, определенные в методе Produce класса Producer. Метод Produce содержит оператор for (строки 57—61), повторяющийся четыре раза. Каждая итера- ция цикла сначала активизирует метод sleep класса Thread для помещения потока-производителя в состояние WaitSleepJoin на произвольный интервал времени между 0 и 3 секундами. Когда данный поток выходит из ре- жима ожидания, в строке 61 значение управляющей переменной count присваивается свойству Buffer объекта HoldlntegerUnsynchronized, что заставляет процедуру доступа set объекта HoldlntegerUnsynchronized моди- фицировать переменную экземпляра buffer объекта HoldlntegerUnsynchronized. Когда цикл завершается, з строках 63—65 в консольном окне отображается строка текста, сообщающая, что поток завершил генерирова- ние данных, и его исполнение прекращается, после чего прекращается исполнение метода Produce, и поток- производитель переводится в состояние Stopped. Класс Consumer (строки 73—106) состоит из переменной экземпляра sharedLocation (строка 75), переменной экземпляра randomSleepTime (строка 76), конструктора (строки 79—84) для инициализации этих переменных и метода Consume (строки 87—104). Конструктор инициализирует переменную экземпляра sharedLocation для ссылки на объект HoldlntegerUnsynchronized, полученный из метода Main в качестве аргумента shared. Поток- потребитель в этой программе выполняет задачи, определенные в методе Consume класса Consumer.. Метод Consume содержит оператор for (строки 94—98), повторяющийся четыре раза. Каждая итерация цикла сначала активизирует метод sleep класса Thread для помещения потока-потребителя в состояние WaitSleepJoin на про- извольный интервал времени между 0 и 3 секундами. Затем в строке 97 присваивается значение свойству Buffer объекта HoldlntegerUnsynchronized, и прибавляется это значение переменной sum. Когда цикл завершается, строки 100—102 отображают в консольном окне строку, указывающую сумму всех считанных значений, после чего прекращается исполнение метода Consume, и поток-потребитель переводится в состояние Stopped. Примечание_____________________________________________________________________________________ В данном примере используется метод Sleep для подчеркивания, что в многозадачных приложениях не ясно, ко- гда и как долго каждый поток будет выполнять свои задачи после получения им процессора. Обычно такие ас- пекты планирования потоков выполняются операционной системой компьютера. В данной программе задачи по- токов достаточно просты: для производителя — повторить цикл четыре раза и выполнить оператор присваива- ния, для потребителя— также повторить цикл четыре раза и прибавить значение переменной sum. Без обращения к методу Sleep, если производитель исполняется первым, он завершит выполнение своей задачи до того, как потребитель получит шанс на исполнение. Если первым исполняется потребитель, то он четыре раза получит значение -1, после чего прекратит исполнение до того, как производитель сможет сгенерировать первое реальное значение. Метод Main класса Sharedcell (строки 112—142) создает совместно используемый объект HoldlntegerUnsynchronized (строки 115—116) и объект Random (строка 119) для генерирования произвольных значений времени ожидания, после чего использует эти объекты в качестве аргументов для объектов классов Producer (строку 122—123) и Consumer (строки 125 и 126). Объект HoldlntegerUnsynchronized содержит дан- ные, которые будут использоваться совместно потоком-производителем и потоком-потребителем. В строках 130—132 создается и присваивается имя объекта producerThread. Делегат Threadstart для producerThread ука- зывает, что данный поток будет выполнять метод Produce объекта producer. В строках 134—136 создается и присваивается имя объекта consumerThread. Делегат Threadstart для consumerThread указывает, что данный поток будет выполнять метод consume объекта consumer. Наконец, в строках 139 и 140 два потока помещаются в состояние Started путем активизации метода start каждого потока, после чего исполнение метода Main прекра- щается. В идеале желательно, чтобы каждое значение, производимое объектом Producer, потреблялось только один раз объектом Consumer. Однако при рассмотрении первого вывода листинга 11.2 видно, что потребитель получил значение -1 до того, как производитель поместил значение в совместно используемый буфер, и что значение 1 было считано три раза. Потребитель завершил свое исполнение до того, как производитель имел возможность создать значения 2, 3 и 4. Таким образом, эти три значения были утеряны. Во втором выводе видно, что зна- чение 1 было утеряно, потому что значения 1 и 2 были сгенерированы до того, как поток-потребитель смог считать значение 1. Так же значение 4 было считано дважды. Последний вывод демонстрирует, что при опреде- ленной удаче можно получить надлежащий результат, в котором каждое сгенерированное производителем зна- чение будет считываться потребителем один и только один раз. Данный пример наглядно показывает необхо-
Организация многозадачной обработки'359 димость тщательного контроля доступа параллельных потоков к совместно используемым данным; в противном случае программа может выдавать некорректные результаты. Для решения проблем утерянных данных и данных, считанных более одного раза в предыдущем примере, син- хронизируем (листинг 11.3) доступ параллельных потока-производителя и потока-потребителя к коду, управ- ляющему совместно используемыми данными, использованием методов Enter, Wait, Pulse и Exit класса Monitor. Когда для доступа к совместно используемому объекту поток применяет синхронизацию, объект бло- кируется, и ни один другой поток не может одновременно получить блокировку на данный совместно исполь- зуемый объект. 11.6. Отношение "производитель/потребитель" с синхронизацией потоков В листинге 11.3 представлены производитель и потребитель, осуществляющие синхронизированный доступ к совместно используемой ячейке памяти, т. е. потребитель считывает значение только после его создания произ- водителем, а последний генерирует новое значение лишь после считывания потребителем предыдущего значе- ния. Классы Producer (строки 90—123), Consumer (строки 126—162) и Sharecell (строки 165—200) идентичны приведенным в листинге 11.2, за исключением того, что здесь используется новый класс HoldintegerSynchronized. Примечание____________________________________________________________________ В данном примере демонстрируется синхронизация с методами Enter и Exit класса Monitor. В следующем примере те же концепции демонстрируются посредством блока lock. Листинг 11.3. Доступ с синхронизацией к совместно используемому объекту потоком-прои^в и потоком-потребителем 1 // Листинг 11.3.: Synchronized.cs 2 // Демонстрация нескольких потоков, модифицирующих общий объект 3 //с синхронизацией 4 5 using System; б using System.Threading; 7 8 // данный класс синхронизирует доступ к целому числу 9 public class HoldintegerSynchronized 10 { 11 // буфер, совместно используемый потоком-производителем и 11а // потоком-потребителем 12 private int buffer = -1; 13 14 //occupiedBufferCount поддерживает счет занятых буферов 15 private in occupiedBufferCount = 0; 16 17 // свойство Buffer 18 public int Buffer 19 { 20 get 21 { 22 // получение блокировки на данный объект 23 Monitor.Enter(this); 24 25 // если нет данных для считывания, поместите активизирующий 26 // поток в состояние WaitSleepJoin 27 if (occupiedBufferCount — 0) 28 { 29 ConsoleWriteLine( 30 Thread.CurrentThread.Name + " tries to read. "); 31 32 Displaystate("Buffer empty. " + 33 Thread.CurrentThread.Name + " waits."); 34
360 Глава 11 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 91а 92 93 94 95 96 Monitor.Wait(this); } // указывает, что производитель может сохранить другое // значение, потому что потребитель получил значение буфера —occupiedBufferCount; Displaystate( Thread.CurrentThread.Name + ” reads " + buffer); // сообщить ожидающему потоку (если есть), что надо // подготовиться к исполнению (состояние Started) Monitor.Pulse(this); // Получить копию буфера перед сбросом блокировки. // Возможно, что процессор будет присвоен производителю // сразу после сброса монитора и до выполнения оператора // возврата. В этом случае производитель присвоит буферу // новое значение до возвращения оператором возврата // значения потребителю. Таким образом, потребитель // получит новое значение. Создание копии буфера и // возврат копии обеспечит // получение потребителем // нужного // значения. int buffercopy « buffer; // сброс блокировки на данном объекте Monitor.Exit(this); return bufferCopy; ) // конец get set { // получение блокировки для данного объекта Monitor.Enter(this); // при отсутствии пустых ячеек поместите активизирующий // поток в состояние WaitSleepJoin if (occupiedBufferCount = 1) { ConsoleWriteLine ( Thread.CurrentThread.Name + " tries to write. "); Displaystate ("Buffer full. " + Thread.CurrentThread.Name + " waits. "); Monitor.Wait (this); . I // задание нового значения буфера buffer = value; // указывает, что производитель не может сохранить другое // значение, пока потребитель не получит текущее значение // буфера ++occupiedBufferCount; Displaystate( Thread.CurrentThread.Name + " writes " + buffer);
Организация многозадачной обработки 361 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 // сообщить ожидающему потоку (если есть) // подготовиться к исполнению (состояние Started) Monitor.Pulse(this); // сброс блокировки данного объекта Monitor.Exit(this); } // конец set ) // отображение текущей операции и состояния буфера public void Displaystate(string operation) { ConsoleWriteLine ("{0,-35}{1,-9}{2}\n", operation, buffer, occupiedBufferCount); } } // конец класса HoldlntegerSynchronized // Метод Producer класса Produce управляет потоком, // сохраняющим значения от 1 до 4 в sharedLocation class Producer { private HoldlntegerSynchronized sharedLocation; private Random randomSleepTime; // конструктор public Producer( HoldlntegerSynchronized shared, Random random) { sharedLocation = shared; randomSleepTime = random; } // сохранение значений 1-4 в объекте sharedLocation public void Produce() { // режим ожидания — произвольный интервал времени до 3000 мсек, // затем установка свойства Buffer объекта sharedLocation for (int count = 1; count <— 4; count++) I { Thread.Sleep(randomSleepTime.Next(1, 3000)); sharedLocation.Buffer = count; } Console.WriteLine(Thread.CurrentThread.Name + " done producing. \nTerminating " +• Thread.CurrentThread.Name + "."); } // конец метода Produce } // конец класса Producer 11 Метод Consume класса Consumer управляет потоком, повторяющимся 11 4 раза, и считывает значение из sharedLocation class Consumer { private HoldlntegerSynchronized sharedLocation; private Random randomSleepTime; // конструктор public Consumer( HoldlntegerSynchronized shared, Random random)
362 Глава 11 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 { sharedLocation = shared; randomSleepTime = random; ) // четырехкратное считывание значения sharedLocation public void Consume() { int sum = 0;170 // получение текущего потока Thread current = Thread.CurrentThread; // режим ожидания — произвольный интервал времени до 3000 мсек, // затем'добавление значения свойства Buffer объекта // sharedLocation к сумме for (int count = 1; count <= 4; count++) { Thread.Sleep(randomSleepTime.Next(1, 3000)); sum += sharedLocation.Buffer; } Console.WriteLine(Thread.CurrentThread.Name + " read values totaling: " + sum + ".\nTerminating " + Thread.CurrentThread.Name + } // конец метода Consume } // конец класса Consumer // данный класс создает поток-производитель и поток-потребитель class SharedCell { 11 создание потока-производителя и потока-потребителя и их // запуск static void Main (string[] args) { // создание объекта, совместно используемого потоками HoldlntegerSynchronized holdinteger = new Holdintegersynchroni zed() // объект Random, используемый каждым потоком Random random = new Random(); // создание объектов Producer и Consumer Producer producer » new Producer(holdinteger, random); Consumer consumer = new Consumer(holdinteger, random) ; // заголовки столбца выхода и первоначальное состояние буфера ConsoleWriteLine (’’ {0, -35} {1, -9} {2} \п", "Operation", "Buffer", "Occupied Count"); holdinteger.Displaystate("Initial state"); • 11 создание потоков для производителя и потребителя //и задание делегатов для каждого потока Thread producerThread new Thread(new Threadstart(producer.Produce)); producerThread.Name = "Producer"; Thread consumerThread = new Thread(new Threadstart(consumer.Consume)); consumerThread.Name = "Consumer";
Организация многозадачной обработки 363 226 II запуск каждого потока 227 producerThread.Start(); 228 consumerThread.Start(); 229 230 } // конец метода Main 231 232 } // конец класса SharedCell Результат работы программы: Operation Buffer Occupied Count Initial state -1 0 Producer writes 1 1 1 Consumer reads 1 1 0 Consumer tries to read. Buffer empty. Consumer waits. 1 0 Producer writes 2 2 1 Consumer reads 2 2 0 Producer writes 3 3 1 Producer tries to write. Buffer full. Producer waits. 3 * 1 Consumer reads 3 3 0 Producer writes 4 4 1 Producer done producing. Terminating Producer. Consumer reads 4 4 0 Consumer read values totaling: Terminating Consumer. 10. / Operation Buffer Occupied Count Initial state -1 0 Consumer tries to read. Buffer empty. Consumer waits. -1 0 Producer writes 1 1 1 Consumer reads 1 1 0 Producer writes 2 2 1 Consumer reads 2 2 0 Producer writes 3 3 1 Producer tries to write. Buffer full. Producer waits. 3 1 Consumer reads 3 3 0 Producer writes 4 4 1 Producer done producing. Terminating Producer.
364 Глава 11 Consumer reads 4 4 О Consumer read values totaling: 10. Terminatxng Consumer. Operation Buffer Occupied Count > Initial state -1 0 Producer writes 1 1 1 Consumer reads 1 1 0 Producer writes 2 2 1 Consumer reads 2 2 0 Prdducer writes 3 3 1 Consumer reads 3 3 0 Producer writes 4 4 1 Producer done producing. Terminating Producer. Consumer reads 4 4 0 Consumer read values totaling: 10. Terminating Consumer. Класс HoldlntegerSynchronized (строки 9—115) содержит две переменные экземпляра— buffer (строка 12) и occupiedBufferCount (строка 15). Также процедуры доступа get (строки 20—67) и set (строки 60—196) свойст- ва Buffer теперь используют методы класса Monitor для синхронизации доступа к свойству Buffer. Таким обра- зом, каждый объект класса HoldlntegerSynchronized имеет SyncBlock для поддержки синхронизации. Перемен- ная экземпляра occupiedBufferCount известна как переменная условия', процедуры доступа свойства Buffer ис- пользуют это целочисленное значение в условиях для определения, чья очередь выполнять задачу: производителя или потребителя. Если occupiedBufferCount равна 0, тогда процедура доступа set свойства Buffer может поместить значение в переменную Buffer, потому что в текущий момент эта переменная не мо- жет содержать информации. Однако это означает, что процедура доступа get свойства Buffer в текущий мо- мент не может считать значение buffer. Если occupiedBufferCount равна 1, тогда процедура доступа get свой- ства Buffer может считать значение из переменной buffer, потому что в текущий момент эта переменная со- держит информацию. В этом случае процедура доступа set свойства Buffer не может поместить значение в buffer. Как и в примере листинга 11.2, поток-производитель выполняет задачи, поставленные в методе Produce объекта producer. Когда в строке 140 задается значение свойства Buffer класса HoldlntegerSynchronized, поток- производитель активизирует процедуру доступа set в строках 69—104. В строке 72 активизируется метод Enter класса Monitor для получения блокировки на объект HoldlntegerSynchronized. Оператор if в строках 76—85 определяет, равна ли переменная occupiedBufferCount единице. Если это условие истинно (true), то строки 78 и 79 выдают сообщение, указывающее, что поток-производитель делает попытку записи значения, а строки 81 и 82 активизируют метод Displaystate (строки 109—113) для выдачи другого сообщения, указывающего, *что буфер полон и поток-производитель находится в режиме ожидания. Строка 84 активизирует метод wait класса Monitor для помещения вызывающего потока (производителя) в состояние WaitSleepJoin для объекта HoldlntegerSynchronized и освобождает блокировку на этот объект. Состояние WaitSleepJoin для объекта под- держивается SyncBlock этого объекта. Теперь другой поток может активизировать метод процедуры доступа свойства Buffer класса HoldlntegerSynchronized. Поток-производитель остается в состоянии WaitSleepJoin до тех пор, пока не получит разрешения на исполне- ние; в этой точке поток возвращается в состояние Started и ожидает, пока система не присвоит ему процессор. Когда поток возвращается в состояние Running, он неявно повторно получает блокировку на объект HoldlntegerSynchronized, и процедура доступа set продолжает исполнение со следующего оператора после Wait. В строке 88 присваивается значение (value) переменной buffer. В строке 92 приращивается
Организация многозадачной обработки 365 occupiedBufferCount для указания на то, что совместно используемый буфер теперь содержит значение (т. е. потребитель может считывать значение, но производитель еще не может создать следующее значение). В стро- ках 94 и 95 активизируется метод Displaystate для вывода в консольное окно строки, извещающей, что произ- водитель записывает в buffer новое значение. В строке 99 активизируется метод Pulse класса Monitor с объек- том HoldlntegerSynchronized в качестве аргумента. Если в SyncBlock этого объекта есть ожидающие потоки, тогда первый ожидающий поток входит в состояние Started, указывая, что он может повторить попытку вы- полнения своей задачи (как только ему будет предоставлен процессор). Метод Pulse сразу возвращает управ- ление. В строке 102 активизируется метод Exit класса Monitor для сброса блокировки на объекте HoldlntegerSynchronized, а процедура доступа set возвращается вызывающему элементу. Распространенная ошибка программирования______________________________________________________ Если не сбросить блокировку на объекте, когда она больше не требуется, то будет иметь место логическая ошибка. Потоки программы, которым нужна блокировка, не смогут ее получить и, соответственно, начать испол- нение поставленных перед ними задач. Такие потоки будут вынуждены находиться в режиме ожидания (без не- обходимости, потому что ничто не использует блокировку). Такое ожидание может привести к взаимоблокировке и зависанию на неопределенное время. Процедуры доступа get и set реализуются подобным же образом. Как и в примере из листинга 11.2, поток- потребитель выполняет задачи, поставленные в методе Consume объекта consumer. Поток-потребитель получает значение свойства Buffer класса HoldlntegerSynchronized (строка 180) активизацией процедуры доступа get в строках 20—67. В строке 23 активизируется метод Enter класса Monitor для получения блокировки на объект HoldlntegerSynchronized. Оператор if в строках 27—36 определяет, равна ли переменная occupiedBufferCount нулю. Если это условие истинно (true), то в строках 29 и 30 выдается сообщение, указывающее, что поток-потребитель делает попытку считывания значения, а в строках 32 и 33 активизируется метод Displaystate для выдачи другого сообщения, указывающего, что буфер пуст и поток-потребитель находится в режиме ожидания. В строке 35 активизируется метод Wait класса Monitor для помещения вызывающего потока (потребителя) в состояние WaitSleepJoin для объекта HoldlntegerSynchronized и освобождает блокировку на этот объект. Теперь другой поток может акти- визировать метод процедуры доступа свойства Buffer класса HoldlntegerSynchronized. Поток-потребитель остается в состоянии WaitSleepJoin до тех пор, пока не получит разрешения на исполнение; в этой точке поток возвращается в состояние Started и ожидает, пока система не предоставит ему процессор. Когда поток возвращается в состояние Running, он неявно повторно получает блокировку на объект HoldlntegerSynchronized, и процедура доступа get продолжает исполнение со следующего оператора после Wait. В строке 40 occupiedBufferCount уменьшается для указания на то, что совместно используемый буфер теперь пуст (т. е. потребитель не может считывать значение, но производитель может поместить следующее значение в совместно используемый буфер). В строках 42 и 43 в консольное окно выводится строка, указываю- щая значение, считываемое потребителем, а в строке 47 активизируется метод Pulse класса Monitor с объектом HoldlntegerSynchronized в качестве аргумента. Если в SyncBlock этого объекта есть ожидающие потоки, тогда первый ожидающий поток входит в состояние Started, указывая, что он может повторить попытку выполнения своей задачи (как только ему будет присвоен процессор). Метод Pulse сразу возвращается. Перед сбросом бло- кировки в строку 60 вводится копия buffer. Возможно, что процессор производителю будет предоставлен сразу после сброса блокировки (строка 63) и до исполнения оператора return. В этом случае производитель присвоит новое значение переменной buffer до того, как оператор return возвратит это значение потребителю. Таким образом, потребитель получит новое значение. Создание копии buffer и возвращение этой копии гарантирует получение потребителем надлежащего значения. В строке 63 активизируется метод Exit класса Monitor для сброса блокировки на объекте HoldlntegerSynchronized, а процедура доступа set возвращает buffercopy вы- зывающему элементу. Изучите результаты выполнения программы из листинга 11.3. Обратите внимание, что каждое произведенное целое число считывается только один раз; значения не утеряны и ни одно из них не считывается более одного раза. Это достигается за счет того, что производитель и потребитель не могут выполнять поставленные перед ними задачи "вне очереди". Сначала должен исполняться производитель; потребитель должен дождаться записи производителя, потому что потребитель считал последнее значение. Производитель же должен дождаться счи- тывания потребителем последнего записанного значения. Выполните прогон данной программы несколько раз, чтобы убедиться в однократном считывании каждого записанного числа. В первом и втором выводе обратите внимание на строки, указывающие на то, когда производитель и потреби- тель должны дождаться разрешения на выполнение стоящих перед ними задач. В третьем выводе производи- тель и потребитель могли выполнять свои задачи без ожидания.
366 Глава 11 11.7. Отношение "производитель/потребитель": кольцевой буфер В листинге 11.3 применяется синхронизация потоков для гарантии того, что два потока будут корректно мани- пулировать данными в совместно используемом буфере. Однако не каждое программное приложение работает оптимально. Если два потока функционируют с разными скоростями, то один из них будет тратить больше (или большую часть) времени на ожидание. Например, в листинге 11.3 одно целое число было разделено между дву- мя потоками. Если поток-производитель создает значения быстрее, чем потребитель может их считать, тогда поток-потребитель ждет потребителя, потому что в памяти нет свободной ячейки для размещения следующего значения. Точно так же, если потребитель считывает значения быстрее, чем производитель их записывает, то наступает очередь потребителя ждать, пока производитель поместит в ячейку памяти очередное значение. Даже если потоки функционируют приблизительно с одинаковыми скоростями, то с течением времени их синхрони- зация может смещаться (нарушаться), в результате чего один поток, так или иначе, будет ожидать другой. Труд- но строить предположения об относительных скоростях асинхронных параллельных потоков: слишком велико число взаимодействий с операционной системой, с сетью, с пользователями и другими компонентами, которые могут вызвать сбой рабочих скоростей потоков. Когда такие случаи имеют место, тогда потоки входят в режим ожидания. Когда потоки ожидают друг друга, производительность программы снижается, она медленнее реаги- рует на команды пользователя, а сетевые приложения работают с продолжительными задержками из-за неэф- фективного использования процессора. Для сведения к минимуму времени ожидания потоков, использующих совместные ресурсы, и их функциониро- вания с одинаковыми сравнительными скоростями можно реализовать кольцевой буфер, обеспечивающий до- полнительные буферы, в которые производитель может помещать значения и из которых потребитель может их считывать. Предположим, что буфер реализован в виде массива. Производитель и потребитель работают с нача- ла этого массива. Когда любой из потоков достигает конца массива, он просто возвращается к первому элемен- ту массива для выполнения следующей задачи. Если в какой-то период времени производитель генерирует зна- чения быстрее, чем потребитель их считывает, то производитель может записывать новые значения в дополни- тельные буферы (при наличии ячеек). Это позволяет производителю выполнить свою задачу, несмотря на то, что потребитель не готов к считыванию текущего сгенерированного значения. И наоборот: если потребитель считывает значения быстрее, чем производитель их генерирует, то потребитель может считывать дополнитель- ные значения из буфера (при наличии таковых). Это позволяет потребителю выполнить свою задачу, несмотря на то, что производитель не готов к записи новых значений. Заметьте, что кольцевой буфер будет неуместен, если производитель и потребитель работают с разными скоро- стями. Если потребитель всегда исполняется быстрее, чем производитель, тогда достаточно буфера в одной ячейке: дополнительные будут напрасно занимать память. Если всегда быстрее исполняется производитель, то для размещения постоянно записываемых новых значений потребуется буфер с бесконечным числом ячеек. Ключом к использованию кольцевого буфера является его определение с достаточным количеством дополни- тельных ячеек для обработки предполагаемых "дополнительных” значений. Если с течением времени становит- ся понятно, что производитель постоянно генерирует в три раза больше значений, чем потребитель успевает считывать, тогда можно определить буфер минимум с тремя ячейками для обработки записываемых значений. Буфер не должен быть слишком маленьким, потому что в этом случае увеличится время ожидания потоков. С другой стороны, он не должен быть слишком большим, во избежание пустого расходования ресурсов памяти. Совет по повышению производительности________________________________________________ Даже при использовании кольцевого буфера может возникнуть ситуация, когда поток-производитель полностью заполняет буфер, что вынудит его дожидаться считывания потребителем значения с освобождением элемента буфера. Точно так же, если в какой-то момент времени буфер оказывается пустым, поток-потребитель должен ожидать записи очередного значения. Смысл использования кольцевого буфера— оптимизация его размера для сведения к минимуму времени ожидания потоков. В листинге 11.4 представлены производитель и потребитель с синхронизированным доступом к кольцевому буферу (в данном случае — совместно используемый массив из двух ячеек). В данной версии отношения "про- изводитель/потребитель" потребитель считывает значение, только если массив не пустой, а производитель запи- сывает значение, только если массив не полон. Программа реализована как Windows-приложение, отправляю- щее выходные данные в TextBox. Классы Producer (строки 174—210) и consumer (строки 213—252) выполняют те же задачи, что и в примерах, показанных в листингах 11.2 и 11.3, за исключением того, что сообщения выво- дятся в текстовое поле приложения. Операторы, создававшие и запускавшие объекты потоков в методах Main класса sharecell листингов 11.2 и 11.3, теперь появляются в классе circularBuffer (строки 255—313), где эти операторы выполняет обработчик событий Load (строки 278—311). Самые значительные отличия от листинга 11.3 имеют место в классе HoldintegerSynchronized, который теперь содержит шесть переменных экземпляра. Массив buffers — это трехэлементный целочисленный массив, пред-
Организация многозадачной обработки 367 ставляющий кольцевой буфер. Переменная occupiedBufferCount — переменная условия, которую можно ис- пользовать для определения, может ли производитель записывать значения в кольцевой буфер (т. е. occupiedBufferCount меньше числа элементов в массиве buffers), и может ли потребитель считывать значения из кольцевого буфера (т. е. occupiedBufferCount — больше 0). Переменная readLocation указывает положение, из которого следующее значение может быть считано потребителем. Переменная writeLocation указывает сле- дующую ячейку, в которую производитель может поместить значение. Вывод программы отображается в outputTextBox (элемент управления TextBox). 1 // Листинг 11.4: CircularBuffer.cs 2 // Реализация отношения "производитель/потребитель" 3 //с кольцевым буфером 4 5 using System; 6 using System.Drawing; 7 using System.Collections; 8 using System.ComponentModel; 9 using System.windows.Forms; 10 using System.Data' 11 using System.Threading; 12 13 // реализация совместно используемого целого числа с синхронизацией 14 public class HoldlntegerSynchronized 15 { 16 // каждый элемент массива — буфер 17 private int[] buffers = { -1, -1, -1 }; 18 19 // occupiedBufferCount поддерживает, подсчет занятых буферов 20 private int occupiedBufferCount = 0; 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 // переменные, поддерживающие положения буфера считывания и записи private int readLocation = 0, writeLocation =0; // компонент GUI для отображения результата private TextBox outputTextBox; // конструктор public HoldlntegerSynchronized(TextBox output) { outputTextBox = output; } // свойство Buffer public int Buffer { get { // блокирование объекта при получении значения //из массива буферов lock (this) { // если нет данных для считывания, поместить // активизирующий поток в состояние WaitSleepJoin if (occupiedBufferCount == 0) { outputTextBox.Text += "\r\nAll buffers empty. " + Thread.CurrentThread.Name + " waits."; outputTextBox.ScrollToCaret(); Monitor.Wait(this); }
368 Глава 11 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 ё9 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 П получение значения в текущем readLocation, затем // добавление строки, указывающей считанное значение // на выходе int readvalue = buffers[readLocation]; outputTextBox.Text += "\r\n" + Thread.CurrentThread.Name + " reads ” + buffers [readLocation] + " ”; •// значение считано, поэтому увеличивается число // занятых буферов —occupiedBufferCount; // обновление readLocation для будущей операции // считывания, затем добавление текущего, состояния на выходе readLocation = (readLocation +1) % buffers.Length; outputTextBox.Text += CreateStateOutput(); outputTextBox.ScrollToCaret(); // возвращение ожидающего потока (если имеется) // в состояние Started Monitor.Pulse (this); return readValue; ) // конец lock } // конец get set { // блокирование данного объекта с заданием значения //в массиве буферов lock (this) { // при отсутствии пустых ячеек, помещение // активизирующего потока в состояние WaitSleepJoin if (occupiedBufferCount ~ buffers.Length) { outputTextBox.text += "\r\nAll buffers full. ” + Thread.CurrentThread.Name + " waits." ; outputTextBox.ScrollTocaret(); Monitor.Wait (this); , ) // помещение значения в writeLocation буферов, затем // добавление строки, указывающей записанное значение //на выходе buffers[writeLocation] = value; outputTextBox.Text += "\r\n" + Thread.CurrentThread.Name + " writes " + buffers [readLocation] + " ”; // значение записано, поэтому увеличивается число // занятых буферов ++occupiedBufferCount; // обновление writeLocation для будущей операции // записи, затем добавление текущего состояния на выходе writeLocation = - 115 (writeLocation +1) % buffers.Length;
Организация многозадачной обработки 369 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 16.6 167 168 169 170 171 172 173 174 175 176 177 178 179 outputTextBox.Text += CreateStateOutputO; outputTextBox.ScrollToCaret(); // возвращение ожидающего потока (если имеется) // в состояние Started Monitor.Pulse(this); } // конец lock } // конец set } // конец свойства Buffer // создание состояния на выходе public string CreateStateOutput() { // отображение первой строки информации о состоянии string output = "(buffers occupied: " + occupiedBufferCount + ") \r\nbuffers: for (int i = 0; i < buffers.Length; i++) utput += " " + buffers [i] + " * output += "\r\n"; // отображение второй строки информации о* состоянии output += " for (int i = 0; i < buffers.Length; i++) output += "------"; output += "\r\n"; // отображение третьей строки информации о состоянии output += " // отображение индикаторов readLocation (R) и writeLocation (W) // под соответствую ними местоположениями буферов for (int i - 0; i < Duffers.Length; i++) I if (i — writeLocation && writeLocation == readLocation) output += " WR "; else if (i == writelocation) output += " W "; else if (i == readlocation) output += ” R "; else output += " "; output += "\r\n"; return output; ) / } // конец класса HoldintegerSynchronized // создание целых чисел от 11 до 20 и помещение их в буфер public class Producer { private HoldintegerSynchronized sharedLocation; private TextBox outputTextBox; private Random randomSleepTime; 24 Зак. 3333
370 Глава 11 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 233 235 236 237 238 239 240 241 // конструктор public Producer(HoldlntegerSynchronized shared, Random random, TextBox output) { sharedLocation = shared; outputTextBox = output; randomSleepTime = random; } // создание значений от 11 до 20 и помещение их //в буфер объекта sharedLocation public void Produce() { // режим ожидания — произвольный интервал времени до 3000 мсек, // затем установка свойства Buffer объекта sharedLocation for (int count = 11; count <» 20; count++) t { Thread.Sleep(randomSleepTime.Next(1, 3000)); sharedLocation.Buffer = count; } string name = Thread.CurrentThread.Name; outputTextBox.Text += "\r\n" + name + " done producing. \r\n" + name + " terminated.\r\n"; outputTextBox.ScrollTpCaret(); } // конец метода Produce } // конец класса Producer // считывание чисел от 12 до 20 из кольцевого буфера public class Consumer { private HoldlntegerSynchronized sharedLocation; private TextBox outputTextBox; private Random randomSleepTime; // конструктор public Consumer(HoldlntegerSynchronized shared, Random random, TextBox output) { sharedLocation = shared; outputTextBox = output; randomSleepTime = random; ) // считывание из буфера 10 чисел public void Consume () { int sum = 0; // повторение цикла 10 раз и режим ожидания — произвольный // интервал времени до 3000 мсек, затем прибавление // значения свойства Buffer объекта sharedLocation к сумме for (int count = 1; count <= 10; count++) { Thread.Sleep(randomSleepTime.Next(1, 3000)); sum += sharedLocation.Buffer; }
Организация многозадачной обработки 371 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 2981 299 300 301 302 303 304 305 306 string name = Thread.CurrentThread.Name; * outputTextBox.Text += "\r\nTotal " + name + " consumed: " + sum + ". \r\n” + name + " terminated.\r\n"; outputTextBox.ScrollTpCaret(); ) // конец метода Consume } // конец класса Consumer // задание потоков производителя и потребителя и их запуск public class Circular Buffer : Systern.Windows.Forms.Form { private System.Windows.Forms.TextBox outputTextBox; // требуется переменная проектировщика private System.ComponentModel.Container components = null; // конструктор без аргументов public CircularBuffer() { InitializeComponent(); } // код, сгенерированный Visual Studio .NET // основная точка входа для приложения [STAThread] static void Main() { Application.Run(new CircularBuffer()); } // обработчик событий Load создает и запускает потоки private void CircularBuffer_Load( object sender, System.EventArgs e) { // создание совместно используемого объекта HoldlntegerSynchronized sharedLocation = new HoldlntegerSynchronized(outputtextBox); // отображение состояния sharedLocation до начала исполнения // потоков производителя и потребителя outputTextBox.Text = sharedLocation.CreateStateOutput(); f/ объект Random, используемый каждым потоком Random random = new Random(); // создание объектов Producer и Consumer Producer producer = new Producer(sharedLocation, random, outputTextBox); Consumer consumer = new Consumer(sharedLocation, random, outputTextBox); // создание потоков и присвоение им имен Thread producerThread = new Thread(new Threadstart(producer.Produce)) ; producerThread.Name = "Producer"; Thread consumetThread = new Thread(new Threadstart(consumer.Consume)); consumerThread.Name = "Consumer";
372 Глава 11 307 // запуск потоков 308 producerThread.Start(); 309 consumerThread.Start(); 310 311 } конец метода CircularBuffer_Load 312 313 } // конец класса CircularBuffer Процедура доступа set (строки 83—125) свойства Buffer выполняет те же задачи, что и в примере, проиллюст- рированном в листинге 11.3, с некоторыми изменениями. Вместо использования методов Enter и Exit класса Monitor для получения и сброса блокировки на объекте HoldintegerSynchronized используется блок кода, перед которым вводится ключевое слово lock для блокирования объекта HoldintegerSynchronized. При входе управ- ления программы в блок lock текущий исполняющийся поток приобретает блокировку (предполагается, что она доступна) на объект HoldintegerSynchronized (т. е. this). Когда блок lock прекращает исполнение, поток авто- матически сбрасывает блокировку. • Распространенная ошибка программирования_____________________________________________________ При использовании методов Enter и Exit класса Monitor для управления блокировкой объекта метод Exit должен вызываться явно для сброса блокировки. Если в методе имеет место исключение до того, как Exit мо- жет быть вызван, и это исключение не захвачено, тогда метод может прекратить исполнение без вызова Exit. В этом случае блокировка не сбрасывается. Во избежание этой ошибки поместите код, который может выдать исключение, в блок try, а обращение к Exit — в соответствующий блок finally для гарантии того, что блоки- ровка будет сброшена. f Замечание по технологии программирования_____________________________________________________ Использование блока lock для управления блокировкой на синхронизированном объекте устраняет вероятность того, что пользователь забудет сбросить блокировку вызовом метода Exit класса Monitor. C# неявно вызывает метод Exit, когда блок lock по какой-либо причине прекращает исполнение. Таким образом, даже при возник- новении в блоке исключения, блокировка будет сброшена. Оператор if в строках 91—98 r процедуре доступа set определяет необходимость ожидания производителем (т. е. все буферы заполнены). Если поток-производитель должен подождать, то строки 93 и 94 добавляют текст в out put Text Box, указывая, что производитель ожидает выполнения своей задачи, а в строке 97 активизируется метод wait класса Monitor для помещения потока-производителя в состояние WaitSleepJoin объекта HoldintegerSynchronized. Когда в строке 102 исполнение возобновляется после оператора if, записанное про- изводителем значение помещается в кольцевой буфер в местоположении writeLocation. Затем в строках 104— 106 добавляется сообщение, содержащее значение, записанное в TextBox. В строке ПО выполняется прираще- ние объекта occupiedBufferCount, потому что теперь имеется минимум одно значение в буфере, которое потре- битель может считать. После этого в строках 114 и 115 обновляется writeLocation для следующего обращения к процедуре доступа set свойства Buffer. Вывод продолжается в строке 116 активизацией метода CreateStateOutput (строки 130—169), выводящего количество заполненных буферов, их содержимое и текущие writeLocation и readLocation. Наконец, в строке 121 активизируется метод Pulse класса Monitor для указания на то, что поток, ожидающий на объекте HoldintegerSynchronized (при наличии ожидающего потока), должен перейти в состояние Started. Обратите внимание, что при достижении правой закрывающей фигурной скобки блока lock В строке 123 ПОТОК сбрасывает блокировку объекта HoldintegerSynchronized. Процедура доступа get (строки 37—81) свойства Buffer выполняет те же задачи, что и в примере, приведенном в листинге 11.3, но с некоторыми изменениями. Оператор if в строках 45—52 в процедуре доступа get опреде- ляет необходимость ожидания потребителем (т. е. все буферы пусты). Если поток-потребитель должен подож- дать, то в строках 47 и 48 г outputTextBox добавляется текст, указывая, что потребитель ожидает выполнения своей задачи, а в строке 51 активизируется метод Wait класса Monitor для помещения потока-потребителя в со- стояние WaitSleepJoin объекта HoldintegerSynchronized. Здесь опять используется блок lock для сброса блоки- ровки на объекте HoldintegerSynchronized, вместо вызова методов Enter и Exit класса Monitor. Когда в стро- ке 56 исполнение возобновляется после оператора if, readvalue присваивается значение в местоположении readLocation в кольцевом буфере. В строках 58—60 добавляется сообщение, считанное в TextBox. В строке 64 occupiedBufferCount уменьшается, потому что теперь в буфере имеется минимум одна открытая позиция, в которую производитель может поместить очередное значение. После этого в строках 68 и 69 обновляется объ- ект readLocation для следующего обращения к процедуре доступа get свойства Buffer. Вывод продолжается в строке 70 активизацией метода CreateStateOutput, отображающего количество заполненных буферов, их со- держимое и текущие writeLocation и readLocation. Наконец, в строке 80 активизируется метод Pulse для пере- вода очередного потока, ожидающего объект HoldintegerSynchronized, в состояние Started, и в строке 82 счи- танное значение возвращается в вызывающий метод.
Организация многозадачной обработки 373 ।(buffers occupied: puffers: -1 -1 HR Ml buffers eapty. Producer writes 11 buffers: 11 -1 Cc&stner «alts, (buffers occupied: 1)'t' — ij Рис. 11.3. Демонстрация использования кольцевого буфера: а — опережения в считывании/записи нет; б — проявляется эффект кольцевого буфера на 4-м и 7-м значениях; в — проявляется эффект кольцевого буфера на 10-м значении; г — окончание вывода a W Consumer reads 11 (buffers occupied: 0) - buffers: 11 -1 HR Ml buffers eepty. Consuser waits. Producer writes 12 (buffers occupied: Ip buffers: 11 12 -1 Значение заменило последний буфер Следующее значение резервирует самый левый буфер Значение заменило последний буфер Следующее значение резервирует самый левый буфер Эффект кольцевого буфера: седьмое значение резервируется в самом левом буфере Эффект кольцевого буфера: четвертое значение резервируется в самом левом буфере К R Ccasuaer reads 19 (buffers occupied: 1) buffers: 20 19 19 R W Значение заменило последний буфер Следующее значение резервирует самый левый буфер Эффект кольцевого буфера: десятое значение резервируется в самом левом буфере Total Consider consumed: 155. 'bnsuaer temlnated. Ctxsuaer reads 20 (buffers occupied: 0) buffers: 20 IS 19
374 Глава 11 В листинге 11.4 данные вывода включают в себя текущий объект occupiedBufferCount, содержимое буферов и текущие writeLocation и readLocation. На выходе буквы W и R обозначают текущие writeLocation и readLocation соответственно. Обратите внимание, что после помещения третьего значения в третий элемент буфера четвертое значение вставляется в начало массива. Данная процедура обеспечивает эффект кольцевого буфера. Результат работы программы представлен на рис. 11.3. 11.8. Резюме Компьютеры выполняют все операции параллельно. Языки программирования предоставляют весьма упрощен- ный набор управляющих структур, позволяющих разработчикам выполнять одновременно только одну опера- цию и переходить к следующей лишь по завершении выполнения предыдущей Библиотека FCL предоставляет программистам C# возможность задавать в приложениях потоки исполнения, где каждый поток обозначает часть программы, которая может исполняться параллельно с другими потоками. Такая возможность называется многозадачностью. Поток инициализируется с помощью конструктора класса Thread, принимающего делегат Threadstart. Этот делегат указывает метод, содержащий задачи, которые будет выполнять поток. Поток пребывает в состоянии Unstarted до вызова метода start потока; при вызове этого метода поток входит в состояние Started. Поток в состоянии Started входит в состояние Running, когда система предоставляет потоку процессор. Система предос- тавляет процессор потоку Started с самым высоким приоритетом. Поток входит в состояние Stopped, когда его делегат Threadstart завершает или прекращает исполнение. Поток принудительно вводится в состояние Stopped при вызове метода Abort (этим или другим потоком). Поток Running входит в состояние Blocked, когда он выдает запрос на ввод/вывод. Поток Blocked переходит в состояние Started по завершении ожидаемого им ввода/вывода. Поток Blocked нс может использовать процессор даже при наличии последнего. Если поток должен войти в режим ожидания, он вызывает метод Sleep. Поток выходит из режима ожидания по истечении заданного времени ожидания. Если поток не может продолжать исполнение до прекращения испол- нения другого потока, то первый поток, называемый зависимым, вызывает метод Join другого потока для их "объединения". Когда два потока объединены, зависимый поток выходит из состояния WaitSleepJoin, когда дру- гой поток заканчивает исполнение. Когда поток сталкивается с кодом, который он не может запустить, поток вызывает метод wait класса Monitor до тех пор, пока не выполнятся определенные операции, которые обеспечат потоку продолжение исполнения. Вызов этого метода помещает поток в состояние WaitSleepJoin. Любой поток в состоянии WaitSleepJoin может выйти из него, если другой поток активизирует метод interrupt класса Thread на поток в состоянии WaitSleep- Join. Если поток вызывает метод Wait класса Monitor, соответствующий вызов метода Pulse или PulseAll клас- са Monitor другим потоком в программе переведет первоначальный поток из состояния WaitSleepJoin в состоя- ние Started. При вызове на потоке метода Suspend класса Thread этот поток входит в состояние Suspended. Поток выходит из состояния Suspended, когда другой поток активизирует метод Resume класса Thread на приостановленном (sus- pended) потоке. Каждый поток в C# имеет свой приоритет. В задачу планировщика потоков входит поддержание постоянного исполнения потоков с самым высоким приоритетом и, при наличии нескольких потоков с самым высоким при- оритетом, обеспечение исполнения всех потоков с одинаковым приоритетом за квант времени круговым мето- дом. Приоритет потока задается с помощью свойства Priority, которому присваивается значение из перечисле- ния ThreadProprity. Поток, обновляющий совместно используемые данные, вызывает метод Enter класса Monitor для получения блокировки этих данных. Поток обновляет данные и по завершении вызывает метод Exit класса Monitor. В то время как данные заблокированы, все прочие потоки, пытающиеся получить блокировку на эти данные, нахо- дятся в режиме ожидания. При вводе ключевого слова lock перед блоком кода блокировка устанавливается на указанный объект, когда программа входит в этот блок; блокировка сбрасывается, когда исполнение блока пре- кращается.
ГЛАВА 1 2 Строки, символы и регулярные выражения Король наш Генрих странным стал. И все веревочки жевал. Хилари Беллок Энергичное письмо лаконично. В предложениях не должно быть ненужных слов, а в абзацах — ненужных предложений. Уильям Странк мл. Это письмо получилось длиннее обычного, потому что у меня не было времени сделать его короче. Блез Паскаль Разница между почти верным и верным словами огромна: она подобна разнице между светляком и молнией. Марк Твен Молчание есть слово. Мигель де Сервантес Темы данной главы: □ создание неизменяемых объектов символьных строк класса string и манипуляции ими; □ создание изменяемых объектов символьных строк класса stringBuilder и манипуляции ими; □ использование регулярных выражений совместно с классами Regex и Match. 12.1. Введение В данной главе представлены возможности обработки строк и символов библиотеки классов .NET Framework, а также продемонстрировано использование регулярных выражений для поиска шаблонов в тексте. Описывае- мые в главе методики можно применять при разработке текстовых редакторов, текстовых процессоров, про- граммных пакетов верстки, автоматизированных систем текстового набора и других видов программного обес- печения обработки текстов. В предыдущих главах уже рассматривались некоторые особенности обработки строк. В данной главе эта информация изложена более подробно с уделением особого внимания возможностям класса string и типа char пространства имен System, класса StringBuilder пространства имен System.Text и классов Regex И Match пространства имен System.Text .RegularExpressions. 12.2. Основы понятий символов и строк Символы — основные строительные элементы исходного кода в С#. Каждая программа состоит из символов, из которых — при осмысленном их группировании — получается последовательность, рассматриваемая компиля- тором как серия команд, описывающих процесс выполнения той или иной операции. Помимо обычных симво- лов, программа можез содержать символьные константы. Символьной константой называется символ, пред- ставленный в виде целого числа, называемого кодом символа. Например, целое число 122 соответствует сим- вольной константе "Z". Целое число 10 соответствует символу новой строки ’\п'. Символьные константы определяются в соответствии с набором символов Unicode — международным набором символов, содержащим гораздо большее их количество, нежели набор символов ASCII (см. приложение 6). Подробности о стандарте Unicode представлены в приложении 7.
376 Глава 12 Строка— это серия символов, рассматриваемая как отдельная единица. Символы могут быть прописными, строчными, цифрами и различными специальными символами, такими как +, -, *, /, $ и др. Строка является объектом класса string пространства имен System1. Строковые литералы или строковые константы (часто называемые литералами string) записываются как последовательности символов в двойных кавычках: "John Q. Doe" "9999 Main Street" "Waltham, Massachusetts / "(201) 555-1212" Объявление может присвоить литерал string ссылке на string. Объявление string color = "blue"; инициализирует ссылку string — color — для обозначения объекта "blue" литерала string. Совет по повышению производительности________________________________________________________ Если в приложении объект одного литерала string встречается многократно, тогда на одну копию объекта ли- терала string будет делаться ссылка из каждого места программы, использующей этот строковый литерал. Так объект можно использовать совместно, потому что объекты строковых литералов неявно являются константами Подобное совместное использование резервирует память. Иногда строка string может содержать многочисленные символы обратной косой черты (часто это имеет место в имени файла). Управляющую последовательность можно исключить и интерпретировать все символы в string буквально с помощью символа @. Обратные косые в двойных кавычках считаются не управляющими последовательностями, а обычными символами обратной косой черты. Это часто упрощает процесс програм- мирования и делает код более удобочитаемым. Например, рассмотрим строку "C:\MyFolder\MySubFolder \MyFile.txt" со следующим присвоением: string file = "С:\\MyFolderWMySubFolderWMyFile.txt"; С помощью дословного строкового синтаксиса данное присвоение можно изменить на string file = @"С:\MyFolder\MySubFolder\MyFile.txt"; Такой подход обладает преимуществом распространения строк символов на много строк путем сохранения всех символов новой строки, пробелов и символов табуляции. 12.3. Конструкторы класса String Класс string предоставляет восемь конструкторов для инициализации string разными способами. В листин- ге 12.1 продемонстрировано использование трех конструкторов. У’Ч’Гу—...v' .....-.-.ryaiy‘У-Ч’ГТF••'"’’Л'”-* ^Листинг 12.1. Конструкторы String ' ’г 'Ж 1 // Листинг 12.1: Stringconstructor.cs 2 // Демонстрация конструкторов класса String 3 4 using System; 5 using System.Windows.Forms; 6' 7 // тестирование нескольких конструкторов класса String 8 class Stringconstructor 9 { • 10 // основная точка входа для приложения * 11 [STAThread] 12 static void Main(string[] args) 13 { 14 string output; 15 string originalstring, stringl, string2, 16 string3, string4; 17 1 C# предоставляет ключевое слово string в качестве псевдонима класса String. В данной книге String используется для обо- значения класса String, a string — для обозначения объекта класса String.
Строки, символы и регулярные выражения 377 18 char[] characterArray - 19 { ’b*, 'i', 'r', 't', 'h', ' 'd', 'a', ’y' }; 20 21 // инициализация строки 22 originalstring = "Welcome to C# programming!"; 23 stringl = originalstring; 24 string2 = new string(characterArray); 25 string3 = new string(characterArray, 6, 3); 26 string4 = new string('C, 5) ; 27 28 output = "stringl = " + "\"" + stringl + "\"\n" + 29 "string2 = " + + string2 + "\"\n" + 30 "string3 = " + "\"" + string2 + "\"\n" + 31 ”string4 = " + "\"" + string2 + "\”\n"; 32 33 MessageBox.Show(output, "String Class Constructors", 34 MessageBoxButtins.OK, MessageBoxIcon.Ibformation); 35 36 } // конец метода Main 37 38 } // конец класса Stringconstructor Рис. 12.1. Демонстрация конструктора класса String В строках 14—16 объявляются выходные данные строк output, originalstring, stringl, string2, string3 и string4. В строках 18 и 19 распределяется массив char— characterArray,— содержащий девять символов. В строке 22 присваивается строковый литерал "Welcome to C# programming!" строковой ссылке originalstring. В строке 23 stringl задается как ссылка на тот же литерал string. Замечание по технологии программирования________________'__________________________________ В большинстве случаев нет необходимости создавать копию существующей строки. Все строки — неизменяе- мые, т. е после создания строк содержащиеся в них символы изменить нельзя. Также при наличии одной или нескольких ссылок на string (или на любой интересующий объект) данный объект не может быть отправлен в "мусор". В строке 25 string-З присваивается новая строка с помощью конструктора string, принимающего массив char и два аргумента int. Второй аргумент задает начальную позицию индекса (смещение), из которого копируются символы в массиве Третий аргумент задает число символов (подсчет) для копирования из заданной в массиве начальной позиции. Если заданное смещение или подсчет указывают, что программа должна осущест- вить доступ к элементу за пределами границ массива символов, тогда генерируется исключение ArgumentOutOfRangeException. В строке 26 string4 присваивается новая строка с помощью конструктора string, принимающего в качестве аргументов символ и переменную типа int, указывающую количество раз повторения данного символа в строке. Результат работы программы представлен на рис. 12.1. 12.4. Индексатор класса String, свойство Length и метод СоруТо В приложении из листинга 12.2 представлен индексатор string, упрощающий извлечение любого символа из строки, и свойство Length класса string, возвращающее длину строки. Метод СоруТо класса string копирует заданное число символов из строки в массив char. В данном примере создается приложение, которое определяет длину строки, располагает символы в ней в об- ратном порядке и копирует серию символов из строки в массив символов. 1 // Листинг 12.2: StringMethods.cs 2 // Использование индексатора, свойства Length и метода СоруТо 3 // класса String 4 5 using System; 6 using System.Windows.Forms; 7
378 Глава 12 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 // создание строковых объектов и отображение результатов // использования индексатора, свойства Length и метода СоруТо class StringMethods ( // главная точка входа для приложения [STAThread] static void Main(string[] args) { string stringl, output; char[] characterArray; stringl = "hello there"; characterArray = new char[5]; // строка вывода output = "stringl: \"" + stringl + "\""; // проверка свойства Length output += "\nLength of stringl: " + stringl.Length; // прохождение символов в stringl и отображение // в обратном порядке output += "\nThe string reversed is: "; for (int i = stringl.Length - 1; i >= 0; i—) output += stringl[i]; Рис. 12.2. Демонстрация индексатора класса String, свойства Length и метода СоруТо // копирование символов из stringl в stringl.СоруТо(0, characterArray, 0, output += "\nThe character array is: characterArray 5); for (int i = 0 ; i < characterArray.Length; i++) output + = characterArray[i]; MessageBox.Show(output, "Demonstrating the string " + "Indexer, Length Property and СоруТо Method", MessageBoxButtons.OK, MessageBoxIcon.Information); } // конец метода Main } // конец класса StringMethods В строке 27 используется свойство Length класса string для определения количества символов в строке stringl. Подобно массивам, строки всегда имеют представление о собственных размерах. В строках 33 и 34 в output добавляются символы строки stringl в обратном порядке. Индексатор string воз- вращает символ в заданную позицию строки. Индексатор string рассматривает строку как массив, состоящий из данных типа char. Индексатор получает целочисленный аргумент как число позиции и возвращает символ в эту позицию. Как и в массивах, считается, что первый элемент строки находится в позиции 0. Распространенная ошибка программирования_____________________________________________________ Результатом попыток доступа к символу, находящемуся за пределами строки (т. е. если индекс меньше нуля, либо больше или равен длине строки), становится исключение IndexOutofRangeException. В строке 37 используется метод СоруТо класса string для копирования символов строки (stringl) в массив символов (characterArray). Первым аргументом, переданным в метод СоруТо, является указатель, с которого метод начинает копировать символы в строку. Второй аргумент— массив символов, в который копируются символы. Третий аргумент — это указатель, задающий местоположение для размещения в массиве скопирован- ного символа. Последний аргумент— число символов, которые метод скопирует из строки. В строках 40 и 41 добавляется содержимое массива char в string output, по одному символу. Результат работы программы представлен на рис. 12.2.
Строки, символы и регулярные выражения 379 12.5. Сравнительный анализ строк Следующие два примера демонстрируют различные методы, предоставляемые C# для сравнения строк. Для по- нимания того, как одна строка может быть "больше" или "меньше" другой, рассмотрим процесс расположения в алфавитном порядке серии фамилий. Без сомнения, фамилия Jones будет помещена перед фамилией Smith, по- тому что первая буква е слове Jones по алфавиту идет перед первой буквой слова Smith. Алфавит — это не про- сто набор из 26 букв (английского); это упорядоченный список символов, в котором каждая буква находится в определенной позиции. Например, z больше, чем просто буква алфавита; z — двадцать шестая буква английско- го алфавита. Компьютеры могут располагать символы в алфавитном порядке, потому что они имеют внутреннее представле- ние в виде числовых кодов системы Unicode. При сравнении двух строк C# просто сравнивает числовые коды символов в строке. Класс string предоставляет несколько способов сравнения строк. В листинге 12.3 показано использование ме- тодов Equals и Сошрагето и оператора сравнения (=). Листинг 12.3. Проверка Stri^ для определения рянннства ................;;............:>;.р... ............. х;.„ — .. л... / 1 // Листинг 12.3: StringCompare.cs 2 // Сравнение строк 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 using System; using System.Windows.Forms; // сравнение некоторого числа строк class StringCompare { // главная точка входа для приложения [STAThread] static void Main(string[] args) { string stringl = "hello"; string string2 = "good bye"; string string3 = "Happy Birthday"; string string4 = "happy birthday"; string output; // выходные значения четырех цтрок output = "stringl = V’" + stringl + + "\nstring2 = \"" + string2 + "\"" + "\nstring3 = \"" + string3 + "\"" + "\nstring4 = \B" + string4 + "\"\n\n"; // проверка на равенство с помощью метода Equals if (string.Equals("hello")) output == "stringl equals \"hello\"\n"; else output += "stringl does not equal \"hello\"\n"; // проверка на равенство с помощью — if (stringl += "hello") output += "stringl equals \"hello\"\n"; else output += "stringl does not equal \"hello\"\n"; // проверка на равенство сравнением регистра if (String.Equals(string3, stripg4()) output += "string3 equals string4\n"; else output +«= "string3 does not equal string4\n";
380 Глава 12 44 // проверка СошрагеТо 45 output += "\nstringl.CompareTo( string2 ) is " + 46 stringl.СошрагеТо( string2 ) + "\n" + 47 "string2.CompareTo( stringl ) is " + 48 string2.СошрагеТо( stringl ) + ”\n" + 49 "stringl.СошрагеТо( stringl ) is " + 50 stringl.СошрагеТо( stringl ) + "\n” + 51 "string3.СошрагеТо( string4 ) is " + 52 string3.CompareTo( string4 ) + "\n* + 53 "string4.СошрагеТо( string3 ) is " + 54 string4.CompareTo( string3 ) + "\n\n"; 55 56 MessageBox.Show(output, "Demonstrating string " + 57 "Comparisons", MessageBoxButtons.OK, 58 MessageBoxIcon.Information); 59 60 } // конец метода Main 61 62 } // конец класса StringCompare Demonstrating string с ич ш.л» stmgl - "Mo" string2-"goodbye" bs й str ng3-"Happy Birthoay" « string*- Sappy birthday" str.ijl squat "Mb" string! aquae"helb* r strings does not equal string* IIV’1, .-'J.- ,’.4^ stringl .CtxnpareTot string?) Is I string?.CompareTof stringl )b-l strngl.CompareTo( stringl )ls 0 strings.СотрагеГо( string* )Js 1 *mng*.tonpar<5Toi strings) is -I Sa, ——— Рис. 12.3. Сравнение строк Условие в операторе if (строка 27) использует метод экземпляра Equals для сравнения stringl и литерала string "hello" для определения их равенства. Метод Equals (унаследованный классом string из класса object) проверяет любые два объекта на равенство (т. е. проверяет, содержат ли эти объекты идентичные ком- поненты). Метод возвращает true, если объекты равны; в противном случае — false. В данном случае предше- ствующее условие возвращает true, потому что stringl делает ссылку на объект "hello" строкового литерала. Метод Equals использует лексикографическое сравнение — целочисленные значения Unicode, представляющие символы в каждой строке. Сравнение строки "hello" со строкой "hello" возвратит false, потому что числовое представление строчных букв отличается от числового представления соответствующих им прописных букв. Условие во втором операторе if (строка 33) использует оператор сравнения (==) для сравнения строки stringl со строкой литерала "hello" на предмет их равенства. В C# оператор равенства также базируется на лексико- графическом сравнении двух строк. Таким образом, условие в операторе if оценивается как true, потому что значения stringl и "hello" равны. Для сравнения ссылок двух строк необходимо явно преобразовать строки в тип object и использовать оператор равенства (==). Представляем проверку строк на равенство между string3 и string4 (строка 39) для иллюстрации того, что сравнения, на самом деле, зависят от регистра. Здесь static-метод Equals (в отличие от метода экземпляра в строке 27) используется для сравнения значений двух строк. Строка "Happy Birthday" не равна строке "happy birthDay", поэтому условие оператора if не срабатывает, и к сообщению на выходе (строка 42) добавляется сообщение "string3 does not equal string4". Для сравнения в строках 46—54 применяется метод СошрагеТо класса string. Метод СошрагеТо возвращает: значение о, если строки равны; -1, — если строка, активизирующая СошрагеТо, меньше строки, переданной в качестве аргумента; и 1, если строка, активизирующая метод СошрагеТо, больше строки, переданной в качестве аргумента. Метод СошрагеТо использует лексикографическое сравнение. Обратите внимание, что СошрагеТо рассматривает strings как строку, большую, нежели string4. Единственное отличие между этими двумя строками заключается в том, что string3 содержит две прописные буквы. Данный пример наглядно доказывает, что прописная буква имеет большее значение в наборе символов Unicode, нежели соответствующая ей строчная буква. Результат работы программы представлен на рис. 12.3. В листинге 12.4 показан метод проверки, начинается ли экземпляр string с конкретной строки и заканчивается ли ею. Метод startswith определяет, начинается ли экземпляр string с текстовой строки, переданной ему в качестве аргумента. Метод EndsWith высняет, заканчивается ли экземпляр string текстовой строкой, передан- ной ему в качестве аргумента. Метод Main приложения stringStartEnd определяет массив строк с именем strings, содержащий значения "started", "starting", "ended1’ и "ending". Оставшаяся часть метода Main про- веряет элементы массива для определения того, начинаются они с конкретного набора символов или заканчи- ваются им. 1 // Листинг 12.4: StringStartEnd.cs 2 // Демонстрация методов StartsWith и EndsWith 3
Строки, символы и регулярные выражения 381 4 5 б 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 using System; using System.Windows.Forms; // проверка StartsWith и EndsWith class StringStartEnd { // главная точка входа для приложения [STAThread] static void Main(string[] args) { string[] strings = { "started", "starting", "ended", "ending" }; string output = *1 Demonstrating StartsWith and EndsWith methods started” ends wih "ed” ended" ends with ”ed" "started" starts with "st* Starting" starts with "st" Рис. 12.4. Демонстрация методов StartsWith и EndsWith // проверка каждой строки на предмет начала с символов "st" for (int i = 0 ; i < strings.Length; i++) if (strings[i].StartsWith("st")) output += "\"" + strings[i] + "\"" + " starts with \"st\"\n"; output += "\n"; // проверка каждой строки на предмет окончания символами "ed" for (int i = 0 ; i < strings.Length; i++) if (strings[i].EndsWith("ed")) » output += "\"" + strings[i] + "\"" + " ends with \"ed\"\n"; MessageBox.Show(output, "Demonstrating StartsWith and " + "EndsWith methods", MessageBoxButtons.OK, MessageBoxIcon.Information); } // конец метода Main } // конец класса StringStartEnd В строке 21 используется метод StartsWith, принимающий аргумент типа string. Условие в операторе if оп- ределяет, начинается ли строка с индексом i в массиве с символов "st". Если да, тогда метод возвращает true и добавляет strings [i] в string output для отображения. В строке 30 использован метод EndsWith, также принимающий аргумент типа string. Условие в операторе if определяет, заканчивается ли строка с индексом i в массиве символами "ed". Если да, тогда метод возвращает true И добавляет strings [i] в string output ДЛЯ отображения. Результат работы программы представлен на рис. 12.4. 12.6. Метод GetHashCode класса String Часто строки и другие типы данных необходимо сохранять так, чтобы информацию можно было быстро найти. Одним из лучших способов является сохранение данных в хэш-таблицах. Хэш-таблица (hash table) сохраняет объект выполнением на нем определенных вычислений с созданием хэш-кода (hash code). После этого объект сохраняется в некотором месте хэш-таблицы, определенном рассчитанным хэш-кодом. Когда программе необ- ходимо получить информацию, выполняется то же вычисление с генерированием того же хэш-кода. В хэш- таблице можно сохранить любой объект. Класс Object определяет метод GetHashCode для выполнения расчета хэш-кода. Несмотря на то, что все классы наследуют этот метод из класса object, рекомендуется, чтобы они подменяли реализацию по умолчанию класса Object. Класс string подменяет (override) метод GetHashCode для обеспечения хорошего распределения хэш-кода, исходя из содержимого строки. Более подробно хэширование рассматривается в главе 20. В листинге 12.5 показано применение метода GetHashCode к двум строкам ("hello" и "Hello"). Здесь значения хэш-кода для каждой строки различны. Впрочем, не идентичные строки могут иметь одинаковое значение хэш-кода.
382 Глава 12 1 // Листинг 12.5: StringHashCode.cs 2 // Демонстрация метода GetHashCode класса String 3 4 . using System; 5 using System.Windows.Forms; 6 7 // проверка метода GetHashCode 8 class StringHashCode 9 { 10 // главная точка входа для приложения 11 [STAThread] 12 static void Main(string[] args) 13 { 14 15 string stringl = "hello"; 16 string string2 = "Hello";- .17 string output; 18 19 output = "The hash code for \"" + stringl + 20 "\" is " + stringl.GetHashCode() + "\n"; 21 22 output += "The hash code for \"" + string2 + 23 "\" is " + string2.GetHashCode() + "\n"; 24 25 MessageBox.Show(output, "Demonstrating String " + 26 "method GetHashCode", MessageBoxButtons.OK, 27 MessageBoxIcon.Information); 28 ) 29 } // конец метода Main 30 31 } // конец класса StringHashCode Рис. 12.5. Демонстрация метода GetHashCode Результат работы программы представлен на рис. 12.5. 12.7. Расположение символов и подстрок в классе String Случается, что во многих программных приложениях необходимо найти символ или набор символов. Напри- мер, программист, пишущий текстовый процессор, хочет ввести в него функции поиска документов. В листин- ге 12.6 показано несколько из многих версий методов класса string: indexOf, indexOfAny, LastindexOf и LastindexOfAny, осуществляющих поиск в строке заданного символа или подстроки. (Результат работы про- граммы представлен на рис. 12.6.) Весь поиск в данном примере по строке letters (инициализированной зна- чением "abcdefghijklmabcdefghijklm") Организован В методе Main класса StringlndexMethods. --.--....................................... Листинг 12.6. Использование методов поиска класса string .. «is... ............... ... ________..... 1 // Листинг 126: StringlndexMethods.cs 2 // Использование методов поиска класса String 3 4 using System; 5 using System.Windows.Forms; / 6 7 // проверка функций индексирования строк 8 class StringlndexMethods 9 { 10 // основная точка входа для приложения 11 [STAThread] 12 static void Main(string[] args) 13 { 14 string letters = "abcdefghijklmabcdefghijklm";
Строки, символы и регулярные выражения 383 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 string output = ""; char[] searchLetters = { ' с', ' a', '$' }; // проверка IndexOf для расположения символа в строке output + = "'с' is located at index " + letters.IndexOf('c’); output + = "Xn'a1 is located at index " + letters.IndexOf(’a', 1) ; output + = "\n’$' is located at index " + letters.IndexOf('$’, 3, 5); // проверка LastlndexOf для нахождения символа в строке output + - "\n\nLast ’с’ is located at " + "index " + letters.LastIndexOf('c'); output + = "\nLast 'a* is located at index " + letters.IndexOf('a', 25); output + = "\nLast '$' is located at index " + letters.LastlndexOf(1 $', 15, 5); // проверка IndexOf для обнаружения подстроки в строке output + = "\n\n\"def\" is located at” + " index " = letters.IndexOf("def"); output + = "\n\n\"def\" is located at index " + letters.IndexOf("def", 7); output + = "\n\"hello\" is located at index " + letters.IndexOf("hello", 5, 15); Рис. 12.6. Демонстрация методов поиска подстрок // проверка LastlndexOf для обнаружения в строке подстроки output + = "\n\nLast \"def"\" is located at index " + letters.LastlndexOf("def"); output + = "\nLast \"def\" is located at " + letters.LastlndexOf("def", 25); output + = "\nLast \"hello\" is located at index " + letters.LastIndexOf("hello", 20, 15); // проверка IndexOf Any для нахождения места первого // появления символа в массиве output + = "\n\nFirst occurrence of ’с', 'а', ’$’ is " + "located at " + letters.IndexOf Any(searchLetters); output + = "\nFirst occurrence of 'c', 'a' or '$’ is " -H "located at " + letters.IndexOf Any(searchLetters, 7); output + = "\nFirst occurrence of 'c', 'a' or '$’ is " + "located at " + letters.IndexOf Any(searchLetters, 20, 5); // проверка IndexOf Any для нахождения места // последнего появления символа в массиве output + = "\n\nLast occurrence of 'с', 'a' or ’$' is " + "located at " + 1etters.LastIndexOf Any(searchLetters); output + = "\nLast occurrence of 'c', 'a' or '$' is " + "located at " + letters.LastIndexOf Any(searchLetters, 1);
384 Глава 12 77 output +'= "\nLast occurrence of ’c', 'a' or '$* is ’* + 78 "located at " + letters.LastIndexOf Any 79 (searchLetters, 25, 5); 80 81 MessageBox.Show(output, 82 "Demonstrating class index methods", 83 MessageBoxButtons.pk, MessageBoxIcon.Informat ion), 84 85 } // конец метода Main 86 87 } // конец класса StringlndexMethods В строках 20, 23 и 26 для обнаружения места первого появления в строке символа или подстроки используется метод IndexOf. Если IndexOf обнаруживает символ, то он возвращает номер заданного символа в строке; в про- тивном случае IndexOf возвращает значение -1. В выражении строки 23 используется версия метода IndexOf, принимающего два аргумента: символ, который нужно найти, и начальный индекс, с которого должен начаться поиск строки. Метод не рассматривает символы, появляющиеся до начального указателя (в данном случае 1). В выражении в строке 26 используется другая версия метода IndexOf, принимающая три аргумента: символ, который нужно найти, индекс, с которого следует начать поиск, и количество искомых символов. В строках 30, 33 и 36 для обнаружения места последнего появления в строке символа применяется метод Lastindexof. Он выполняет поиск с конца строки к ее началу. Если Lastindexof обнаруживает символ, то воз- вращает номер заданного символа в строке; в противном случае IndexOf возвращает значение -1. Существуют три версии Lastindexof, осуществляющих поиск символа в строке. Выражение в строке 33 использует версию метода IndexOf, принимающего два аргумента: символ, который нужно найти, и самый верхний индекс, с кото- рого должен начаться обратный поиск символа. В выражении в строке 36 представлена третья версия метода Lastindexof, принимающего три аргумента, символ, который нужно найти, начальный индекс, с которого сле- дует выполнить обратный поиск, и количество искомых символов (часть строки). В строках 40—56 используются версии методов IndexOf и Lastindexof, принимающие в качестве первого аргу- мента строку, а не символ. Эти версии методов выполняются идентично описанным выше, за исключением то- го, что они осуществляют поиск последовательностей символов (или подстрок), указанных аргументами. В строках 61—79 используются методы IndexOf Any и LastIndexOfAny, принимающие в качестве первого аргу- мента массив символов. Эти версии методов также выполняются аналогично описанным выше, за исключением того, что они возвращают индекс первого появления любого из символов, указанных в аргументе метода, в мас- сиве символов. Распространенная ошибка программирования__________________________________________________ В перегруженных методах Lastindexof и LastIndexOfAny, принимающих три параметра, второй аргумент все- гда должен быть больше третьего аргумента или равным ему. Это может показаться нелогичным, но следует помнить, что поиск осуществляется с конца строки к ее началу. 12 .8. Извлечение подстрок из строк Класс string предоставляет два метода Substring, используемых для создания новой строки копированием час- ти существующей строки. Каждый метод возвращает новую строку. Приложение в листинге 12.7 предлагает использование обоих методов. Листинг 12.7. Подстроки, сгенерированные из строк £ 1 // Листинг 12.7: Substring.cs 2 // Демонстрация метода Substring класса String 3 4 using System; 5 using System.Windows.Forms; 6 7 // создание подстрок 8 class SubString 9 { 10 // основная точка входа для приложения 11 [STAThread] 12 static void Main(string[] args)
Строки, символы и регулярные выражения 385 13 { 14 string letters = "abcdefghijklmabcdefghijklm"; 15 string output = 16 17 // активизация метода Substring и передача в него одного 17а // параметра 18 output +» "Substring from index 20 to end is \"" + 19 letters.Substring(20) + "\"\n"; 20 21 // активизация метода Substring и передача в него двух 21а // параметров 22 output += "Substring from index 0 to 6 is \"" + 23 letters.Substring(0, 6) + 24 2 5 MessageBox.Show(output, 26 "Demonstrating String method Substring", 27 MessageBoxButtons.OK, MessageBoxIcon.Information); 28 29 } // конец метода Main 30 31 } // конец класса SubString Рис. 12.7. Демонстрация метода Substring Оператор в строке 19 использует метод Substring, принимающий один аргумент типа int. Данный аргумент задает начальный индекс, с которого метод будет копировать символы из строки-оригинала. Возвращенная под- строка содержит копии символов от начального индекса до конца строки. Если заданный в аргументе индекс находится за пределами строки, тогда программа выдает исключение ArgumentOutOfRangeException. Вторая версия метода Substring (строка 23) принимает два аргумента типа int. Первый аргумент задает на- чальный индекс, с которого метод будет копировать символы из строки-оригинала. Второй аргумент задает длину копируемой подстроки. Возвращенная подстрока содержит копии заданных символов из строки- оригинала. Результат работы программы представлен на рис. 12.7. 12 .9. Сцепление строк Использование операции + (см. главу 2) — не единственный способ выполнения сцепления строк (конкатена- ции). static-метод Concat класса string (листинг 12.8) сцепляет две строки и возвращает новую строку, содер- жащую объединенные символы обеих строк-оригиналов. В строке 23 символы из string2 добавляются в конец stringl с помощью метода Concat. Оператор в строке 23 не изменяет строки-оригиналы. Результат работы программы представлен на рис. 12.8. 1 // Листинг 12.8: SubConcatenation,cs 2 // Демонстрация метода Concat класса String 3 * , 4 using System; 5 using System. Windows.Forms; 6 7 // сцепление строк с помощью метода Concat класса String 8 class Stringconcatenation j 9 { 10 // оснрвная точка входа для приложения 11 [STAThreacfj 12 static void Main (string [] args). 13 { 14 string stringl = "Happy"; 15 string string2 = "Birthday"; 16 string output;17 17 Рис. 12.8. Демонстрация сцепления строк 25 Зак. 3333
386 Глава 12 18 output = "stringl = \"" + stringl + "\"\n" + 19 "string2 + \"" + string2 + "\""; 20 21 output += 22 "\n\nResult of String.Concat( stringl, string2 ) + " + 23 String.Concat(stringl, string2); 24 25 output += "\nstringl after concatenation = " + stringl; 26 27 Mes sageBox.Show(output, 28 "Demonstrating String method Concat", 29 MessageBoxButtons.OK, MessageBoxIcon.Information); 30 31 } // конец метода Main 32 33 } If конец класса Stringconcatenation 12 .10. Прочие методы класса String Класс string предоставляет несколько методов, возвращающих копии строк. Приложение в листинге 12.9 де- монстрирует использование этих методов, включающее методы Replace, ToLower, ToUpper, Trim и Tostring класса string. (Результат работы программы представлен на рис. 12.9.) * Листинг 12.&. Методы Replace, То LlSwer, ToUpper, Trin и То String класса String I... . St.*...: ........is—..............\............ i.. .'Л..;2.;..'...X.w.S... 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 // Листинг 12.9: StringMiscellaneous.es // Демонстрация методов Replace, ToLower, ToUpper, Trim //и ToString класса String using System; using System.Windows.Forms ; // создание строк с помощью методов Replace, ToLower, ToUpper, class StringMethods2 { // основная точка входа для приложения [STAThread] static void Main(string[] args) { string stringl = "cheers!"; string string2 = "GOOD BYE "; string string3 = " spaces "; string output; output = "stringl = \"" + stringl + "\" \n” + "string2 = \"" + string2 + "\"\n" + "string3 = \"" + string3 + "\"" ; // вызов метода Replace output + = "\n\nReplacing \"e\" with \"E\" in stringl: \"" + stringl.Replace('e', 'E') + "\""; Trim Рис. 12.9. Демонстрация методов Replace. ToLower, ToUpper. Trim и ToString // вызов методов ToLower и ToUpper output += "\n\nstringl.ToUpper() + \w" + stringl.ToUpper() + "\"\nstring2.ToLower() = \"" + string2.ToLower() + "\""; // вызов метода Trim output += "\n\nstring3 after trim = \"" + string3.Trim() + "\"";
Строки, символы и регулярные выражения 387 \ 38 // вызов метода Tostring 39 output += "\n\nstringl = \"" + stringl.ToStringO + 40 41 MessageBox.Show(output, 42 "Demonstrating various string method", 43 MessageBoxButtons.OK, MessageBoxIcon.Information); 44 45 ) // конец метода Main 46 47 } // конец класса StringMethods2 В строке 27 используется метод Replace класса string для возвращения новой строки с заменой каждого появ' ления в stringl символа ' е' на символ ' Е'. Метод Replace принимает два аргумента: строку, для которой будет осуществляться поиск, и строку, на которую будут заменены все появления первого аргумента. Строка-оригинал остается неизменной. Если первый аргумент в строке не появляется, тогда метод возвращает строку-оригинал. Метод ToUpper класса string генерирует новую строку (строка 31), заменяющую все строчные буквы в stringl на их прописные эквиваленты. Этим методом возвращается новая строка, содержащая преобразованную строку; строка-оригинал остается неизменной. При отсутствии букв для преобразования в прописные метод возвращает строку-оригинал. В строке 32 используется метод ToLower класса string для возвращения новой строки, в кото- рой все прописные буквы в string2 заменяются на их строчные эквиваленты. Строка-оригинал остается неиз- менной. Как и в случае с ToUpper, при отсутствии букв для преобразования в строчной формат метод ToLower возвращает строку-оригинал. В строке 36 используется метод Trim класса string для удаления всех символов пробела, появляющихся в нача- ле и конце строки. Без каких бы то ни было других изменений строки-оригинала метод возвращает новую стро- ку, но опускает лидирующие или замыкающие пробелы. Другая версия метода Trim принимает массив символов и возвращает строку, не содержащую символов, указанных аргументе. В строке 39 используется метод Tostring класса string для демонстрации того, что различные методы, исполь- зованные в приложении, не изменяли stringl. Зачем же метод Tostring представлен в классе string? В C# все объекты являются производными от класса Object, определяющего виртуальный (virtual) метод Tostring. Та- ким образом, метод Tostring можно вызвать для получения строкового представления любого объекта. Если класс, наследующий от object (например, string), не подменяет метод Tostring, то этот класс использует вер- сию от класса object по умолчанию, возвращающую строку, состоящую из имени класса объекта. Классы, как правило, подменяют метод Tostring для выражения содержимого объекта в виде текста. Класс string подменя- ет метод Tostring так, что вместо возвращения имени класса, он возвращает только строку. 12 .11. Класс StringBuilder Класс string предоставляет много возможностей обработки строк. При этом содержимое строки никогда не меняется. Операции, которые внешне сцепляют строки, на самом деле присваивают ссылки string на вновь создаваемые строки (например, операция += создает новую строку и присваивает ссылку первоначальной строки вновь созданной строке). В следующих нескольких разделах рассматриваются особенности класса StringBuilder (пространство имен System.Text), используемого для создания динамической строковой информации, т. е. изменяемых строк, и ма- нипуляций ею. Каждый член класса stringBuilder может сохранять определенное число символов, указанное по его вместимости. Превышение вместимости stringBuilder вызывает его расширение для принятия дополни- тельных символов. Как будет видно ниже, члены класса stringBuilder, например, методы Append и AppendFormat, можно использовать для сцепления подобно операциям + и += для класса string. Замечание по технологии программирования_________________________________________ Объекты класса String являются постоянными строками, тогда как объекты класса StringBuilder — изменяе- мые строки. C# может выполнять определенные оптимизации с участием строк (например, совместное исполь- зование одной строки несколькими ссылками), потому что известно, что эти объекты — неизменяемые. Совет по повышению производительности____________________________________________ При наличии выбора между использованием string для представления строки и объектом stringBuilder для представления этой же строки всегда следует пользоваться string, если содержимое объекта изменяться не будет. Там, где это уместно, использование string вместо объектов StringBuilder повышает производитель- ность программы.
388 Глава 12 Класс stringBuilder предоставляет шесть перегруженных конструкторов. Класс StringBuilderConstructor (листинг 12.10) демонстрирует использование трех из этих перегруженных конструкторов. 1 // Листинг 12.10: StringBuilderConstructor.cs 2 // Демонстрация конструкторов класса StringBuilder 3 4 using System; 5 using System. Windows.Forms; 6 using System.Text; 7 8 // создание трех StringBuilder с тремя конструкторами 9 class StringBuilderConstructor 10 { 11 // основная точка входа для приложения 12 [STAThread] 13 static void Main(string[] args) 14 { 15 StringBuilder bufferl, buffer2, buffer3; 16 string output; 17 18 bufferl = new StringBuilder(); 19 buffer2 = new StringBuilder(10); 20 buffer3 = new StringBuilder("hello"); 21 22 output = "bufferl = \"" + bufferl.ToString() + "\"\n"; 23 24 output = "buffer2 = \"" + buffer2.ToString() + "\"\n"; 25 26 output = "buffer3 = \"" + buffer3.ToString() + "\"\n"; 27 28 MessageBox.Show(output, 29 "Demonstrating StringBuilder class constructors", 30 MessageBoxButtons.OK, MessageBoxIcon.Information); 31 32 } // конец метода Main 33 34 } // конец класса StringBuilderConstructor Рис. 12.10. Демонстрация конструкторов класса StringBuilder В строке 18 используется конструктор StringBuilder без аргументов для создания экземпляра StringBuilder, не содержащего символов и имеющего исходную емкость I6 символов. В строке 19 используется конструктор StringBuilder, принимающий аргумент типа int для создания StringBuilder, не содержащего символов, имеющего исходную емкость, указанную аргументом типа int (т. е, 10). Строка 20 использует конструктор StringBuilder, принимающий аргумент string для создания stringBuilder, содержащего символы аргумента string. Исходная емкость больше на 1, чем число символов в строке, переданной в качестве аргумента. В строках 22—26 используется метод Tostring класса StringBuilder для получения строкового представления содержимого класса StringBuilder. Этот метод возвращает базовую строку класса StringBuilder. Результат работы программы представлен на рис. 12.10. 12.12. Индексатор класса StringBuilder, свойства Lengths Capacity и метод EnsureCapacity Класс stringBuilder предоставляет свойства Length и Capacityww возвращения числа символов, находящихся в данный момент в StringBuilder, и число символов, которое StringBuilder может сохранить без дополни- тельного распределения памяти, соответственно. Эти свойства также могут увеличить или уменьшить длину или емкость StringBuilder. Метод EnsureCapacity удваивает текущую емкость экземпляра stringBuilder. Если это удвоенное значение превышает значение, которое нужно разработчику, то оно превращается в новую емкость. В противном случае,
Строки, символы и регулярные выражения 389 EnsureCapacity изменяет емкость, увеличивая требуемый объем на 1. Например, если текущее значение емко- сти равно 17, а нужно сделать 40, то 17, умноженное на 2, не превысит 40, поэтому результатом вызова станет новое значение емкости — 41. Если текущее значение емкости равно 23, а нужно — 4Q, то 23 будет умножено на 2 с получением нового значения емкости — 46. 41 и 46 больше 40 и, таким образом, значение емкости, рав- ное 40, обеспечивается методом EnsureCapacity. Программа в листинге 12.11 демонстрирует использование этих методов и свойств. (Результат работы программы представлен на рис. 12.11.) fЛистинг 12.11. Изменение ёмкости и длгны Sb а _________________---» ;— ; 1 // Листинг 12.11: StringBuilderFeatures.cs 2 // Демонстрация некоторых особенностей класса StringBuilder 3 4 using System; 5 using System.Windows.Forms; 6 using System.Text; 7 8 // использование некоторых методов класса StringBuilder 9 class StringBuilderFeatures 10 { 11 // основная точка входа для приложения 12 [STAThread] 13 static void Main(string[] args) 14 { 15 StringBuilder buffer = 16 new StringBuilder("Hello, how are you?"); 17 18 // использование свойств Length и Capacity 19 string output = "buffer = ” + buffer.ToStringO + 20 "\nLength = " + buffer.Length + 21 "\nCapacity = " + buffer.Capacity; 22 23 // использование метода EnsureCapacity 24 buffer.EnsureCapacity (75); 25 26 output += "\n\nNew capacity = " + 27 buffer.Capacity; 28 29 // усечение StringBuilder установкой свойства Length 30 buffer.Length =10; 31 32 output += "\n\nNew length + " + 33 buffer.Length + "\nbuffer = "; 34 35 // использование индексатора StringBuilder 36 for (int i = 0; i < buffer.Length; i++) 37 output += buffer [i] ; 38 39 MessageBox.Show(output, "StringBuilder features", 40 MessageBoxButtons.OK, MessageBoxIcon.Information); 41 42 } // конец метода Main 43 44 } // конец класса StringBuilderFeatures Рис. 12.11. Демонстрация свойств Length и Capacity и метода EnsureCapacity Данная программа содержит один класс stringBuilder, называемый buffer. В строках 15 и 16 программы ис- пользуется конструктор StringBuilder, принимающий аргумент string для создания StringBuilder и инициа- лизации его значения на "Hello, how are you?". В строках 19—21 в output добавляется содержимое, длина и емкость stringBuilder. Обратите внимание, что в окне сообщения исходное значение емкости stringBuilder равно 32. Помните, что конструктор класса StringBuilder, принимающий аргумент string, создает объект stringBuilder с исходной емкостью большей на наименьшую степень двойки, чем число символов в строке, переданной в качестве аргумента.
390 Глава 12 В строке 24 емкость StringBuilder расширяется минимум До 75 символов. Текущая емкость (32), умноженная на 2, меньше 75, поэтому метод EnsureCapacity увеличивает емкость на единицу больше 75 (т. е. до 76). При добавлении в StringBuilder новых символов так, что его длина превышает емкость, последняя расширяется для размещения дополнительных символов так же, как если бы был вызван метод EnsureCapacity. В строке 30 используется процедура доступа set свойства Length для установки значения длины StringBuilder на 10. Если заданная длина меньше текущего числа символов в StringBuilder, тогда содержимое StringBuilder усекается до заданной длины (т. е. программа отбрасывает все символы в StringBuilder, кото- рые появляются после заданной длины). Если заданная длина больше текущего числа символов, находящихся в StringBuilder, то добавляются пустые символы (с числовым представлением, равным 0, что указывает на конец строки) до тех пор, пока общее число символов в StringBuilder не будет равно заданной длине. Распространенная ошибка программирования_________________________________________________ Присвоение значения null ссылке на строку может привести к логическим ошибкам. Ключевое слово null — это нулевая ссылка, а не строка (string). Не путайте null с пустой строкой — "" (строка с длиной, равной 0 и не содержащей символов). 12.13. Методы Appends AppendFormatкласса StringBuilder Класс StringBuilder предоставляет 19 перегруженных методов Append, позволяющих добавлять значения раз- личных типов данных в конец StringBuilder. В C# имеются версии для каждого встроенного типа данных, для массивов символов, а также strings и Objects. (Помните, что метод Tostring создает строковое представление любого object.) Каждый из этих методов принимает аргумент, преобразует его в строку и добавляет ее к StringBuilder. В листинге 12.12 продемонстрировано использование нескольких методов Append. ^Листинг 12.12 t....SI». ...... .... •• ............................ 1 // Листинг 12.12:- StringBuilderAppend.es 2 // Демонстрация методов Append класса StringBuilder 3 4 using System; 5 using System.Windows.Forms; 6 using Systern.Text; 7 8 // проверка метода Append 9 class StringBuilderAppend 10 { 11 // основная точка входа для приложения 12 [STAThread] 13 static void Main(string[] args) 14 { 15 object objectvalue = "hello"; 16 string stringvalue = "good bye"; 17 chart] characterArray = { 'a', 'b', 'c', 'd', 18 'e', 'f }; 19 20 bbol booleanValue = true; 21 char charactervalue = ' z’; 22 int inetegerValue = 7; 23 long longValue = 1000000; 24 floatfloatvalue = 2.5F; 25 double doubleValue = 33.333; 26 StringBuilder buffer = new StringBuilder(); 27 28 // использование метода Append для добавления значений в буфер 2 9 buffer.Append(obj ectValue); 30 buffer.Append(" "); 31 buffer.Append(stringvalue); 32 buf fer.Append(" "); 33 buffer.Append(characterArray) ; 34 buffer.Append(" ");
Строки, символы и регулярные выражения 391 35 buffer.Append(characterArray); 36 buffer.Append(" "); 37 buffer.Append(booleanValue); 38 buffer.Append(" '*); 39 buffer.Append(charactervalue); 4 0 buf fer.Append(" "); 41 buffer.Append(integervalue); 4 2 buffer.Append(" "); 4 3 buf fer.Append(longValue); 4 4 buf fer.Append(" "); 45 buffer.Append(floatValue); 46 buffer.Append(" "); 4 7 buffer.Append(doubleValue); 48 Рис. 12.12. Демонстрация метода Append 49 MessageBox.Show("buffer = " + buffer.ToString(), 50 "Demonstrating StringBuilder append method", 51 MessageBoxButtons.OK, MessageBoxIcon.Information); 52 53 } // конец метода Main 54 55 } // конец класса StringBuilderAppend В строках 29—47 используется 10 перегруженных методов Append для прикрепления объектов, созданных в строках 15—26, в конец класса stringBuilder. Поведение метода Append напоминает операцию +, используе- мую в строках. Точно так же, как + добавляет объекты в строку, Append может добавлять типы данных в базо- вую строку StringBuilder. Результат работы программы представлен на рис. 12.12. Класс stringBuilder также предоставляет метод AppendFormat, преобразующий строку в специализированный формат, после чего добавляет ее в stringBuilder. Пример из листинга 12.13 и рис. 12.13 демонстрируют ис- пользование этого метода. 1 // Листинг 12.13: StringBuilderAppendFormat.cs 2 // Демонстрация метода AppendFormat 3 4 using System; 5 using System.Windows.Forms; 6 using System.Text; • 7 8 // использование метода AppendFormat 9 class StringBuilderAppendFormat 10 { 11 // основная точка входа для приложения 12 [STAThread] 13 static void Main(string[] args) 14 { 15 StringBuilder buffer = new StringBuilder(); 16 string stringl, string2; 17 18 // форматированная строка 19 stringl = "This {0} costs: {l:C}.\n"; 20 21 // массив аргумента stringl 22 object[] objectArray = new object[2]; 23 24 objectArray[0] = "car"; 25 objectArray[1] = 1234.56; 26 27 // добавление в буфер форматированной строки с аргументом 28 buffer.AppendFormat(stringl, objectArray); 29 Рис. 12.13. Демонстрация метода AppendFormat
392 Глава 12 30 // форматированная строка 31 string2 = "Number:{0:d3}.\n" + 32 "Number right aligned with spaces:{0, 4}.\n" + 33 "Number left aligned with spaces:{0, -4}."; 34 35 // добавление в буфер форматированной строки с аргументом 36 buffer.AppendFormat(string2, 5); 37 38 // отображение отформатированных строк 39 MessageBox.Show(buffer.ToString(), "Using AppendFormat", ’ 40 MessageBoxButtons.OK, MessageBoxIcon.Information); 41 42 } // конец метода Main 43 44 } // конец класса StringBuilderAppendFormat В строке 19 создается строка, содержащая информацию о форматировании. Эта информация заключена в фи- гурные скобки и определяет форматирование отдельно взятой части строки. Форматы имеют форму {X [, Y] [: Forma tString] } где х— число аргумента, который нужно отформатировать, с отсчетом от нуля, y— необязательный аргумент, который может быть как положительным, так и отрицательным; он указывает на количество символов, которое должно появиться в результате форматирования. Если результирующая строка меньше числа Y, тогда она будет заполнена пробелами для устранения разницы. Положительное целое число выравнивает строку по правому краю, отрицательное— по левому. Необязательная форма Formatstring применяет к аргументу конкретный формат (помимо прочих сюда входит указание валюты, десятичный или научный формат и т. д.). В данном слу- чае " {0}" означает, что первый аргумент будет распечатан. ”{1:С}" указывает на то, что второй аргумент будет отформатирован как валюта текущих расчетов. В строке 28 приводится версия AppendFormat, принимающая два аргумента: строку, указывающую формат, и массив объектов, служащих аргументами формату string. Аргумент, на который ссылается "{0}", находится в массиве объектов в указателе 0, и т. д. В строках 31—33 определяется другая строка, используемая для форматирования. Первый формат "{0:d3}" указывает, что первый аргумент будет отформатирован как трехзначное десятичное число; это означает, что перед любым числом, имеющим меньше трех знаков, будут введены нули для выравнивания смещения. Сле- дующий формат— "{0, 4}" — указывает, что отформатированная строка должна иметь четыре знака и быть выровнена по правому краю. Третий формат —- " {о, -4}" определяет, что строка должна быть отформатирова- на по левому краю. Другие опции форматирования описаны в документации. В строке 36 используется версия метода AppendFormat, принимающего два параметра: строку, содержащую формат, и объект, к которому этот формат применяется. В этом случае объектом является число 5. На рис. 12.13 представлен результат применения двух версий AppendFormat с соответствующими им аргу- ментами. 12 .14. Методы Insert, Remove и Replace класса StringBuilder Класс StringBuilder имеет 18 перегруженных методов insert, позволяющих вставлять значения различных типов данных в StringBuilder. Класс предоставляет версии для каждого встроенного типа данных, для масси- вов символов, Strings и Objects. (Помните, что метод Tostring создает строковое представление любого object.) Каждый из этих, методов принимает второй аргумент, преобразует его в строку и вставляет ее в StringBuilder перед указателем, обозначенным первым аргументом. Обозначенный первым аргументом индекс должен быть больше или равен 0, но меньше длины StringBuilder; в противном случае, программа выбрасыва- ет исключение ArgumentOutOfRandeException. Класс StringBuilder также предоставляет метод Remove для удаления любой части StringBuilder. Метод Remove принимает два аргумента: индекс, с которого начинается удаление, и количество символов для удаления. Сумма начального индекса и количества удаляемых символов всегда должна быть меньше длины StringBuilder; в противном случае, программа выдает исключение ArgumentOutOfRandeException. Методы Insert й Remove продемонстрированы в листинге 12.14. Результат работы программы представлен на рис. 12.14.
Строки, символы и регулярные выражения 393 --------- ---------------------------..... -_j.--- Листинг 12.14. Вставка и удаление текста в StringBuxlclar .ГШйЙ gfefe 1 2 3 4 5 б 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 // Листинг 12.14: StringBuilderInsertRemove.es // Демонстрация методов Insert и Remove // класса StringBuilder using System; using System.Windows.Forms; using System.Text; // проверка методов Insert и Remove class StringBuilderlnsertremove { // основная точка входа для приложения [STAThread! static void Main(string[] args) { object objectvalue = "hello"; string stringvalue = "good bye"; char[] characterArray ={ 'a', 'b', ' c', 'd', 'e', ’f’ }; bool booleanValue = true; char charactervalue = 'K'; int inetegerValue =7; long longValue = 10000000; floatfloatvalue = 2.5F; double doubleValue = 33.333; StringBuilder buffer = new StringBuilder(); string output; // вставка значений в буфер buffer.Insert(0, objectvalue); buffer.Insert(0, " ”); buffer.Insert(0, stringvalue); buffer.Insert(0, " "); buffer.Insert(0, characterArray); buffer.Insert(0, " "); buffer.Insert(0, booleanValue); buffer.Insert(0, " "); buffer.Insert(0, charactervalue); buffer.Insert(0, " "); buffer.Insert(0, integervalue); buffer.Insert(0, " "); buffer.Insert(0, longValue); buffer.Insert(0, " "); buffer.Insert(0, floatvalue); buffer.Insert(0, " "); buffer.Insert(0, doubleValue); buffer.Insert(0, " "); output = "buffer after inserts: \n" + buffer.ToString() + "\n\n"; buffer.Remove(10, 1); // удаление 2 из 2.5 buffer.Remove(2, 4); // удаление 33.3 в 33.333 output + = "buffer after Removes:\n" + buffer.ToString(); Рис. 12.14. Демонстрация методов Insert и Remove
394 Глава 12 59 MessageBox.Show(output, "Demonstrating, StringBuilder " + 60 , "Insert and Remove methods", MessageBoxButtons.OK, 61 MessageBoxIcon.Information); 62 63 } // конец метода Main 64 65 j // конец класса StringBuilderlnsertRemove Другой полезный метод в рамках StringBuilder — Replace. Он осуществляет поиск заданной строки или сим- вола и заменяет их другой строкой или символом. Метод представлен в листинге 12.15. 1 // Листинг 12.15: StringBuilderReplace.cs 2 // Демонстрация метода Replace 3 4 using System; 5 using System.Windows.Forms; 6 using System.Text; 7 8 // проверка метода Replace 9 class StringBuilderReplace 10 { 11 // основная точка входа для приложения 12 [STAThread] 13 static void Main(string[] args) 14 { 15 StringBuilder builderl = 16 new StringBuilder("Happy Birthday Jane"); 17 18 StringBuilder builder2 = 19 new StringBuilder("good bye greg"); 20 21 string output = "Before replacements:\n" + 22 builderl.ToString() + "\n" + nuilder2.ToString(); 23 24 builderl.Replace("Jane", "Greg"); 25 builder2.Replace(*g', 'G', 0, 5); 26 27 output += "\n\nAfter replacements:\n" + 28 builderl.ToString() + "\n" + builder2.ToString(); 29 30 MessageBox.Show(output, 31 "Using StringBuilder method Replace", 32 MessageBoxButtons.OK, MessageBoxIcon.Information); 33 34 » ) // конец метода Main 35 36 } // конец класса StringBuilderReplace Рис. 12.15. Демонстрация метода Replace В строке 24 используется метод Replace для замены всех экземпляров строки "Jane" строкой "Greg" в builderl. Другая перегрузка этого метода принимает в качестве параметров два символа и заменяет каждое появление первого— вторым. В строке 25 используется перегрузка метода Replace, принимающего четыре параметра, первые два из которых являются символами, а два последние — типа int. Метод заменяет все экземпляры пер- вого символа вторым, начиная с номера, заданного первым int, и продолжая счет, заданный вторым. Таким образом, в данном случае Replace просматривает только пять символов, начинающихся с символа с номером 0. Как показано на рис. 12.15, эта версия Replace меняет "д" на "G" в слове "good", но не в слове "дгед". Это про- исходит потому, что буквы "д" в слове "дгед" не входят в диапазон, указанный аргументами int (т. е. между индексами 0 и 4).
Строки, символы и регулярные выражения 395 12.15. Методы Char Язык C# предоставляет тип данных, называемый структурой, который во многом похож на класс. Несмотря на то, что классы и структуры можно сравнивать по разным показателям, структуры — это типы значений. Подоб- но классам, структуры включают методы и свойства. Те и другие используют одинаковые модификаторы (такие как public, private и protected) и осуществляют доступ к членам с помощью оператора доступа (.). Однако классы создаются с помощью ключевого слова class. А структуры — ключевого слова struct. Многие из простых типов данных, рассматриваемых в книге, на самом деле являются псевдонимами различных структур. К примеру, int определяется структурой System. Int32, Long— System.Int64 и т. д. Эти структуры являются производным от класса valueType, который, в свою очередь, является производным от класса Object. В данном разделе рассматривается структура Char, являющаяся структурой для символов. Большинство методов структуры Char являются статическими (static), принимают минимум один символ в качестве аргумента и выполняют либо проверку этого символа, либо определенную манипуляцию им. В сле- дующем примере показано несколько из этих методов. В листинге 12.16 продемонстрированы static-методы, проверяющие символы на принадлежность к особому типу символов, а также static-методы, изменяющие ре- гистры набора символов (прописной/строчной). 1 // Листинг 12.16: CharMethods.cs 2 // Демонстрация методов проверки символов 3 //из структуры Char 4 5 using System; 6 using System. Drawing; 7 using System.Collections; '8 using System.ComponentModel; 9 using System.Windows.Forms; 10 using System.Data; 11 12 // форма отображает информацию о конкретных символах 13 public class StaticCharMethods : System.Windows.Forms.Form 14 { 15 private System.Windows.Forms.Label enterLabel; 16 private System. Windows. Forms. Text Box inputTextBox; 17 private System. Windows. Forms. But ton analyzeButton; 18 private System.Windows.Forms.TextBox outputTextBox 19 20 private System.ComponentModel.Container components = null; 21 22 // основная точка входа для приложения 23 [STAThread] 24 static void Main() 25 { 26 Application.Run(new StaticCharMethods()); 27 } 28 29 // код, сгенерированный Visual Studio .NET 30 31 // обработка analyzeButtoh_Click 32 private void analyzeButton_Click( 33 object sender, System.EventArgs e) 34 { 35 char character = Convert.ToChar(inputTextBox.text); 36 BuildOutput(character); 37 } 38 39 // отображение информации о символе в текстовом окне выхода 40 private void BuildOutput(char inputcharacter) 41 { 42 string output; 43
396 Глава 12 44 output = "is digit: " + 45 Char.IsDigit(inputcharacter) + "\r\n"; 46 47 output += "is letter: " + 48 Char.IsLetter(inputcharacter) + "\r\n"; 49 50 output += "is letter or digit: " + 51 Char.IsLetterOrDigit(inputcharacter) + "\r\n"; 52 53 output += "is lower case: " + 54 Char.IsLower(inputCharacter) + "\r\n"; 55 56 output += "is upper case: " + 57 Char.IsUpper(inputcharacter) + "\r\n"; 58 59 output += "to upper case: " + 60 Char.ToUpper(inputcharacter) + ”\r\n"; 61 62 output += "is lower case: " + 63 Char.ToLower(inputcharacter) + "\r\n"; 64 65 output += "is punctuation: " + 66 Char.IsPunctuation(inputCharacter) + "\r\n"; 67 68 output = "is symbol: " + Char.IsSyiribol (inputcharacter); 69 70 outputTextBox.Text = output; 71 72 } // конец метода BuildOutput 73 74 } // конец класса StaticCharMethods Данное Windows-приложение содержит подсказку в виде поля, в которое пользователь может ввести символ, кнопку, которую он может нажать после ввода символа, и второе текстовое поле, отображающее выходные дан- ные анализа. При нажатии пользователем кнопки Analyze Character активизируется обработчик события analyzeButton_click (строки 32—37). Этот метод преобразует введенные данные из string в Char с помощью метода Convert .ToChar (строка 35). В строке 36 вызывается метод BuildOutput, определенный в строках 40—72. В строке 45 используется метод IsDigit структуры char для определения, является ли inputcharacter цифрой. Если да, то метод возвращает true; в противном случае метод возвращает false. В строке 48 используется метод IsLetter структуры char для определения, является ли inputcharacter буквой. Если да, то метод возвращает true; в противном случае метод возвращает false. В строке 51 используется ме- тод IsLetterOrDigit структуры Char для определения, является inputcharacter буквой или цифрой. Если да, то метод возвращает true; в противном случае метод возвращает false. В строке 54 используется метод isLower структуры char для определения, набран ли inputcharacter строчными буквами. Если да, то метод возвращает true; в противном случае возвращается false. В строке 57 используется метод IsUpper структуры char для выяснения, набран ли inputcharacter прописными буквами. Если да, то ме- тод возвращает true; в противном случае возвращается false. В строке 60 используется метод ToUpper структу- ры char для преобразования символа inputcharacter в его прописной эквивалент. Метод возвращает преобра- зованный символ, если последний имеет прописной эквивалент; в противном случае метод возвращает первона- чальный символ. В строке 63 используется метод ToLower структуры char для преобразования символа inputcharacter в его строчной эквивалент. Метод возвращает преобразованный символ, если последний имеет прописной эквивалент; в противном случае возвращается первоначальный аргумент. В строке 66 используется метод isPunctuation структуры char для определения, является ли inputcharacter знаком пунктуации. Если да, то метод возвращает true; в противном случае возвращается false. В строке 68 используется метод isSymbol структуры Char для выяснения, является ли inputcharacter символом. Если да, то метод возвращает true; в противном случае возвращается false. Результат работы программы представлен на рис. 12.16. Структура Char также содержит другие методы, не представленные в примере. Многие из static-методов сход- ны; скажем, isWhiteSpace служит для определения, является тот или иной символ "белым" (например, симво-
Строки, символы и регулярные выражения 397 лом новой строки, табуляции или пробелом). Данная структура также содержит несколько public-методов эк- земпляра; многие из них, например методы Tostring и Equals, являются методами, уже встречавшимися в дру- гих классах. В эту группу входит метод СошрагеТо, используемый для сравнения значений двух символов друг с другом. Рис. 12.16. Демонстрация методов структуры Char: а — проверка буквы А; б — проверка цифры 8; в — проверка символа @ 12.16. Моделирование процессов тасования и раздачи карт В данном разделе применяется генерирование случайных чисел для разработки программы, моделирующей процессы тасования и раздачи карт. После создания эту программу можно внедрить в программы-симуляторы различных карточных игр. Класс Card (листинг 12.17) содержит две переменные экземпляра строки (string)— face (лицо) и suit (масть), — сохраняющие ссылки на название карты и масть той или иной карты. Конструктор для данного клас- са принимает две строки, используемые для инициализации — face и suit. Метод Tostring (строки 20—24) формирует строку, состоящую из лица карты и ее масти. 1 // Листинг 12.17: Card.cs 2 // Сохранение информации о лице и масти каждой карты 3 4 using System; ' 5 6 // представление карты 7 public class Card 8 { 9 private string face; 10 private string suit; 11 12 public Card(string faceValue, 13 string suitvalue) 14 { 15 face = faceValue; 16 suit = suitvalue; 17 18 } // конец конструктора 19 20 public override string ToString() 21 { 22 return face + " of " + suit; 23 24 } // конец метода ToString 25 26 } // конец класса Card
398 Глава 12 Разрабатывается приложение DeckForm (листинг 12.18) с созданием колоды из 52 карт, с помощью объектов Card. Пользователи могут раздавать карты нажатием кнопки Deal Card. Каждая сданная карта отображается в Label. Играющие могут тасовать колоду в любое время нажатием кнопки Shuffle Cards. Листинг 12.18, Мо,Цк пирование процессов тасования и раздачи карт - ....«•***• X .. ....... ••••«» ... 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 // Листинг 12.18: DeckOfCards.cs // Моделирование процессов тасования и раздачи карт using System; using System.Drawing; using System.Collections; using System.ComponentModel; using System.Windows.Forms; using System.Data; // обеспечение функциональности формы public class DeckForm : System.Windows.Forms.Form { private System.Winrodws.Forms.Button dealButton; private System.Winrodws.Forms.Button shuffleButton; private System.Winrodws.Forms.Label displayLabel; private System.Winrodws.Forms.Label statusLabel; private System.ComponentModel.Container comppnents « null; private Card[] deck = new Card [52]; private int currentcard; // основная точка входа для приложения [STAThread] static void Main() { Application.Run (new deckFormO); } // код, сгенерированный Visual Studio .NET // обрабатывает форму во время загрузки private void DeckForm_Load( object sender, System.EventArgs e) { string[] faces = { "Ace", "Deuce", "Three", "Four", "Five", "Six", "Seven","Eight", "Nine", "Ten", "Jack", "Queen", "King" } string[] suits = { "Hearts", "Diamonds", "Clubs", "Spades" } // карты не сданы currentCard = -1; // инициализация колоды for (int i = 0; i < deck.Length; i++) deckfi] = new Card(faces[i % 13], suits[i % 4]); } // конец метода deckForm_Load // обработка dealButton_Click private void dealButton_Click( object sender, System.EventArgs e)
Строки, символы и регулярные выражения 399 58 { 59 Card dealt — DealCardO; 60 61 It- если сданная карта — null, тогда карт не остается, 62 // игрок должен перетасовать карты 63 if (dealt != null) 64 { 65 displayLabel.Text = dealt.Tostring(); 66 statusLabel.Text « "Card #: " + currentcard; 67 } 68 else 69 { 70 displayLabel.Text = "NO MORE CARDS TO DEAL"; 71 statusLabel.Text - "Shuffle cards to continue"; 72 ) 73 } 74 75 // тасование карт 76 private void Shuffle() 77 { 78 Random randomNumber = new Randomf); 79 Card temporaryvalue; 80 81 currentcard = -1; 82 83 / / перемешать каждую карту с произвольньыи картами 84 for (int i « 0; i < deck.Length; i++) 85 { 86 int j = randomNumber.Next(52); 87 88 // поменять карты местами 89 temporaryvalue - deckfi]; 90 deck[i] = deck[j]; 91 deck[j] = temporaryvalue; 92 ) 93 94 dealButton.Enabled » true; 95 96 ) // конец метода Shuffle 97 98 private Card DealCardO 99 { 100 // если есть карта для сдачи — сдать ее; 101 //в противном случае сигнализировать, что карты нужно 102 // тасовать отключением кнопки dealButton и возвращением null 103 if (currentcard + 1 < deck.Length) 104 { 105 currentCard++; 106 return deck(currentcard]; 107 } 108 else 109 { 110 dealButton.Enabled « false; 111 return null; 112 } 113 114 } // конец метода DealCard 115 116 // обработка shuffleButtonClick 117 private void shuffleButton_Click ( 118 object sender, System.EventArgs e) 119 { 120 displayLabel.Text = "SHUFFLING..."; 121 Shuffle();
400 Глава 12 122 displayLabel.Text = "DECK IS SHUFFLED"; 123 statusLabel.Text - 124 } // конец метода ShuffleButton_Click 125 126 } // конец класса deckForm В методе DeckForm_Load (строки 35—53 листинга 12.18) используется оператор for (строки 50—51) для запол- нения массива deck объектами Card. Обратите внимание, что каждый объект card создается и инициализируется двумя строками: одной— из массива faces (строки с "Асе" по "King"), а другой— из массива suits ("Hearts", "Diamonds", "clubs" или "Spades"). Результатом расчета i%13 всегда является значение от 0 до 12 (тринадцать индексов массива faces), а результатом расчета i%4 всегда является значение от 0 до 3 (четыре индекса массива suits). Инициализированный массив deck содержит карты с лицом от туза до короля каждой масти. При нажатии пользователем кнопки Deal Card обработчик события dealButton Click (строки 56—73) активи- зирует метод Dealcard (определен в строках 98—114) для получения следующей карты в массиве deck. Если deck не пустой, то метод возвращает ссылку на объект card; в противном случае метод возвращает null. Если ссылка не null, тогда в строках 65 и 66 отображается Card в displayLabel, а номер карты выводится в statusLabel. Если метод DealCard возвращает ссылку null, тогда в displayLabel отображается строка "NO MORE CARDS ТО DEAL", а в statusLabel — строка "Shuffle cards to continue". При нажатии пользователем кнопки Shuffle Cards обрабатывающий событие метод shuffieButton Click (стро- ки 117—124) активизирует метод shuffle (определен в строках 79—96) для тасования карт. Цикл повторяется 52 раза (по числу карт: индексы массива от 0 до 51). Для каждой карты метод произвольно выбирает номер от 0 до 51. Затем текущий объект card и случайно выбранный объект card меняются в массиве местами. Для тасова- ния карт метод shuffle выполняет 52 перемены карт местами за один проход всего массива. По завершении тасования displayLabel отображает строку "DECK IS SHUFFLED", a statusLabel сбрасывается. Результат работы программы представлен на рис. 12.17. а б Рис. 12.17. Моделирование процессов тасования и раздачи карт: а — до нажатия кнопки Deal Card; б — нет карт для раздачи; а — тасование завершено; г — раздача 12.17. Регулярные выражения и класс Regex Регулярные выражения представляют собой особым образом отформатированные строки, используемые для поиска шаблонов в тексте, а также для подтверждения корректности информации в целях обеспечения надле- жащего формата данных. К примеру, код ZIP может состоять из пяти цифр, и последнее имя должно начинаться с прописной буквы. Одним из применений регулярных выражений является упрощение конструкции компиля- тора. Часто расширенное и сложное регулярное выражение используется для подтверждения допустимости син-
Строки символы и регулярные выражения 401 таксиса программы. Если программный код не соответствует регулярному выражению, то компилятор распо- знает в нем синтаксическую ошибку. NET Framework предоставляет несколько классов в помощь разработчикам при распознавании ре1улярных вы- ражений и манипуляциях ими. Класс Regex (пространство имен System.Text.RegularExpressions) представляет неизменяемое регулярное выражение. Он содержит статичные методы, обеспечивающие использование класса Regex без явного создания объектов этого класса. Класс Match представляет результаты операции совмещения регулярного выражения. Класс Regex содержит метод Match, который возвращает объект класса Match, представляющий в свою очередь одно Тоответствие регулярного выражения. Regex также предоставляет метод Matches, находящий все соответ- ствия регулярному выражению в произвольной строке и возвращающий объект Matchcollection, т. е. набор соответствий. 1 Распространенная ошибка программирования___________________________________________________ При использовании регулярных выражений не следует путь класс Match с методом Match, принадлежащим классу Regex. Распространенная ошибка программирования___________________________________________________ Visual Studio не добавляет System.Text.RegularExpressions в список пространств имен, импортированных в свойства проекта, поэтому программист должен импортировать его вручную вместе с оператором using System.Text.RegularExpressions В табл. 12.1 представлено несколько классов символов, которые можно использовать с регулярными выраже- ниями Класс символов — это управляющая последовательность, представляющая группу символов. Таблица 12.1. Классы символов Символ Соответствия Символ Соответствия \d Любая цифра \D Любой символ, не являющийся цифрой \w Любой символ-слово \w Любой символ, не являющийся словом \s Любой "белый" пробел \s Любой символ, не являющийся "белым" пробелом Символ-слово — это любой символ (буква или цифра) или знак подчеркивания. Символ "белого" пробела — это пробел, знак табуляции, возврата каретки, новой строки или подачи страницы на принтер. Цифра — это символ любой цифры. Впрочем, регулярные выражения не ограничиваются этими классами символов. В выражениях применяются различные операторы и другие формы обозначений для поиска сложных сочетаний. Несколько из этих методик будут рассмотрены в контексте следующего примера. В листинге 12.19 приведен простой пример, в котором применяются регулярные выражения. В данной про- грамме делается попытка применения соответствия дней рождений регулярным выражениям. Выражение соот- ветствует только дням рождения во всех месяцах, кроме апреля, у людей, имена которых начинаются с буквы J. ' Листинг 12.19. Регулярные выражения с проверкой дней рождения .. " - • ...... . .л...;. . . ..............< ....... . - 1 // Листинг 12.19: RegexMatches.cs 2 // Демонстрация класса Regex Matches 3 4 using System; 5 using System.Windows.Forms; 6 using System.Text.RegularExpressions; 7 8 // проверка регулярных выражений 9 class RegexMatches 10 { 11 // основная точка входа для приложения 12 [STAThread] 13 static void Main(string[] args) 14 { 15 string output = 16 26 Зак 3333
402 Глава 12 Рис. 12.18. Демонстрация применения регулярных выражений 17 // создать регулярное выражение 18 Regex expression = 19 new Regex(@"J.*\d[0-35-9]-\d\d-\d\d"); 20 21 string stringl = "Jane's Birthday is 05-12-75\n” + 22 "Dave's Birthday is ll-04-68\n" + 23 "John's Birthday is 04-28-73\n" + 24 "Joe's Birthday is 12-17-77"; 25 26 // совпадение регулярного выражения co строкой и 27 // распечатка всех совпадений 28 foreach (Match myMatch in expression.Matches(stringl)) 29 output += myMatch.ToString() + "\n"; 30 31 MessageBox.Show(output, "Using class Regex", 32 MessageBoxButtons.OK, MessageBoxIcon.Information); 33 34 } // конец метода Main 35 36 } // конец класса RegexMatches / В строке 19 создается экземпляр класса Regex и определяется шаблон регулярного выражения, для которого Regex будет осуществлять поиск. Первый символ J в регулярном выражении рассматривается как буквен- ный символ. Это означает, что любая строка, соответствующая этому регулярному выражению, должна начи- наться с J. В регулярном выражении символ точки (.) соответствует любому одиночному символу, за исключением симво- ла новой строки. Однако, когда за символом точки следует "звездочка", как в выражении ". то он соответст- вует любому количеству не заданных символов. Вообще говоря, когда к какому-либо выражению применяется операция ♦, тогда выражение будет совпадать с количеством случаев его появления, отличным от нуля. И на- оборот: применение к выражению операции + вызывает совпадение выражения с одним или более появлений этого выражения. Например, "А*" и "А+” будут соответствовать "А", но пустой строке будет соответствовать только "А*". Как показано в табл. 12.1, "\d" совпадает с любым числовым символом. Для указания наборов символов, совпа- дающих с имеющимися, последние можно заключить в квадратные скобки []. Например, шаблон [aeiou] мож- но использовать для соответствия любой гласной. Диапазоны символов можно представлять вводом тире между двумя символами. В приведенном примере [0-35-9] соответствует только цифрам в диапазонах, обозначенных шаблоном. В данном случае шаблон соответствует любой цифре между 0 и 3, либо между 5 и 9; следовательно, он соответствует любой цифре, за исключением 4. Если первый символ в скобках — Л, тогда выражение прини- мает любой символ, отличный от указанных. Однако тут важно отметить, что [А 4] — не то же самое, что [0-35-9]; первое совпадает с любым символом, не являющимся цифрой, помимо цифр, отличных от 4. Несмотря на то, что заключенный в скобки символ - обозначает диапазон, экземпляры символа - за пределами групповых выражений рассматриваются как буквенные символы. Таким образом, регулярное выражение в строке 19 осуществляет поиск строки, начинающейся с буквы J, за которой следует любое количество символов и двузначное число (второй знак которого не может быть 4), тире, другое двузначное число, тире и третье дву- значное число. В строках 28—29 применяется цикл foreach для итерации через каждый Match, полученный из expression.Matches, в котором в качестве аргумента используется stringl. Рисунок 12.18 демонстрирует два совпадения, найденные в stringl. Обратите внимание, что оба совпадения соответствуют шаблону, заданному регулярным выражением. Звездочка (*) и плюс (+) в предыдущем примере называются кванторами. В табл. 12.2 представлены различные кванторы и их использование. Принцип работы символов * и + уже рассматривался. Вопросительный знак (?) соответствует нулевому или од- ному появлению выражения, которое он квантифицирует. Скобки, в которых заключено число ({п}), соответст- вуют п появлениям квантифицируемого выражения. Данный квантор представлен в следующем примере. Встав- ка запятой после числа, заключенного в скобки, соответствует п появлений квантифицированного выражения Скобки, заключающие два числа ({п,ш}), соответствуют появлению квантифицируемого выражения между п и т. Все кванторы "жадные". Это означает, что они будут соответствовать стольким фактам появления, сколько возможно, все то время, пока совпадение остается успешным. Однако если за каким-либо из этих кванторов следует вопросительный знак (?), то он становится "ленивым". Такой квантор будет соответствовать минимуму появлений все время, пока совпадение остается успешным.
Строки, символы и регулярные выражения '403 Таблица 12.2. Кванторы, используемые в регулярных выражениях Квантор Соответствует * 0 или более появлений в шаблоне + одному или более появлений в шаблоне 7 0 или одному появлений в шаблоне {n} п появлений {n, } минимум п появлении {n,m} количеству появлений между пит включительно Windows-приложение в листинге 12.20 демонстрирует более конкретный пример, подтверждающий допусти- мость пользовательских входных данных посредством регулярных выражений. 1 // Листинг 12.20: Validate.cs 2 // Подтверждение корректности пользовательской информации 2а //с помощью регулярных выражений 3 4 using System; 5 using System.Drawing; 6 using System.Collections; 7 using Sys tern. ComponentModel ; 8 using System.Windows.Forms; 9 using System.Data; 10 using System.Text.RegularExpressions; 11 12 // использование регулярных выражений для подтверждения 12а // допустимости строк 13 public class ValidateForm : System.Windows.Forms.Form 14 < 15 private System.Windows.Forms.Label phoneLabel; 16 private System.Windows.Forms.Label zipLabel; 17 private System.Windows.Forms.Label stateLabel; 18 private System.Windows.Forms.Label cityLabel; 19 private System.Windows.Forms.Label addressLabel; 20 private System.Windows.Forms.Label firstLabel; 21 private System.Windows.Forms.Label lastLabel; 22 23 private System.Windows.Forms.Button okButton; 24 25 private System.Windows.Forms.TextBox phoneTextBox; 26 private System.Windows.Forms.TextBox zipTextBox; 27 private System.Windows.Forms.TextBox stateTextBox; 28 private Systern.Windows.Forms.TextBox cityTextBox; 29 private System.Windows.Forms.TextBox addressTextBox; 30 private System.Windows.Forms.TextBox firstTextBox; 31 private System.Windows.Forms.TextBox lastTextBox; 32 33 private System.ComponentModel.Container components = null; 34 35 // основная точка входа для приложения 36 [STAThread] 37 static void Main() 38 { 39 Application.Run(new validateForm()); 40 } 41
404 Глава 12 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 // код, сгенерированный Visual Studio .NET // управление событием okButton_Click private void okButton_Click( object sender, System.EventArgs e) { // подтверждение, что текстовые окна заполнены if (lastTextBox.text = "" || firstTextBox.Text — || addressTextBox.Text == "" || cityTextBox.Text == "" || stateTextBox.Text ™ "" || zipTextBox.Text "" || phoneTextBox.Text = "") { // отображение окна сообщения MessageBox.Show("Please fill in all fields", "Error", MessageBoxButtons.OK, MessageBoxIcon.Error); // настройка фокуса на lastTextBox lastTextBox.Focus(); return; } // при некорректности формата фамилии показать сообщение if (!Regex.Match(lastTextBox.Text, @"A[A-Z][a-zA-Z]*$").Success) { // фамилия некорректна MessageBox.Show("Invalid Last Name", "Message", MessageBoxButtons.OK, MessageBoxIcon.Error); lastTextBox.Focus(); return; } // при некорректности формата имени показать сообщение if (!Regex.Match(firstTextBox.Text, @"A[A-Z][a-zA-Z]*$").Success) ( // имя некорректно MessageBox.Show("Invalid First Name", "Message", MessageBoxButtons.OK, MessageBoxIcon.Error); firstTextBox.Focus(); return; } // при недействительности формата адреса показать сообщение if (!Regex.Match(addressTextBox.Text, @"Л[0-9]+\s+([a-zA-Z]+|[a-zA-Z]+\s[a-zA-Z]+)$").Success) { // адрес некорректен MessageBox. Show ("Invalid Address*’, "Message", MessageBoxButtons.OK, MessageBoxIcon.Error); addressTextBox.Focus(); return; ) // при недопустимом формате города показать сообщение if (!Regex.Match(cityTextBox.Text, @”л ([a-zA-Z] + | [a-zA-Z]4-\s[a-zA-Z]+)$") .Success)
Строки, символы и регулярные выражения 405 103 { 104 // название города некорректно 105 MessageBox.Show("Invalid City", "Message", 106 MessageBoxButtons.OK, MessageBoxIcon.Error); 107 cityTextBox.Focus(); 108 109 return; 110 } 111 112 // при недопустимом формате штата показать сообщение 113 if (!Regex.Match(stateTextBox.Text, 114 @"A([a-zA-Z]+|[a-zA-Z] +\s[a-zA-Z]+)$").Success) 115 { 116 // название штата некорректно 117 MessageBox.Show("Invalid State", "Message", 118 MessageBoxButtons.OK, MessageBoxIcon.Error); 119 stateTextBox.Focus (); 120 121 return; 122 } 123 124 // при недопустимом формате почтового индекса - сообщение 125 if (! Regex. Mat ch (zipText Box. Text, @"/'\d{5}$") .Success) 126 { 127 // некорректен почтовый индекс 128 MessageBox.Show("Invalid Zip Code", "Message", 129 MessageBoxButtons.OK, MessageBoxIcon.Error); 130 zipTextBox.Focus(); 131 132 return; 133 } 134 135 // при недопустимом формате номера телефона - сообщение 136 if (!Regex.Match(phoneTextBox.Text, 137 @"Л[1-9]\d{2}-[1-9]\d(2)-\d{4}$") .Success) 138 { 139 I/ некорректен номер телефона 140 MessageBox.Show("Invalid Phone Number", "Message", 141 MessageBoxButtons.QK, MessageBoxIcon.Error); 142 phoneTextBox.Focus(); 143 144 return; 145 } 146 147 // информация корректна, сообщить об этом и закрыть приложение 148 this.Hide(); 149 MessageBox.Show("Thank You!", "Information Correct", 150 MessageBoxButtons.OK, MessageBoxIcon.Information); 151 ’ 152 Application.Exit(); 153 154 ) // конец метода okButton_Click 155 156 } // конец класса ValidateForm При нажатии пользователем кнопки ОК программа выполняет проверку, что все поля заполнены (строки 49— 52). Если одно или несколько полей остается незаполненным, тогда программа сигнализирует пользователю, что до проверки корректности входной информации (строки 55 и 56) все поля должны быть заполнены. В стро- ке 59 вызывается метод Focus класса TextBox. Метод Focus помещает курсор в рамках поля TextBox, которое делает вызов. После этого программа выходит из обработчика событий (строка 61). Если незаполненных полей нет, тогда проверяется корректность пользовательских входных данных. Сначала выясняется допустимость Last Name — фамилии (строки 65—74). Если Last Name проходит проверку (т. е. если свойство Success экземпляра Match равно true), тогда управление передается процессу подтверждения корректности First Name — имени
406 Глава 12 (строки 77—86). Данный процесс продолжается, пока не подтверждается допустимость всех введенных данных, либо до сбоя проверки (Success равно false), и программа выдает соответствующее сообщение об ошибке. Ес- ли все поля содержат корректную информацию, то подается сигнал об успешном выполнении проверки, и про- грамма завершает работу. В предыдущем примере осуществлялся поиск подстрок, совпадающих с регулярным выражением. В данном примере необходимо проверить, соответствует ли целая строка регулярному выражению. Например, в качестве фамилии нужно принять "Smith", а не "9@smith#". Этот эффект достигается началом каждого регулярного вы- ражения с символа Л и окончанием символом $. Символы Л и $ соответствуют положениям в начале и в конце строки соответственно. При этом регулярное выражение оценивает всю строку и не возвращает совпадение при успешном совпадении подстроки. В данной программе используется версия static-метода Match класса Regex, принимающая дополнительный параметр, задающий регулярное выражение, поиск совпадения которого выполняется. В выражении в строке 66 используется квадратная скобка и обозначение диапазона для соответствия с первой прописной буквой, за кото- рой следуют буквы в любом регистре: а—z соответствуют строчным буквам, а А—z — прописным. Квантор * обозначает, что второй диапазон символов может появиться в строке ноль или более раз. Следовательно, данное выражение соответствует любой строке, .состоящей из одной прописной буквы, за которой следуют ноль или более дополнительных букв. Обозначение \s совпадает с одним символом пробела (строки 90, 102 и 114). Выражение \d{ 5}, использованное в поле Zip, совпадает с любыми пятью цифрами (строка 125). Вообще говоря, выражение с положительным це- лым числом х в фигурных скобках будет совпадать любым цифрам х (обратите внимание на важность символов Л и $, чтобы в почтовом индексе не была подтверждена корректность лишних цифр). Символ | совпадает с выражением, расположенным слева или справа от него. Например, Hi (John | Jane) совпа- дает с Hi John и Hi Jane. При группировании частей регулярного выражения обратите внимание на использова- ние круглых скобок. Рис. 12.19. Проверка корректности введенных данных с помощью регулярных выражений: а — не заполнено одно поле; б — сообщение о необходимости заполнить все поля; в — все поля заполнены; г — сообщение о некорректных данных в поле Zip; д — все данные корректные; е — сообщение об успешной проверке Поля Last Name и First Name принимают строки любой длины, начинающиеся с прописной буквы Поле Address совпадает с числом, состоящим минимум из одной цифры, за которой следует пробел и одна или более букв; либо за одной или более буквой следует пробел и другая серия из одной или более букв (строка 90). Таким образом, записи "10 Broadway" и "10 Main street" являются корректными адресами. Поля City (строка 102) и State (строка 114) соответствуют любому слову, состоящему минимум из одного символа, либо двум словам, состоящим минимум из одного символа, если эти слова разделены одним пробелом. Это означает, что Waltham и
Строки, символы и регулярные выражения 407 west Newton будут совпадать. Как отмечалось выше, код Zip должен состоять из пяти цифр (строка 125). Номер телефона (поле Phone) должен быть представлен в форме ххх-ууу-уууу, где ххх означает код региона, а ууу-уууу— собственно номер (строка 137). Первые символы х и у не могут быть нулями. Результат представлен на рис. 12.19. Иногда полезно заменять части строки на другие, либо разбивать строку в соответствии с регулярным выраже- нием. Для этой цели класс Regex предоставляет static-версию и версию экземпляра методов Replace и Split, продемонстрированных в листинге 12.21. Результат работы программы представлен на рис. 12.20. 1 // Листинг 12.21: RegexSubstitution.cs 2 // Использование методов Replace и Split класса Regex 3 4 using System; 5 using System.Text.RegularExpressions; 6 using System.Windows.Forms; 7 Рис. 12.20. Демонстрация методов Replace и Split 8 // описание резюме для RegexSubstitution 9 public class RegexSubstitutionl 10 { 11 12 // основная точка входа для приложения 13 static void Main(string!] args) 14 { 15 string testStringl = 16 "This sentence ends in 5 stars *****"; 17 18 string testString2 = "1, 2, 3, 4, 5, 6, 7, 8"; 19 Regex testRegexl = new Regex("stars"); 20 Regex testRegex2 = new Regex(@"\d"); 21 string!] results; 22 string output = "Original String l\t\t\t" + testStringl; 23 24 testStringl = Regex.Replace(testStringl, @"\*", "л"); 25 26 output += "\пл substituted for *\t\t\t" + testStringl; 27 28 testStringl = testRegexl.Replace(testStringl, "carets"); 29 30 output = "\n\"carets\" substituted for \"stars\"\t\t" + 31 testStringl; 32 33 output += "\nEvery word replaced by \"word\"\t" + 34 Regex.Replace(testStringl, @"\w+", "word”); 35 36 output +» "\n\nOriginal String 2\t\t\t" + testString2; 37 38 output += "\nFirst 3 digits replaced by \"digit\"\t\t" + 39 testRegex2.Replace(testString2, "digit", 3); 40 41 output += "\nString split at commas\t\t["; 42 43 results = Regex.Split(testString2, @",\s*"); 44 45 foreach (string resultstring in results) 46 { ' 47 output += "\"" + resultstring + "\", "; 48 } 49
408 Глава 12 50 output = output.Substring(0, output.Length — 2) + 51 52 MessageBox.Show(output, f 53 "Substitution using regular expressions"); 54 55 } /7 конец метода Main 56 57 } // конец класса RegexSubstitution Метод Replace замещает текст в строке новым текстом в тех местах, где первоначальная строка совпадает с ре- гулярным выражением. В примере листинга 12.21 представлены две версии этого метода. Первая версия (стро- ка 24) — static и принимает три параметра: строку для изменения, строку, содержащую регулярное выраже- ние, которому должно быть найдено соответствие, и строку замены. Здесь метод Replace заменяет любой эк- земпляр * в teststring на Л. Обратите внимание, что в регулярном выражении (@"\*") символу * предшествует обратная косая — \. Обычно * является квантором, указывающим на то, что регулярное выражение должно совпадать с определенным количеством появлений предшествующего шаблона. Однако в строке 24 необходимо найти все случаи появления символа литерала *; для этого символ * необходимо заменить на символ \. Заменой специального символа регулярного выражения на символ \ механизм совпадения регулярного выражения ин- формируется о поиске фактического символа, в противовес тому, что приводится в регулярном выражении. Вторая версия метода Replace (строка 28) — это метод экземпляра, использующий регулярное выражение, пе- реданное в конструктор testRegexl (строка 19) для выполнения операции замены. В этом случае каждое совпа- дение для регулярного выражения "stars" в teststring заменяется на "carets”. В строке 20 создается объект testRegex2 с аргументом §"\d". Обращение к методу экземпляра Replace в стро- ке 39 принимает три аргумента: строку для изменения, строку, содержащую текст замены, и int, указывающий на количество замен, необходимых для выполнения. Другими словами, данная версия метода Replace замещает первые три экземпляра цифры ("\d") в teststring2 на текст "digit" (строка 39). Метод Split делит строку на несколько подстрок. Оригинальная строка делится • любом местоположении, сов- падающем с указанным регулярным выражением. Метод Split возвращает массив, содержащий подстроки ме- жду совпадениями для регулярного выражения. В строке 43 используется версия static метода Split для отде- ления строки целых чисел, разделенных запятой. Первый аргумент— это строка для деления; второй аргу- мент — это регулярное выражение. В данном случае для разделения подстрок при всяком появлении запятой используется регулярное выражение \s". С помощью поиска "белых" пробелов из результирующих подстрок удаляются лишние пробелы. 12.18. Резюме Символы — это основополагающие компоновочные блоки программного кода в С#. Каждая программа состоит из последовательности символов, рассматриваемых компилятором как серия команд, необходимых для выпол- нения поставленной задачи. Строка — это серия символов, рассматриваемая как единый блок. В строку могут входить буквы, цифры и раз- личные специальные символы, например, +, -, *, / и $. Все символы соответствуют числовым кодам. Когда компьютер сравнивает две строки, он, фактически, сравнивает числовые коды символов в строках. После созда- ния строки ее содержимое никогда не меняется. Класс StringBuilder предоставляет изменяемую строку как блок, размер которого может как увеличиваться, так и сокращаться. В хэш-таблице информация сохраняется с помощью специального вычисления на объекте сохранения, при ко- тором создается хэш-код. Последний используется для выбора местоположения в таблице, в которой объект сохраняется. Класс Object определяет метод GetHashCode для выполнения расчета хэш-кода. Скобки в формате string указывают способ форматирования той или иной части информации. Форматы имеют форму {х[, г] [:Formatstring]}, где х— количество аргументов для форматирования, начиная с нуля, y— это необязательный аргумент, который может быть как положительным, так и отрицательным, у указывает, сколько символов должно быть в отформатированном результате; если результирующая строка меньше этого числа, тогда она будет заполнена пробелами для "покрытия" разницы. Если у является положительным целым числом, тогда строка будет выровнена по правому краю; если у— отрицательное целое число, тогда строка будет вы- ровнена по левому краю. Необязательный параметр Formatstring указывает на тип форматирования, который должен быть применен к аргументу: по валюте, десятичный или научный.
Строки, символы и регулярные выражения 409 Структуры сходны с классами; во многом основная разница между ними заключается в том, что структуры ин- капсулируют типы значений, а классы — типы ссылок. Многие из используемых встроенных типов данных на самом деле являются псевдонимами различных структур. Эти структуры являются производными от класса valueType, который, в свою очередь, является производным от класса object. Char — это структура, представ- ляющая символы. Регулярные выражения совпадают с шаблонами в тексте. .NET Framework предоставляет класс Regex в помощь разработчикам при распознавании регулярных выражений и манипуляциях ими. Regex имеет метод Match, воз- вращающий объект класса Match. Данный объект представляет одно совпадение в регулярном выражении. Regex также обеспечивает метод Matches, осуществляющий поиск всех совпадений регулярного выражения в произ- вольной строке и возвращающий Matchcollection — набор совпадений (Match).
ГЛАВА 13 Графика и мультимедиа Одна картина стоит десяти тысяч слов. Китайская пословица Рассматривайте природу как цилиндр, сферу и конус в перспективе. Пол Сезанн Ничто не становится реальным до тех пор, пока человек не испытает это на . самом себе; даже пословица не станет таковой, пока жизнь не подтвердит ее Джон Китс Одного взгляда на картину достаточно, чтобы увидеть то, на что уйдут десят- ки страниц описания. Иван Сергеевич Темы данной главы: □ понятие графических контекстов и графических объектов; О манипуляции с цветами и гарнитурами шрифтов; □ понимание и возможность использования методов GDI+ Graphics для изображения линий, прямоугольни- ков, строк и изображений; □ использование класса image для манипуляций с графикой и ее отображения; О изображение сложных форм из простых с помощью класса Graphics Path; □ использование Windows Media Player и Microsoft Agent в приложениях С#. 13.1. Введение В данной главе рассматриваются инструментальные средства C# для изображения двумерных форм, а также для управления цветами и гарнитурами шрифтов. C# поддерживает графику, позволяющую программистам визу- ально расширять свои Windows-приложения. FCL содержит множество сложных возможностей рисования как части пространства имен System. Drawing и других пространств имен, составляющих .NET-pecypc GDI+. GDI+ (Graphical Device Interface, расширение интерфейса графических устройств) является программным интерфей- сом приложения (API), предоставляющим классы для создания двумерной векторной графики (способа описа- ния графики, при котором ею можно легко манипулировать с помощью высокотехнологичных методик), мани- пулирования шрифтами и вставки графических объектов. GDI+ расширяет GDI путем упрощения модели про- граммирования и ввода нескольких новых функций: графических путей, поддержки формата файла расширенного изображения и "альфа-сопряжения'' (наложения с учетом прозрачности). С помощью API GDI+ программисты могут создавать изображения, не беспокоясь об особенностях платформ графических аппарат- ных средств. Начнем с введения в графические возможности структуры .NET. Затем рассмотрим расширенные функции, та- кие как изменение стиля линий, применяемых для изображения фигур, и управление цветами и узорами залитых фигур. На рис. 13.1 показана часть иерархии класса system. Drawing, содержащая несколько базовых графических клас- сов и структур, описываемых в данной главе. Наиболее часто используемые компоненты GDI+ размещены в пространствах имен System.Drawing И System.Drawing.Drawing2D. Класс Graphics содержит методы, используемые для изображения строк, линий, прямоугольников и других форм в Control. Методы рисования класса Graphics обычно требуют объект Реп (писчее перо) или Brush (кисть) для визуализации заданной фигуры. С помощью Реп изображаются контуры фигуры; Brush создает залитые объекты.
Графика и мультимедиа 411 System-Drawing Jb Font ) Ключ Q класс ( структура Graphics Image ) Color J Point J Rectangle ) Size j HatchBrush ' LinearGradientBrush PathGradientBrush r SolidBrush TextureBrush J Рис. 13.1. Классы и структуры пространства имен System. Drawing Структура color содержит многочисленные свойства static, задающие цвета различных графических компо- нентов, а также методы, дающие пользователям возможность создания новых цветов. Класс Font содержит свойства, определяющие уникальные гарнитуры шрифтов. Класс FontFamily содержит методы для получения информации о шрифте. Перед началом рисования в C# необходимо понять систему координат GDI+ (рис. 13.2)— схему для опреде- ления местоположения каждой точки на экране. По умолчанию верхний левый угол компонента GUI (например, Panel или Form) имеет координаты (0,0). В пару координат входит координата х (горизонтальная координата) и координата у (вертикальная координата). Координатах — это расстояние по горизонтали (вправо) от верхнего левого угла. Координата у — это расстояние по вертикали (вниз) от верхнего левого угла. Ось х определяет каждую горизонтальную координату, а ось у — каждую вертикальную. Программисты размещают текст и фигу- ры на экране указанием их координат (х,у). Координаты измеряются в пикселах, являющихся наименьшими единицами разрешения монитора компьютера. Осьх (*< У) Ось у Рис. 13.2. Система координат GDI+. Единицы измеряются в пикселах Пространство имен System. Drawing предоставляет структуры Rectangle и Point. Структура Rectangle опреде- ляет прямоугольные формы и их размеры. Структура Point представляет координаты (х, у) точки на двумерной плоскости. Совет по улучшению переносимости_____________________________________________________ Разные мониторы имеют различные разрешения, поэтому плотности пикселов мониторов различаются. При этом размеры графических объектов на разных мониторах могут выглядеть по-разному
412 Глава 13 В оставшейся части главы мы исследуем методики манипуляций изображениями и создания плавной анимации. Мы также рассмотрим класс image, в котором можно сохранять изображения в различных форматах файлов и управлять ими. Далее объясним комбинирование возможностей графической визуализации, описанной в первых разделах главы, с возможностями обработки изображений. 13.2. Графические контексты и графические объекты Графический контекст C# представляет собой поверхность, обеспечивающую рисование на экране. Объект Graphics управляет графическим контекстом и позволяет увидеть, как изображается информация. Объект Graphics содержит методы для рисования, манипуляций гарнитурами шрифтов, цветами и выполнения других действий, связанных с графическим изображением. Каждое Windows-приложение, получающее производные от System.Windows.Forms.Form, наследует виртуальный (virtual) обработчик события OnPaint, в котором выпол- няется большинство графических операций. К аргументам метода OnPaint относится объект PaintEventArgs, Из которого можно получить объект Graphics для управления. Объект Graphics необходимо получать для каждого обращения к методу, потому что свойства графического контекста, представляемые графическим объектом, могут меняться. Метод OnPaint запускает событие Paint класса Control. При отображении графической информации в клиентской области Form программисты могут подменять метод OnPaint для получения объекта Graphics из аргумента PaintEventArgs, либо для создания нового объекта Graphics, связанного с соответствующей плоскостью (поверхностью). Далее в главе описываются эти методы графического изображения в С#. Для подмены унаследованного метода OnPaint используется следующее определение метода: protected override void OnPaint(PainteventArgs e) Затем входящий объект Graphics извлекается из аргумента PaintEventArgs: Graphics graphicsObject = e.Graphics; Теперь для изображения фигур и строк на форме доступна переменная graphicsobject. Обращение к методу OnPaint вызывает событие Paint. Вместо подмены метода OnPaint программисты могут добавить обработчик для события Paint. Visual Studio .NET генерирует обработчик событий Paint в следующем виде: protected void MyEventHandler_Paint( object sender, PaintEventArgs e) Программисты редко используют метод OnPaint напрямую, потому что рисование графики— событийно- управляемый процесс. Событие, например, раскрытие, закрытие окна или изменение его размера, вызывает ме- тод OnPaint формы. Точно так же при отображении любого элемента управления (например, TextBox или Label) программа вызывает метод Paint этого элемента управления. Если разработчикам нужно, чтобы метод OnPaint выполнялся явно, то его не следует вызывать. Вместо этого можно вызвать метод invalidate (унаследованный от Control). Данный метод обновляет клиентскую область элемента управления и неявно перерисовывает все графические компоненты. C# содержит несколько перегру- женных методов invalidate, дающих программистам возможность обновления частей клиентской области. Совет по повышению производительности______________________________________________________ Вызов метода Invalidate для обновления Control часто неэффективен. Вместо этого рекомендуется вызы- вать Invalidate с параметром Rectangle для обновления только области, обозначенной прямоугольником. Это повышает производительность программы. Такие элементы управления, как Label и Button, не имеют собственных графических контекстов, но их можно создать. Для изображения на элементе управления сначала следует создать графический объект обращением к методу CreateGraphics: Graphics graphicsObject = controlName.CreateGraphics(); где graphicsObject обозначает экземпляр класса Graphics, a controlName— любой элемент управления. Теперь для изображения на элементе управления программист может пользоваться методами, представленными в клас- се Graphics.
Графика и мультимедиа 413 13.3. Управление цветом Цвета значительно улучшают внешний вид программы и делают ее более наглядной. Например, красный цвет светофора означает остановку, желтый — сигнал подготовиться, а зеленый — разрешение на дальнейшее дви- жение. Структура Color определяет методы и константы, используемые для манипуляций цветом. Будучи достаточно легковесным, выполняющим всего несколько операций и сохраняющим поля static, объект Color реализуется как структура, а не как класс. Каждый цвет можно создать из комбинации значений альфа-прозрачности, красного, зеленого и синего компо- нентов. Все вместе эти компоненты называются значениями ARGB Все четыре компонента ARGB являются байтами (byte), представляющими целые числа от 0 до 255. Альфа-значение определяет непрозрачность цвета. Например, если значение альфа равно 0, то цвет становится прозрачным; результатом значения 255 — стано- вится насыщенный цвет. Значения альфа между 0 и 255 дают взвешенный эффект совмещения RGB-значения цвета со значением любого фонового цвета, делая цвет полупрозрачным. Первое число в RGB-значении опреде- ляет количество красного в цвете, второе — количество зеленого, а третье — синего. Чем больше значение, тем больше процентный вес того или иного цвета. C# дает программистам возможность выбора из порядка 17 млн цветов. Если монитор компьютера не способен отобразить все эти цвета, то будет выбран цвет, ближайший к заданному. В табл. 13.1 представлены некоторые предварительно определенные константы Color, а в табл. 13.2 описано несколько методов и свойств Color. В табл. 13.2 описаны два обращения к методу FromArgb. Одно принимает три аргумента int, а другое — четыре аргумента int (все значения аргументов должны входить в диапазон от 0 до 255). Оба обращения принимают аргументы int, указывающие количество красного, зеленого и синего цветов. Перегруженная версия принимает четыре аргумента и обеспечивает пользователю возможность задания значения альфа; версия с тремя аргумен- тами по умолчанию задает значение альфа, равное 255. Оба метода возвращают объект Color, представляющий заданные значения. Свойства Color — a, r, g и в возвращают байты, представляющие значения int от 0 до 255, соответствующие количеству красного, зеленого и синего цветов соответственно. Таблица 13.1. Константы static структуры Color и их значения RGB Константы в структуре Color (все public static) Значение RGB Константы в структуре Color (все public static) Значение RGB Orange (оранжевый) 255, 200, 0 White (белый) 255, 255, 255 Pink (розовый) 255, 175, 175 Gray (серый) 128, 128, 128 Cyan (голубой) 0, 255, 255 4 parkGray (темно-серый) 64, 64,64 Magenta (пурпурный) 255, 0, 255 Red (красный) 255, 0. 0 Yellow (желтый) 255, 255, 0 Green (зеленый) 0, 255, 0 Black (черный) 0, 0,0 Blue(синий) 0, 0. 255 Таблица 13.2. Члены структуры Color Методы и свойства структуры Color Описание Общие методы Static FromArgb Создание цвета на основе значений красного, зеленого и синего цветов, выраженных как int от 0 до 255. Перегруженная версия обеспечивает спецификацию значений альфа, красного, зеленого и синего цвета Static FromName Создание цвета из имени, переданного как string Общие свойства А byte между 0 и 255, представляющий альфа-компонент R byte между 0 и 255, представляющий компонент красного цвета G byte между 0 и 256, представляющий компонент зеленого цвета В byte между 0 и 255, представляющий компонент синего цвета
414 Глава 13 Разработчики изображают фигуры и строки с помощью кистей (Brush) и перьев (Реп). Объект Реп работает по- добно обычной шариковой ручке и используется в большинстве методов рисования. Перегруженные конструк- торы Реп обеспечивают программистам возможность задания цветов и значений толщины линий. Пространство имен System.Drawing также предоставляет коллекцию объектов Реп, содержащую предварительно заданные объекты Реп. Все классы, производные от абстрактного класса Brush, определяют объекты, задающие цвет внутренней части графических фигур (например, конструктор SolidBrysh принимает объект Color — цвет, которым будет выпол- нено изображение). В большинстве методах Fill кисти (Brush) заполняют пространство цветом, узором или графическим изображением. В табл. 13.3 представлены кисти и их функции. I Таблица 13.3. Классы, производные от класса Brush Класс Описание HatchBrush Использование прямоугольной кисти для заполнения области узором. Узор определяет- ся членом перечисления Hatchstyle, цветом переднего плана (которым изображен узор) и фоновым цветом LinearGradientBrush Заполнение области постепенным смешением одного цвета с другим. Линейные гради- енты определяются вдоль линии. Их можно задать двумя цветами, углом градиента и либо шириной прямоугольника, либо двумя точками Solidbrush Заполнение области одним цветом Определяется объектом color TextureBrush Заполнение области повторением заданного изображения (image) вдоль поверхности Программа из листинга 13.1 демонстрирует несколько методов и свойств, описанных в табл. 13.3. В программе представлены два пересекающихся прямоугольника, позволяющих пользователю экспериментировать со значе- ниями и именами цветов. 1 // Листинг 13.1: ShowColors.cs 2 II Использование различных цветов в C# 3 4 using System; 5 using System.Drawing; 6 using System.Collections; 7 using System.ComponentModel; 8 using System.Windows.Forms; 9 using System.Data; 10 11 // изменение цвета с помощью названия 12 // цвета или значений argb 13 class ShowColors : System.Windows.Forms.Form 14 { 15 private System.ComponentModel.Container components = null; 16 17 // цвет черного прямоугольника 18 private Color behindColor = Color.Wheat; 19 private System.Windows.Forms.GroupBox nameGroup; 20 private System.Windows.Forms.GroupBox colorValueGroup; 21 private System.Windows.Forms.TextBox colorNameTextBox; 22 private System.Windows.Forms.TextBox alphaTextBox; 23 private System.Windows.Forms.TextBox redTextBox; 24 private System.Windows.Forms.TextBox greenTextBox; 25 private System.Windows.Forms.TextBox blueTextBox; 26 private System.Windows.Forms.Button colorValueButton; 27 private System.Windows.Forms.Button colorNameButton; 28 29 // цвет переднего прямоугольника 30 private Color frontcolor = 31 Color.FromArgb1100, 0, 0, 255); 32
Графика и мультимедиа 415 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 [STAThead] static void Main() { Application.Run(new ShowColors()); } // код, сгенерированный Visual Studio .NET // подмена метода OnPaint форды protected override void OnPaint(PaintEventArgs e) { Graphics graphicsObject = e.Graphics; // получение графики // создание кисти для изображения текста SolidBrush textBrush = new SolidBrush(Color.Black); // создание кисти заливки SolidBrush textBrush = new SolidBrush(Color.White); // изображение белого фона graphicsObject.FillRectangle(brush, 4, 4, 275, 180); // отображение названия behindColor graphicsObject.Drawstring(behindColor.Name, this.Font, textBrush, 40, 5); // установка цвета кисти и отображение заднего прямоугольника brush.Color = behindColor; graphicsObject.FillRectangle(brush, 45, 20, 150, 120); // отображение значений Argb цвета переднего плана graphicsObject.Drawstring("Alpha: " + frontColor.A + "Red: " + frontColor.R + " Green: " + frontcolor.G + " Blue: " + frontcolor.B, this.Font, textBrush, 55, 165); // цвет кисти и отображение переднего прямоугольника brush.Color = frontcolor; i. ? graphicsObject.FillRectangle(brush, 65, 35, 170, 130); ) // конец метода OnPaint 4 // обработка события colorValueButton_click private void colorValueButton_Click( object sender, System.EventArgs e) { // получение нового цвета переднего плана из текстовых полей frontcolor = Color.FromArgb(Convert.Tolnt32( alphaTextBox.Text), Convert.Tolnt32(redTextBox.Text), Convert.Tolnt32(greenTextBox.Text), Convert.Tolnt32(blueTextBox.Text)), Invalidate(); // обновление Form ) // обработка события colorNameButton_click private void colorNameButtonjSlick( object sender. System.EventArgs e) ( // установка behindColor на цвет, указанный в текстовом поле behindColor = Color.FromName(colorNameTextBox.text);
416 Глава 13 98 Invalidate(); // обновление Form 99 } 100 101 } // конец класса ShowColors С началом выполнения приложение вызывает метод OnPaint класса showcolor для раскраски окна. В строке 44 получается ссылка на Graphics-объект PaintEventArgs е и присваивается объекту класса Graphics — graphicsObject. В строках 47—50 создается черная и белая кисти SolidBrush для рисования на форме. Класс Solidbrush является производным от абстрактного базового класса Brush; с помощью SolidBrush программи- сты могут рисовать закрашенные фигуры. Метод FillRectangle класса Graphics изображает прямоугольник, залитый белым цветом с помощью кисти, представленной как параметр (строка 53). В качестве параметров метод принимает кисть, координаты (х, у) точ- ки, а также ширину и высоту прямоугольника, который нужно изобразить. Точка обозначает верхний левый угол прямоугольника. В строках 56—57 указано свойство string Name свойства Color класса Brush с методом GraphicsDrawString. Программист имеет доступ к нескольким перегруженным методам Drawstring; версия, показанная в строках 56—57, принимает строку для отображения, гарнитуру шрифта (Font), Brush и координа- ты (х, у) местоположения первого символа строки. Строки 60—62 присваивают значение Color behindcolor свойству Color класса Brush и отображают прямо- угольник. В строках 65—68 выделяются и отображаются значения ARGB Color frontcolor, после чего рисуется заполненный прямоугольник, накладывающийся на первый. Обработчик события colorValueButton_Click класса Button (строки 78—89) использует метод FromArgb класса Color для построения нового объекта Color из значений ARGB, которые пользователь задает в текстовых полях. После этого обработчик события присваивает вновь созданный объект color frontcolor. Обработчик события colorNameButton_Click класса Button (строки 92—99) использует метод FromName класса Color для создания нового объекта Color из colorName, которое пользователь вводит в текстовое поле. Этот color присваивается behindColor. Если пользователь присваивает frontcolor значение альфа в диапазоне от 0 до 255, то эффекты альфа- сопряжения становятся очевидными. На выходе красный прямоугольник заднего плана сливается с синим пря- моугольником переднего плана, и в месте их наложения создается фиолетовый цвет. Результат работы программы представлен на рис. 13.3. Рис. 13.3. Демонстрация значения цвета и параметра альфа Замечание по технологии программирования_______________________________________________ В классе Color нет никаких методов, позволяющих программистам изменять характеристики текущего цвета. Для использования другого цвета следует создать объект Color. Предварительно определенный компонент ColorDiaiog интерфейса представляет собой диалоговое окно, даю- щее пользователям возможность выбора цвета из доступной палитры. Оно также предлагает опцию создания специальных цветов. Программа в листинге 13.2 демонстрирует использование такого диалогового окна. Когда пользователь выбирает цвет и нажимает кнопку ОК, приложение принимает выбор через свойство Color ком- понента ColorDiaiog.
. Графика и мультимедиа 417 GUI для данного приложения содержит две кнопки (buttons). С помощью верхней — backgroundColorButton — можно изменять фоновые цвета формы и кнопки. С помощью нижней — textColorButton — можно изменять цвет текста кнопки. 1 // Листинг 13.2: ShowColorsComplex.cs 2 // Изменение цветов фона и текста формы 3 4 using System; 5 using^System.Drawing; 6 using System.Collections; 7 using System.ComponentModel; 8 using System.Windows.Forms; 9 using System.Data; 10 11 // позволяет пользователям изменять цвета с помощью ColorDialog 12 public class ShowColorsComplex : System.Windows.Forms.Form 13 { 14 private System.Windows.Forms.Button backgroundColorButton; 15 private System.Windows.Forms.Button textColorButton; 16 17 private System.ComponentModel.Container components = null; 18 19 [STAThread] 20 static void Main() 21 { 22 Application.Run(new ShowColorsComplex()); 23 } 24 25 // код, сгенерированный Visual Studio .NET 26 27 // изменение цвета текста 28 private void textColorButton_Click( 29 object sender, System.EventArgs e) 30 { 31 // создание объекта ColorDialog 32 ColorDialog colorchooser « new ColorDialog(); 33 DialogResult result; 34 35 // получение выбранного цвета 36 result = colorChooser.ShowDialog(); , 37 38 if (result == DialogResult.Cancel) 39 return 40 41 // присвоение цвета переднего плана результату диалогового окна 42 backgroundColorButton.ForeColor = colorChooser.Color; 43 textColorButton.ForeColor = colorChooser.Color; 44 45 } // конец метода textColorButton_Click 46 47 // изменение фонового цвета 48 private void backgroundColorButton_Click( 49 object sender, System.EventArgs e) 50 { 51 // создание объекта ColorDialog 52 ColorDialog colorChooser = new ColorDialog(); 53 DialogResult result; 54 55 // показать ColorDialog и получение результата 56 colorChooser.FullOpen = true; 57 result = colorChooser.ShowDialog (); 58 27 Зак. 3333
418 Глава 13 59 if (result == DialogResult.Cancel) 60 return; 61 62 . // задание фонового цвета 63 this.backColor = colorchooser.Color; 64 65 } // конец метода backgroundColorButton_Click 66 67 } // конец класса ShowColorsComplex Строки 28—45 определяют обработчик событий, вызываемый при нажатии пользователем кнопки textColorButton. Данный обработчик события создает новый объект ColorDialog с именем colorchooser и ак- тивизирует его метод ShowDialog, отображающий окно. Свойство Color объекта colorchooser сохраняет поль- зовательские настройки. Строки 42—43 меняют цвет текста обеих кнопок на выбранный. Строки 48—65 определяют обработчик событий для backgroundColorButton. Данный метод изменяет фоновый цвет формы установкой BackColor равным свойству Color диалогового окна. Метод создает новый объект ColorDialog и присваивает свойству Fullopen значение true. Теперь в окне отображены все доступные цвета, как показано на рис. 13.4. Отображение регулярных цветов не показывает правую часть экрана. а Рис. 13.4. Изменение цвета фона и цвета текста с помощью компонентов ColorDialog: а — системное диалоговое окно для изменения цвета; б — исходное состояние формы; в — вид формы после выбора цвета б Возможности пользователей не ограничиваются 48 цветами объекта ColorDialog. Для создания специального цвета можно щелкнуть кнопкой мыши на крупном прямоугольнике в ColorDialog; при этом отобразятся раз- личные оттенки цветов. Для выбора нужного оттенка и насыщенности цвета используется ползунок. По завер- шении настроек пользователь нажимает кнопку Add to Custom Colors, после чего специально настроенный цвет добавляется в раздел специальных (пользовательских) цветов диалогового окна. При нажатии кнопки ОК устанавливается свойство Color диалогового окна ColorDialog. После выбора цвета и нажатия кнопки ОК диа- логового окна фон цвета приложения меняется. 13.4. Управление гарнитурами шрифтов В данном разделе представлены методы и константы, имеющие отношение к управлению гарнитурами шриф- тов. Если гарнитура (класс Font) выбрана, то ее свойства изменять нельзя. Если требуется другая гарнитура, то необходимо выбрать новый объект Font. Для создания специальных гарнитур существует много перегруженных версий конструктора Font. Некоторые свойства класса Font в общем виде представлены в табл. 13.4. Обратите внимание, что свойство size возвращает размер гарнитуры в единицах измерения, заданных при про- ектировании, тогда как SizeinPoints возвращает размер гарнитуры в пунктах (более общая единица измере- ния). Говоря, что свойство size измеряет размер гарнитуры в проектных единицах, имеется в виду то, что раз- мер гарнитуры можно задать по-разному, например, в дюймах или в миллиметрах. Некоторые версии конструк- тора Font принимают аргумент Graphicsunit — перечисление, дающее пользователям возможность задавать единицу измерения, применяющуюся для описания размера гарнитуры. В число членов перечисления GraphicsUnit входят Point (1/72 дюйма), Display (1/75 дюйма), Document (1/300 дюйма), Millimeter, Inch и Pixel. При наличии этого аргумента свойство Size содержит размер гарнитуры шрифта в проектных единицах,
Графика и мультимедиа 419 а свойство SizeinPoints преобразует размер в пункты. Например, если создается Font с размером 1 и для изме- рения гарнитуры задается использование Graphicsunit. inch, тогда свойство size будет равно 1, а свойство SizeinPoints — 72. Если применяется конструктор, не принимающий член Graphicsunit, тогда размер гарни- туры по умолчанию будет GraphicsUnit. Point (следовательно, свойства Size И SizeinPoints будут равны). Таблица 13.4. Свойства только для чтения класса Font Свойство Описание Bold Проверяется, является ли стиль шрифта полужирным. Возвращается значение true, если гарни- тура имеет полужирный стиль FontFamily Представление FontFamily гарнитуры (структуры группирования для организации гарнитур и оп- ределения их сходных свойств) Height Представление высоты гарнитуры Italic Проверяется, является ли стиль шрифта курсивным. Возвращается значение true, если гарниту- ра имеет курсивный стиль Name Названия гарнитуры в виде строки Size Возвращается значение типа float, указывающее текущий размер гарнитуры в лроектных едини- цах (проектные единицы — любые заданные единицы измерения для гарнитуры) f SizeinPoints Возвращается значение типа float, указывающее текущий размер гарнитуры в пунктах Strikeout Проверяется, является ли стиль шрифта перечеркнутым. Возвращается значение true, если гар- нитура имеет перечеркнутый стиль Underline Проверяется, является ли стиль шрифта подчеркнутым. Возвращается значение true, если гар- нитура имеет подчеркнутый стиль Класс Font имеет несколько конструкторов. Большинство из них требуют названия гарнитуры шрифта (font name) — строку, обозначающую гарнитуру шрифта, поддерживаемую в данный момент системой. В число са- мых распространенных шрифтов входят SansSerif и Serif от корпорации Microsoft. Также конструкторы, как правило, требуют в качестве аргумента размер гарнитуры шрифта (font size). Наконец, конструкторы Font обычно требуют стиль гарнитуры шрифта (font style), заданного перечислением Fontstyle: Bold (полужир- ный), italic (курсив), Regular (обычный), strikeout (перечеркнутый), Underline (подчеркнутый). Стили гар- нитуры можно комбинировать с помощью операции | (например, при использовании Fontstyle. italic | Font style. Bold гарнитура выделяется полужирным курсивом). Метод Drawstring класса Graphics устанавливает текущую гарнитуру для рисования — ту, которой отображает- ся текст, — в аргумент Font. Распространенная ошибка программирования_______________________________________________ Указание недоступной в системе гарнитуры является логической ошибкой. При этом C# заменит системную гар- нитуру, заданную по умолчанию. Программа, представленная в листинге 13.3, отображает текст четырьмя разными гарнитурами различных раз- меров. В программе использован конструктор *Font для инициализации объектов Font (строки 32—47). Каждое обращение к конструктору Font передает в виде строки название гарнитуры (например, Arial, Times New Roman, Courier New или Tahoma), размер гарнитуры (float) и объект Fontstyle (style). Метод Drawstring класса Graphics устанавливает гарнитуру и изображает текст в заданном местоположении. Обратите внимание, что в строке 29 создается объект SolidBrush; при этом все строки, изображенные с помощью данной кисти, появ- ляются DarkBlue (темно-синий цвет). Замечание по технологии программирования________________________________________________ Изменить свойства объекта Font нельзя; для использования другой гарнитуры необходимо создать новый объ- ект Font. Листинг 13.3. Гарнитуры шрифтов и и ' стили ;...... ..... -.; ...... 1 // Листинг 13.3: UsingFonts.cs 2 // Демонстрация различных настроек гарнитур 3
420 Глава 13 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 using System; using System.Drawing; using System.Collections; using System.ComponentModel; using System. Windows. Forms; using System.Data; // демонстрация конструкторов и свойств гарнитур public class UsingFonts : System.Windows.Forms.Form { private System.ComponentModel.Container components = null; [STAThread] static void Main() { Application.Run(new UsingFonts()); } // код, сгенерированный Visual Studio .NET // демонстрация различных настроек гарнитур и стилей protected override void OnPaint( PaintEventArgs paintEvent) { Graphics graphicsObject = paintEvent.Graphics; SolidBrush brush = new Solidbrush(Color.DarkBlue); // arial, 12 пунктов, полужирный Fontstyle style = Fontstyle.Bold; Font arial = new Font(new FontFamily("Arial"), 12, style); // times new roman, 12 пунктов, обычный style = Fontstyle.Regular; Font timesNewRoman = new Font("Times New Roman”, 12, style); // courier new, 16 пунктов, полужирный курсив style = FontStyle.Bold | Fontstyle.Italic; Font courierNew - new Font("Courier New", 16, style); // tahoma, 18 пунктов, перечеркнутый style = Fontstyle.Strikeout; Font Tahoma = new Font("Tahoma", 18, style); graphicsObj ect.Drawstring(arial.Name + " 12 point bold.", arial, brush, 10, 10); graphicsObj ect.Drawstring(timesNewRoman.Name + z " 12 point plain.", timesNewRoman, brush, 10, 30); graphicsObj ect.DrawString(courierNew.Name + " 16 point bold and italic.", courierNew, brush, 10, 54); graphicsObj ect.DrawString(tahoma.Name + " 18 point strikeout.”, Tahoma, brush, 10, 75); } // окончание метода OnPaint } // окончание класса UsingFonts Результат работы программы представлен на рис. 13.5.
Графика и мультимедиа 421 Рис. 13.5. Гарнитуры шрифтов и их стили Программисты могут задать точную информацию о метрике (или свойствах) гарнитуры, например, о высоте шрифта, высоте нижнего выносного элемента, высоте строчного знака и интерлиньяже. На рис. 13.6 эти свойства представлены наглядно. Рис. 13.6. Иллюстрация метрик гарнитуры Класс FontFamily определяет характеристики, присущие группе связных между собой гарнитур. Класс FontFamily предоставляет несколько методов, применяемых для определения метрик гарнитуры, совместно ис- пользуемых членами того или иного семейства шрифтов. Эти методы в общем виде представлены в табл. 13.5. Таблица 13.5. Методы FontFamily, возвращающие информацию о метрике гарнитуры Метод Описание GetCellAscent Возвращает значение типа int, представляющее высоту строчного знака гарнитуры шрифта, измеренную в проектных единицах GetCellDescent Возвращает значение типа int, представляющее высоту нижнего выносного элемента шрифта, измеренную в проектных единицах GetEmHeight Возвращает значение типа int, представляющее высоту гарнитуры шрифта, измеренной в про- ектных единицах GetLineSpacing Возвращает значение типа int, представляющее расстояние между двумя последовательными строками текста, измеренное в проектных единицах Программа, показанная в листинге 13.4, вызывает метод Tostring для отображения метрик двух гарнитур. Строка 32 создает Font arial и задает размер гарнитуры Arial — 12 пунктов. В строке 33 используется свойство FontFamily класса Font для получения объекта FontFamily объекта arial. Строки 38—39 вызывают ToString для вывода строкового представления гарнитуры шрифта. Затем строки 41—55 используют методы класса FontFamily для возвращения целых чисел, обозначающих высоту строчного знака, высоту нижнего выносного элемента, высоту шрифта и интерлиньяж. Строки 58—77 повторяют этот процесс для гарнитуры sansSerif — объекта Font, производного от FontFamily для гарнитуры MS Sans Serif. Г"". -—w чпвашцамшанинннааг.. вяв-иива ж пяв - - --- — » -------"1 [’Лис гинг 13.4. Класс. ^ontFamixy, использованный для получения информации о метриках гарнитур шрифтов \..Л '. ... ...... ...... . . .. ... ... « 1 // Листинг 13.4: UsingFontMetrics.cs 2 // Отображение информации о метриках гарнитуры 3 4 using System; 5 using System.Drawing; 6 using System.Collections; 7 using System.ComponentModel; 8 using System.Windows.Forms; 9 using System.Data; 10
422 Глава 13 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 // отображение информации о гарнитуре public class UsingFontMetrics : System.Windows.Forms.Form { private System.ComponentModel.Container components = null; [STAThread] static void MainO { Application.Run (new UsingFontMetrics’()) ; } // код, сгенерированный Visual Studio .NET // отображение информации о гарнитуре шрифта protected override void OnPaint( PaintEventArgs paintEvent) { Graphics graphicsObject = paintEvent.Graphics; SolidBrush brush = new Solidbrush(Color.DarkBlue); // Метрика гарнитуры Arial Font arial = new Font("Arial", 12); FontFamily family = arial.FontFamily; Font sanserif = new Font("Microsoft Sans Serif", 14, Fontstyle. Italic);. // отображение метрики гарнитуры Arial graphicsObject.DrawString("Current Font: " + arial.ToString(), arial, brush, 10, 10); graphicsObject.Drawstring("Ascent: " + family.GetCellAscent(Fontstyle.Regular), arial brush, 10, 30); graphicsObject.Drawstring("Descent: " + family.GetCellDescent(Fontstyle.Regular), arial brush, 10, 50); graphicsObject.Drawstring(^'Height: " + family.GetEmHeight(Fontstyle.Regular), arial; brush, 10, 70); graphicsObject.Drawstring("Leading: " + family.GetLinespacing(FontStyle.Regular), arial, brush, 10, 90); // отображение метрик гарнитуры Sans Serif family = sanserif.FontFamily; graphicsObject.Drawstring("Current Font: " + sanserif.ToString(), sanserif, brush, 10, 130); graphicsObject.Drawstring("Ascent: " + family.GetCellAscent(Fontstyle.Regular), sanserif, brush, 10, 150); graphicsObject.Drawstring("Descent: " + family.GetCellDescent(Fontstyle.Regular), sanserif, brush, 10, 170); graphicsObject.Drawstring("Height: " + family.GetEmHeight(FontStyle.Regular), sanserif, brush, 10, 190);
Графика и мультимедиа 423 75 graphicsObject.Drawstring("Leading: " + 76 family.GetLineSpacing(Fontstyle.Regular), sanserif, 77 brush, 10, 210); 78 79 } // окончание метода OnPaint 80 81 } // окончание класса UsingFontMetrics Результат работы программы представлен на рис. 13.7. Descent 434 Leadiflg 23-й ' .Яг? Д-, - Ив Current '-'cm [Font- Hame^Microsoti Sans Serif, Size-14, Umts-3. adiCharSet=1,GdiVerhca Ponl=F ilse] Ascent 1888 4 4 - Ж... Л: 3%. v/* * Descer ‘ 432 ’Й? Height 20*8 Leading: 2318 ) UsingFontMetrics Рис. 13.7. Получение данных о метриках гарнитур шрифтов 13.5. Рисование линий, прямоугольников и эллипсов В данном разделе представлено несколько методов класса Graphics для изображения линий, прямоугольников и эллипсов. Каждый из этих методов рисования имеет несколько перегруженных версий. При использовании ме- тодов рисования фигурных контуров применяются объект Реп и четыре аргумента int; при использовании ме- тодов изображения залитых фигур используются версии с объектом Brush и четырьмя аргументами int. В обо- их случаях первые два аргумента int обозначают координаты верхнего левого угла фигуры либо окружающей ее области, а оставшиеся два аргумента int определяют ширину и высоту фигуры. В табл. 13.6 представлены методы Graphics и их параметры. Таблица 13.6. Методы Graphics, изображающие линии, прямоугольники и эллипсы Методы изображения графических фигур Описание DrawLine(Pen р, int xl, int yl, int х2, int у2) Изображение линии от (xl, yl) до (x2, y2). Pen определяет цвет, стиль и толщину линии DrawRectangle(Pen р, int х, int у, int width, int height) Изображение прямоугольника заданной ширины и высоты. Верхний левый угол прямоугольника находится в точке (х, у). Реп определяет цвет, стиль и ширину границы прямоугольника FillRectangle(Brush b, int x, int y, int width, int height) Изображение залитого прямоугольника заданной ширины и высоты. Верх- ний левый угол прямоугольника находится в точке (х, у). Brush определя- ет стиль заливки внутри прямоугольника DrawEllipse (Pen p, int x, int y, int width, int height) Изображение эллипса внутри прямоугольника. Ширина и высота прямо- угольника остаются заданными, а левый верхний угол находится в точке (х, у). Реп определяет цвет, стиль и ширину границы эллипса FillEllipse(Brush b, int x, int y, int width, int width) Изображение залитого эллипса внутри прямоугольника. Ширина и высота прямоугольника остаются заданными, а левый верхний угол находится в точке (х, у). Brush определяет стиль заливки внутри эллипса Примечание. Многие из этих методов перегружены; полный список см. в документации. Приложение из листинга 13.5 рисует линии, прямоугольники и эллипсы. В данном приложении также демонст- рируются методы, изображающие залитые и пустые фигуры. Методы FillRectangle и DrawRectangle (строки 33 и 42) изображают на мониторе прямоугольники. Для каждо- го из этих методов первый аргумент задает фигуру для изображения. Метод DrawRectangle использует объект
424 Глава 13 Реп, тогда как метод FillRectangle использует объект Brush (в данном случае — экземпляр SolidBrush — клас- са, производного от Brush). Следующие два аргумента задают координаты верхнего левого угла ограничиваю- щего прямоугольника, обозначающего область, в которой будет нарисован прямоугольник. Четвертый и пятый аргументы задают его ширину и высоту. Метод DrawLine (строки 36—39) принимает Реп и две пары целых зна- чений, обозначающих точки начала и конца линии. После этого данный метод изображает линию с помощью переданного ему объекта Реп. Методы DrawEllipse и FillEllipse предоставляют перегруженные версии, принимающие пять аргументов. В обоих методах первый аргумент обозначает объекты для рисования. Следующие два аргумента задают коор- динаты верхнего левого угла ограничивающего прямоугольника, представляющего область, в которой будет нарисован эллипс. Последние два аргумента задают ширину и высоту ограничивающего прямоугольника соот- ветственно. На рис. 13.8 показан эллипс, ограниченный прямоугольником. Эллипс касается средней точки каж- дой из четырех сторон ограничивающего прямоугольника. Последний на мониторе компьютера не отображается. ................................ —.--------------------------------------...... Листинг 13.5. Демонстрация методов изображения линий, прямоугольников и эллипсов 1 // Листинг 13.5: LinesRectanglesOvals.es. 2 // Демонстрация линий, прямоугольников и овалов 3 4 using System; 5 using System.Drawing; 6 using System.Collections; 7 using System.ComponentModel; 8 using System.Windows.Forms; 9 using System.Data; 10 11 // изображение фигур на форде 12 public class LinesRectanglesOvals : System.Windows.Forms.Form 13 { 14 private System.ComponentModel.Container components = null; 15 16 [STAThread] 17 static void Main() 18 { 19 Application.Run(new LinesRectanglesOvals()); 20 } 21 22 // код, сгенерированный Visual Studio .NET 23 24 protected override void OnPaint( 25 PaintEventArgs paintEvent) 26 { 27 // получение графического объекта 28 Graphics g = paintEvent.Graphics; 29 SolidBrush brush = new SolidBrush(Color.Blue); 30 Pen pen = new Pen(Color. Al iceBlue); 31 32 // создание залитого прямоугольника 33 g.FillRectangle(brush, 90, 30, 150, 90); 34 35 // рисование линий для совмещения прямоугольников 36 g.DrawLine(pen, 90, 30, 110, 40); 37 g.DrawLine(pen, 90, 120, 110, 130); 38 g.DrawLine(pen, 240, 30, 2’60, 40), 39 g.DrawLine(pen, 240, 120, 260, 130); 40 41 // рисование верхнего прямоугольника 42 g.DrawRectangle(pen, 110, 40, 150, 90); 43 44 // установка красного цвета кисти 45 brush.Color = Color.Red; 46
Графика и мультимедиа 425 41 // рисование базового эллипса 48 g.FillEllipse(brush, 280, 75, 100, 50); 49 50 // рисование связующих линий 51 g.DrawLine(реп, 380, 55, 380, 100); 52 g.DrawLine(реп, 280, 55, 280, 100); 53 54 // рисование контура эллипса 55 g.DrawEllipse(реп, 280, 30, 100, 50); 56 57 } // окончание метода OnPaint 58 59 } // окончание класса LinesRectanglesOvals Результат работы программы представлен на рис. 13.9. Рис. 13.8. Эллипс, ограниченный прямоугольником Рис. 13.9. Демонстрация методов изображения линий, прямоугольников и эллипсов 13 .6. Рисование дуг Дуги — это части эллипсов, измеряемые в градусах, начинающиеся со стартового угла и продолжающиеся неко- торое число градусов, называемое углом дуги. Говорится, что дуга развертывает (обходит) угол дуги, начиная с угла засылки. Дуга, проходящая по часовой стрелке, измеряется в положительных градусах, а проходящая в направлении против часовой стрелки, — в отрицательных. На рис. 13.10 показаны две дуги. Обратите внимание, что левая часть рисунка проходит вниз от нулевого градуса приблизительно на 110°. Дуга же в правой части рисунка проходит вверх от нулевого градуса приблизительно на 110°. Обратите внимание на пунктирную рамку на рис. 13.10. Каждая дуга изображается как часть эллипса (осталь- ные части не показаны) При изображении эллипса его размеры задаются в форме ограничивающего прямо- угольника, в котором заключен эллипс. Рамки на рис. 13.10 соответствуют этим ограничивающим прямоуголь- никам. Методы класса Graphics, используемые для изображения дуг,— DrawArc, DrawPie и FillPie — пред- ставлены в табл. 13.7. Положительный угол Отрицательный угол / Рис. 13.10. Положительные и отрицательные углы дуг
426 Глава 13 Таблица 13.7. Методы Graphics для изображения дуг Графические методы Описание DrawArc(Pen p, int x, int y, int width, int height, int startAngle, int sweepAngle) Изображение дуги эллипса, начиная с угла startAngle (в градусах), с проходом значений градусов sweepAngle Эллипс определяется огра- ничивающим прямоугольником с шириной w, высотой h и координатами верхнего левого угла (х, у). Реп определяет цвет, стиль и толщину дуги DrawPie(Pen p, int x, int y, int width, int height, int startAngle, int sweepAngle) Изображение сектора эллипса, начиная с угла startAngle (в градусах), с проходом значений градусов sweepAngle. Эллипс определяется огра- ничивающим прямоугольником с шириной w, высотой h и координатами верхнего левого угла (х, у). Реп определяет цвет, стиль и толщину дуги FillPie(Brush b, int x, int y, int width, int height, int startAngle, int sweepAngle) Функции, сходные с DrawPie, за исключением того, что дуга изобража- ется заполненной (сектор). Brush определяет стиль заливки области, охватываемой дугой DrawEllipse(Pen p, int x, int y, int width, int height) Изображение эллипса внутри прямоугольника. Ширина и высота прямо- угольника остаются заданными, а левый верхний угол находится в точке (х, у). Реп определяет цвет, стиль и ширину границы эллипса Примечание. Многие из этих методов перегружены; полный список см. в документации. Программа из листинга 13.6 рисует шесть изображений (три дуги и три залитых цветом сектора) для демонст- рации методов изображения дуг, перечисленных в табл. 13.7. Для изображения ограничивающих прямоугольни- ков, определяющих размеры и местоположения дуг, дуги показаны внутри красных прямоугольников, имеющих те же координаты (х, у), а также аргументы ширины и высоты, что определяют ограничивающие прямоугольни- ки д ля дуг. Строки 28—35 создают объекты, необходимые для изображения различных дуг: объекты класса Graphics — Rectangle, SolidBrushe и Реп. После этого строки 38—39 изображают прямоугольник и дугу внутри прямо- угольника. Дуга проходит 360°, образуя окружность. Строка 42 изменяет местоположение Rectangle установ- кой свойства Location на новую точку. Конструктор Point принимает координаты (х, у) новой точки. Свойство Location определяет верхний левый угол Rectangle. После изображения прямоугольника программа изобража- ет дугу, начинающуюся с 0° и проходящую 110°. По причине того, что значение углов в C# увеличиваются по часовой стрелке, дуга уходит вниз. В строках 47—49 выполняются сходные действия, за исключением того, что указанная дуга проходит -270°. Свойство size метода Rectangle определяет высоту и ширину дуги. В строке 53 устанавливается свойство size на новый объект Size, изменяющий размер прямоугольника. Оставшаяся часть программы сходна с частями, описанными выше, за исключением того, что объект SolidBrush используется с методом FillPie. 1 // Листинг 13.6: DrawArcs.cs 2 // Изображение на форме различных дуг 3 4 using System; 5 using System.Drawing; 6 using System.Collections; 7 using System.ComponentModel; 8 using System.Windows.Forms; 9 using System.Data; 10 11 // изображение различных дуг 12 public class DrawArcs : System.Windows.Forms.Form 13 { 14 private System.ComponentModel.Container components = null; 15 16 [STAThread] 17 static void Main() 18 { 19 Application.Run(new DrawArcs()); 20 } 21
Графика и мультимедиа 427 22 fl код, сгенерированный Visual Studio .NET 23 24 private void DrawArcs_Paint ( 25 object sender, System.Windows.Forms.PaintEventArgs e) 26 { 27 // получение графического объекта 28 Graphics graphicsObject = e.Graphics; 29 Rectangle rectanglel = 30 new Rectangle(15, 35, 80, 80); 31 SolidBrush brushl = 32 new SolidBrush(Color.Firebrick); 33 Pen penl = new Pen(brushl, 1); 34 SolidBrush brush2 = new SolidBrush(Color.DarkBlue); 35 Pen pen2 = new Pen(brush2, 1); 36 37 // начало c 0 и проход 360 градусов 38 graphicsObj ect.DrawRectangle(penl, rectanglel); 39 graphicsObject.DrawArc(pen2, rectanglel, 0, 360); 40 41 // начало c 0 и проход 110 градусов 42 rectanglel.Location = new Point(100, ~35); 43 graphicsObj ect.DrawRectangle(penl, rectanglel); 44 graphicsObject.DrawArc(pen2, rectanglel, 0, 110); 45 46 // начало c 0 и проход -270 градусов 47 rectanglel.Location = new Point(185, 35); 4 8 graphicsObj ect.DrawRectangle(penl, rectanglel); 49 graphicsObject.DrawArc(pen2, rectanglel, 0, -270); 50 51 // начало c 0 и проход 360 градусов 52 rectanglel.Location = new Point(15, 120); 53 rectanglel.Size = new Size(80, 40); 54 graphicsObject.DrawRectangle(penl, rectanglel); 55 graphicsObject.FillPie(brush2, rectanglel, 0, 360); 56 57 // начало c 270 и проход -90 градусов 58 rectanglel.Location = new Point(100, 120); 59 graphicsObject.DrawRectangle(penl, rectanglel); 60 graphicsObj ect.FillPie( 61 brush2, rectanglel, 270, -90); 62 63 // начало c 0 и проход -270 градусов 64 rectanglel.Location = new Point(185, 120); 65 graphicsObject.DrawRectangle(penl, rectanglel); 66 graphicsObject.FillPie( 67 brush2, rectanglel, 0, -270); 68 69 } // окончание метода DrawArc_Paint 70 71 } // окончание класса DrawArcs Результат работы программы представлен на рис. 13.11. Рис. 13.11. Демонстрация метода Аге 13.7. Рисование многоугольников и полилиний Полигонами называются многоугольные фигуры. Для их изображения используется несколько методов Graphics: DrawLines изображает серии связанных между собой точек, DrawPolygon изображает замкнутые мно- гоугольники, Fillpolygon — заполненные многоугольники. Эти методы описаны в табл. 13.8. Программа, пред- ставленная в листинге 13.7, дает пользователям возможность изображения многоугольников и связанных между собой линий посредством методов, перечисленных в табл. 13.8. Результат работы программы показан на рис. 13.12.
428 Глава 13 Таблица 13.8. Методы Graphics для изображения многоугольников Метод Описание DrawLines Изображение серии связанных между собой линий. Координаты каждой точки задаются в масси- ве точек Point. Если последняя точка отличается от первой, то фигура не является замкнутой DrawPolygon Изображение многоугольника. Координаты каждой точки задаются в массиве объектов Point. Данный метод изображает замкнутый многоугольник, даже если последняя точка отличается от первой FillPolygon Изображение заполненного многоугольника Координаты каждой точки задаются в массиве точек Point. Данный метод изображает замкнутый многоугольник, даже если последняя точка отлича- ется от первой Листинг 13.7. Демонстрация рисования многоугольников 1 // Листинг 13.7: DrawPolygons.cs 2 // Демонстрация многоугольников 3 4 using System; 5 using System.Drawing; 6 using System.Collections; 7 using System.ComponentModel; 8 using System.Windows.Forms; 9 using System.Data; 10 11 public class PolygonForm : System.Windows.Forms.Form 12 { 13 private System.Windows.Forms.Button colorButton; 14 private System.Windows.Forms.Button clearButton; 15 private System.Windows.Forms.GroupBox typeGroup; 16 private System.Windows.Forms.RadioButton 17 filledPolygonOption; 18 private System.Windows.Forms.RadioButton lineOption; 19 private System.Windows.Forms.RadioButton polygonoption; 20 private System.Windows.Forms.Panel drawpanel; 21 22 private 23 System.ConponentModel.Container components = null; 24 25 // содержит список вершин многоугольника 26 private ArrayList points = new ArrayList(); 27 28 // инициализация "пера" и "кисти" по умолчанию 29 Pen реп = new Pen(Color.DarkBlue); 30 31 SolidBrush brush = new Solidbrush(Color.DarkBlue); 32 33 [STAThread] 34 static void Main() 35 { 36 Application.Run(new PolygonForm()); 37 } 38 39 // код, сгенерированный Visual Studio .NET 40 41 // обработчик события drawPanel_Mouse Down 42 private void drawPanel_MouseDown( 43 object sender, System.Windows.Forms.MouseEventArgs e) 44 { 45 // добавление положения мыши в список вершин 46 points.Add(new Point(е.х, e.y)); 47 drawPanel.Invalidate(); // обновление панели 48 49 } // окончание метода drawPanel_MouseDown
Графика и мультимедиа 429 50 51 52 53 54 55 56 57 58 . 59 60 61 62 63 64 65 66 67 68 69 70 71 *12 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 . 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 private void drawPanel_Paint( object sender, System.Windows.Forms.PaintEventArgs e) { // получение графического объекта для панели Graphics graphicsObject = е.Graphics; // если ArrayList имеет 2 или более точек, отобразить фигуру if (points.Count > 1) { 11 получение массива для использования в функциях рисования Point [] pointArray = (Point [])points.ToArray( points[0].GetType(); if (polygonoption.Checked) // изображение многоугольника graphicsObject.DrawPolygon(pen, pointArray); else if (lineOption.Checked) // изображение линий graphicsObj ect.DrawLines(pen, pointArray) ; else if (filledPolygonOption.Checked) // изображение заполненного многоугольника graphicsObject.FillPolygon( brush, pointArray); } } // окончание метода drawPanel_Paint // обработка события clearButton_Click private void clearButton_Click( object sender. System.EventArgs e) { points - new ArrayList(); // удаление точек drawPanel.Invalidate(); // обновление панели } // окончание метода clearButton_Click // обработка события Polygon RadioButton CheckedChanged private void polygonOption_CheckedChanged( object sender, System.EventArgs e) { drawPanel.Invalidate(); // обновление панели ) // окончание метода polygonOption_CheckedChanged // обработка события LineRadioButton_CheckedChanged private void lineOption_CheckedChanged( object sender, System.EventArgs e) { drawPanel.Invalidate(); // обновление панели } // окончание метода lineOption_CheckedChanged // обработка события filledpolygon // RadioButton_CheckedChanged private void filledpolygonOption_CheckedChanged( object sender, System.EventArgs e)
430 Глава 13 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 { drawPanel.Invalidate(); // обновление панели } // окончание метода filledPolygonOption_CheckedChanged // обработка события colorButton_Click private void colorButton_Click( object sender; System.EventArgs e) { // создание нового диалогового окна цвета ColorDialog dialogcolor = new ColorDialog(); // отображение диалогового окна и получение результата DialogResult result = dialogColor.ShowDialog(); // возвращение при отмене пользователем if (result = DialogResult.Cancel) return; pen.Color = dialogColor.Color; // настройка цвета Pen brush.Color = dialogColor.Color; // настройка кисти drawPanel.Invalidate(); // обновление панели } // окончание метода colorButton_Click } // окончание класса PolygonForm б Рис. 13.12. Рисование многоугольников: а — установка переключателя для рисования линий; б — рисование линий; в — установка переключателя для рисования многоугольника; а — установка переключателя для закрашивания многоугольника Для того чтобы пользователь мог задать переменное число точек, в строке 26 объявлен ArrayList points в ка- честве контейнера для объектов Point. Строки 29—31 объявляют объекты Реп и Brush для раскрашивания фи-
Графика и мультимедиа 431 гур. Обработчик события MouseDown (строки 42—49) для Panel drawPanel сохраняет местоположения щелчков клавиш мыши в points ArrayList. После этого вызывается метод invalidate drawPanel, чтобы панель обновля- лась в соответствии с каждой новой точкой. Метод drawPanel Paint (строки 51—82) управляет событием Paint класса Panel. Этот метод принимает объект Graphics панели (строка 55) и, если ArrayList points содержит две или более Points, отображает многоугольник с методом, выбранным пользователем с помощью селективных кнопок интерфейса (строки 58—80). В строках 61—63 из ArrayList посредством метода ТоАггау извлекается Array. Метод ТоАггау может принять один аргумент для определения типа возвращаемого массива; тип прини- мается из первого элемента ArrayList. Метод clearButton (строки 85—92) управляет событием нажатия кнопки Clear, создает пустой ArrayList (при этом старый список стирается) и обновляет изображение на экране. Строки 95—117 определяют обработчики событий CheckChanged для селективных кнопок. Каждый метод, обновляет Panel drawPanel для того, чтобы в панели отобразился выбранный тип рисования. Обработчик события ColorButton_ciick (строки 120—137) позволяет пользователю выбрать новый цвет рисования в ColorDialog с помощью методики, продемонстрированной в листинге 13.2. 13.8. Дополнительные графические возможности В C# имеется много дополнительных графических возможностей. К примеру, иерархия класса Brush включает в себя классы HatchBrush, LinearGradientBrush, PatchGradientBrush И TextureBrush. Программа в листинге 13.8 демонстрирует несколько графических возможностей, например, создание пунктир- ных и жирных линий, а также заполнение фигур узорами. Это всего лишь несколько дополнительных возмож- ностей пространства имен System. Drawing. Результат работы программы представлен на рис. 13.13. “V................... ......................----------------------------------------------------------------Г"й............... " ------ ' ...... -з Листинг 13 8. Фигуры, изображенные на форме .........................к,..,.,,... ......... . ...- ... •«•«•••' ....................... — . • ».»•«»* ... ...... i 1 // Листинг 13.8: DrawShapes.cs 2 // Изображение в форме различных фигур 3 4 using System; 5 using System.Drawing; 6 using System.Collections; 7 using System.ComponentModel; 8 using System.Windows.Forms; 9 using System.Data; 10 using System.Drawing.Drawing2D; 11 12 // изображение фигур разными кистями 13 public class DrawShapesForm : System.Windows.Forms.Form 14 { 15 private Systern.ComponentModel.Container components = null; 16 17 [STAThread] 18 static void Main() 19 { 20 Application.Run(new DrawShapesForm()); 21 } 22 23 // код, сгенерированный Visual Studio .NET 24 25 // изображение различных фигур на форме 26 private void DrawShapesForm_Paint( 27 object sender, Systern.Windows.Forms.PaintEventArgs e) 28 { 29 // ссылки на объект, который будет использован 30 Graphics graphicsObject = е.Graphics; 31 32 // эллипс в прямоугольнике и градиентная кисть 33 Rectangle drawAreal.= 34 new Rectangle(5, 35, 30, 100);
432 Глава 13 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 LinearGradientBrush linearBrush = new LinearGradientBrush(drawAreal, Color.Blue, Color.Yellow, LinearGradientMode.ForwardDiagonal); // Pen и местоположение красного контура прямоугольника Pen thickRedPen = new Pen(Color.Red, 10); Rectangle drawArea2 = new Rectangle(80, 30, 65, 100); // обработка растра Bitmap textureBitmap = new Bitmap(10, 10); // получение растровой графики Graphics graphicsObject2 = Graphics.Fromlmage(textureBitmap); // Brush и Pen, используемые в программе SolidBrush solidColorBrush = new SolidBrush(Color.Red); Pen Coloredpen =new Pen(solidColorBrush); // изображение эллипса, заполненного сине-желтым градиентом graphicsObject.FillEllipse( linearBrush, 5, 30, 65, 100); // изображение толстого красного контура прямоугольника graphicsObj ect.DrawRectangle(thickRedPen, drawArea2); // заполнение textureBitmap желтым цветом solidColorBrush.Color = Color.Yellow; graphicsObject2.FillRectangle( solidColorBrush, 0, 0, 10, 10); 11 небольшой черный прямоугольник в textureBitmap coloredPen.Color = Color.Black; graphicsObject2.DrawRectangle( coloredPen, 1, 1, 6, 6); // небольшой синий прямоугольник в textureBitmap solidColorBrush.Coldr = Color.Blue; graphicsObject2iFillRectangle( solidColorBrush, 1, 1, 3, 3); // небольшой красный квадрат в textureBitmap solidColorBrush.Color = Color.Red; graphicsObject2.FillRectangle( solidColorbrush, 4, 4, 3, 3); // создание текстурной кисти и // отображение текстурированного прямоугольника TextureBrush textureBrush « new TextureBrush(textureBitmap); graphicsObject.FillRectangle( textureBrush, 155, 30, 75, 100); // изображение сектора белым цветом coloredPen.Color = Color.White; coloredPen.Width = 6; graphicsObject.DrawPie( coloredPen, 240, 30, 75, 100, 0, 270); // 'изображение линий зеленым и желты* цвете»* coloredPen.Color = Color.Green; coloredPen.Width =5; 4
Графика и мультимедиа 433 98 graphicsObj ect.DrawLine( 99 coloredPen, 395, 30, 320, 150); 100 101 // изображение закругленной пунктирной линии желтого цвета 102 coloredPen.Color = Color.Yellow; 103 coloredPen.DashCap = DashCap.Round; 104 coloredPen.DashStyle = DashStyle.Dash; 105 graphicsObj ect.DrawLine( 106 coloredPen, 320, 30, 395, 150); 107 108 } // окончание метода DrawShapesForm_Paint 109 110 } // окончание класса DrawShapesForm Рис. 13.13. Фигуры, изображенные на форме В строках 26—108 определяется обработчик события Paint для формы. В строках 35—37 создается объект linearBrush класса LinearGradientBrush, размещенный в пространстве имен System.Drawing.Drawing2D. Класс LinearGradientBrush дает пользователю возможность создания изображений с заливкой цветным градиентом. Класс LinearGradientBrush, использованный в данном примере, принимает четыре аргумента: Rectangle, два аргумента Color и член перечисления LinearGradientMode. В C# все линейные градиенты определены вдоль линии, направленной к конечной точке градиента. Эту линию можно задать либо начальной и конечной точкой, либо диагональю прямоугольника. Первый аргумент— Rectangle drawAreal— задает линию для объекта linearBrush класса LinearGradientBrush. Данный аргумент Rectangle представляет конечные точки линейного градиента: верхний левый угол является начальной точкой, а нижний правый — конечной. Второй и третий ар- гументы задают цвета, которые будут использованы в градиенте. В данном случае цвет эллипса будет постепен- но меняться от Color.Blue к Color.Yellow. Последний аргумент— тип из перечисления LinearGradientMode — задает направление линейного градиента. В примере используется LinearGradientMode.ForwardDiagonall, соз- дающий градиент из верхнего левого в нижний правый угол. После этого в строках 56 и 57 используется метод FiliEllipse класса Graphics для изображения эллипса с помощью linearBrush; цвет постепенно меняется от синего к желтому, как описано выше. В строке 40 создается объект thickRedPen класса Реп. В конструктор объекта thickRedPen передаются Color.Red и аргумент int 10, указывающие, что thickRedPen должен рисовать красные линии шириной 10 пик- селов. В строке 44 создается новое изначально пустое изображение Bitmap. Класс Bitmap может создавать как черно- белые, так и цветные изображения; в нашем случае изображение имеет ширину и высоту по 10 пикселов. Метод Fromlmage (строки 47 и 48) — static-член класса Graphics, принимающий объект Graphics, связанный с Image, который можно использовать для выполнения рисования на изображении. В строках 63—80 выполняется рисо- вание на Bitmap узора, состоящего из черного, синего, красного и желтого прямоугольников и линий. TextureBrush — это кисть, заполняющая внутреннюю область фигуры изображением, а не просто сплошным цветом. В строках 86 и 87 объект textureBrush класса TextureBrush заполняет прямоугольник созданным рас- тровым изображением (Bitmap). Используемая версия конструктора TextureBrush принимает в качестве аргу- мента изображение, определяющее его текстуру. Затем создается изображение сектора с широкой белой линией. В строках 90 и 91 цвет coloredPen устанавлива- ется на white, а ширина изменяется на шесть пикселов. После этого на форме рисуется сектор путем задания классу Реп х-координаты, у-координаты, длины и ширины ограничивающего прямоугольника, а также началь- ный угол и угол поворота. Наконец, в строках 103 и 104 используются перечисления DashCap и Dashstyle пространства имен System.Drawing.Drawing2D для изображения диагональной пунктирной линии. Строка 103 задает свойство DashCap объекта coloredPen (не путать с перечислением DashCap) члену перечисления DashCap. Перечисление 28 Зак. 3333
434 Глава 13 Dashcap задает стили для начала и конца пунктирной линии. В рассматриваемом случае необходимо, чтобы оба конца пунктирной линии были закруглены, поэтому используется Dashcap.Round. Строка 104 задает свойство Dashstyle объекта coloredPen (не путать с перечислением Dashstyle) Dashstyle. Dash, указывая на то, что ли- ния должна быть полностью пунктирной. В следующем примере демонстрируется использование общей траектории (general path). Общая траектория — это фигура, построенная из прямых линий и сложных кривых. Объект класса GraphicsPath (пространство имен System.Drawing.Drawing2D) представляет собой общую траекторию. Класс GraphicsPath обеспечивает функ- цию, предоставляющую возможность создания сложных фигур из примитивных векторных графических объек- тов. Объект класса GraphicsPath состоит из фигур, определенных простыми фигурами. Начальная точка каждо- го объекта векторной графики (например, линии или дуги), добавляемого в траекторию, соединена прямой ли- нией с конечной точкой предыдущего объекта. При обращении к методу cioseFigure он соединяет конечную точку последнего объекта векторной графики с исходной начальной точкой текущей фигуры прямой линией, после чего начинает создание новой фигуры. Метод startFigure начинает создание новой фигуры в рамках траектории, без закрытия предыдущей фигуры. В коде, представленном в листинге 13.9, запрограммированы общие траектории в форме пятиконечных звезд. *Листинг13.^Траектории, использованные для изображения звезд на форме . L __— _________с__.i;___________.............. ;.„...; 1 // Листинг 13.9: DrawStarsForm.cs 2 // Использование траекторий для изображения фигур звезд на форме 3 4 using System; 5 using System.Drawing; 6 using System.Collections; 7 using System.ComponentModel; 8 using System.Windows.Forms; 9 using System.Data; 10 using System.Drawing.Drawing2D; 11 12 // изображение разноцветных звезд 13 public class DrawStarsForm : System.Windows.Forms.Form 14 { 15 private 16 System.ComponentModel.Container components = null; 17 18 [STAThread] 19 static void Main() 20 { 21 Application.Run(new DrawStarsForm()); 22 } 23 24 // код, сгенерированный Visual Studio .NET 25 26 // создание траектории и изображение звезд вдоль нее 27 private void DrawStarsForm_Paint( 28 object sender, System.Windows.Forms.PaintEventArgs e) 29 { 30 Graphics graphicsObject = e.Graphics; 31 Random random = new Randomf); 32 Solidbrush brush = 33 new SolidBrush(Color.DarkMagenta); 34 35 // точки x и у траектории 36 int[] xPoints = 37 { 55, 67, 109, 73, 83, 55, 27, 37, 1, 43 }; 38' int[] yPoints = 39 { 0, 36, 36, 54, 96, 72, 96, 54, 36, 36 }; 40 41 // создание траектории graphics для звезды 42 GraphicsPath star = new GraphicsPath(); 43
Графика и мультимедиа 435 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 // преобразование начала координат в (150, 150); graphicsObject.TranslateTransform(150, 150); // создание изображения звезды из серии точек for (int i = 0; i <= 8; i += 2) star.AddLine(xPoints[i], yPoints[i], xPoints[i +1], yPoints[i +1]); // закрытие формы star.CloseFogure(); // поворот начала координат и изображение звезд for (int i = 1; i <= 18; i++) { graphicsObject.RotateTransform(20); brush.Color = Color.FromArgb( random.Next(200, 255), random.Next(255), random.Next(255), random.Next(255)); graphicsObject.FillPath(brush, star); J } // окончание метода DrawStarsForm_Paint Рис. 13.14. Рисование звезд вдоль траектории // окончание класса DrawStarsForm В строке 45 задается исходная точка объекта класса Graphics. Аргументы к методу TranslateTransform указы- вают, что исходная точка должна быть перемещена в точку с координатами (150,150). В строках 36—39 опре- деляются два массива int, представляя координаты х и у точек звезды, а в строке 42 определяется объект GraphicsPath — star. После этого в цикле for создаются линии для соединения точек звезды, и эти линии до- бавляются в star. Для добавления линии в фигуру используется метод AddLine объекта GraphicsPath. Аргумен- ты к AddLine задают координаты конечных точек линии; каждое новое обращение к методу AddLine добавляет линию из предыдущей точки к текущей точке. Для завершения фигуры в строке 38 применяется метод CloseFigure объекта GraphicsPath. Цикл for в строках 56—65 изображает звезду 18 раз, поворачивая ее вокруг начала координат. В строке 58 ис- пользует метод RotateTransform класса Graphics для перехода к следующей позиции на форме; аргумент ука- зывает угол поворота в градусах. После этого с помощью метода FillPath (строка 64) класса Graphics изобра- жается версия звезды, залитая цветом с помощью объекта Brush, созданного в строках 60—62. Данное прило- жение произвольно определяет цвет SolidBrush с помощью метода Next класса Random. Результат работы программы представлен на рис. 13.14. 13.9. Общие сведения о мультимедиа Язык C# предлагает много удобных способов включения в программные продукты изображений и анимаций. Те, кто уже долгое время работает в области вычислительной техники, использовали компьютеры в основном для выполнения арифметических расчетов. По мере развития технологий люди начинают понимать важность возможностей компьютера при манипуляциях с данными. На сегодняшний день существует множество разно- образных программ трехмерной графики. Мультимедийное программирование — весьма увлекательная (и но- ваторская) область, которая, впрочем, привносит свои сложности. Мультимедийные программные приложения требуют очень мощных компьютеров. До недавнего времени для многих они были попросту "не по карману". Однако современные сверхбыстрые процессоры постепенно дела- ют мультимедийные приложения все более распространенными. Со стремительным развитием мультимедиа пользователи приобретают более быстродействующие процессоры, большие объемы памяти, и для поддержки мультимедийных приложений требуются более широкие полосы пропускания компьютерных и сетевых комму- никаций. Все это помогает развиваться сферам вычислительной техники и телекоммуникаций, создающим ап- паратные, программные средства и сервисы. В оставшихся разделах главы рассмотрим использование графических объектов, других мультимедийных функ- ций и возможностей, а также манипуляции ими. В разд. 13.10 описывается загрузка, отображение и масштаби-
436 Глава 13 рование изображений; в разд. 13.11 демонстрируется анимация изображения; в разд. 13.12 представлены две возможности видеоэлемента управления Windows Media Player, а разд. 13.12 исследует технологии Microsoft Agent. 13.10. Загрузка, отображение и масштабирование графических объектов В число мультимедийных возможностей C# входят рисование, вставка изображений, поддержка анимации и видео. В предыдущих разделах демонстрировались возможности векторной графики С#; в данном же разделе упор сделан на манипуляциях готовыми изображениями. Форма Windows, созданная в листинге 13.10, демонст- рирует загрузку image (пространство имен System. Drawing). Данное приложение дает пользователям возмож- ность ввода нужных значений высоты и ширины для объекта image, после чего последний отображается с за- данными размерами. Результат работы программы представлен на рис. 13.15. 1 // Листинг 13.10: DisplayLogoForm.cs 2 // Отображение графического объекта и изменение его размера 3 4 using System; 5 using System.Drawing; 6 using System.Collections; 7 using System.ComponentModel; 8 using System.Windows.Forms; 9 using System.Data; 10 11 // отображение графического объекта и изменение его размера 12 public class DisplayLogoForm : System.Windows.Forms.Form 13 { 14 private System.Windows.Forms.Button setButton; 15 private System.Windows.Forms.TextBox heightTextBox; 16 private System.Windows.Forms.Label heightLabel; 17 private System.Windows.Forms.TextBox widthTextBox; 18 private System. Windows. Forms .Label widthLabel 19 20 private 21 System.ComponentModel.Container components = null; 22 23 private 24 Image image - Image.FromFile("image/Logo.gif’); 25 private Graphics graphicsObject; 26 27 public DisplayLogoForm() 28 { 29 InitializeComponent(); 30 31 graphicsObject = this.CreateGraphics(); 32 } 33 34 [STAThread] 35 static void Main() 36 { 37 Application.Run(new DisplayLogoForm()); 38 } 39 40 // код, сгенерированный Visual Studio .NET 41 42 private void setButton_Click( 43 object sender, Syste.EventArgs y)
Графика и мультимедиа 437 44 { 45 // получение пользовательских входных данных 46 int width = Convert.Tolnt32(widthTextBox.Text); 47 int height = Convert.Tolnt32(heightTextBox.Text); 48 49 // если заданные размеры слишком большие, 50 // вывод сообщения о возможных проблемах 51 if (width > 375 || height > 225) 52 { 53 MessageBox.Show("Height or Width too large"); 54 55 return; 56 } 57 58 // очистить форму Windows 59 graphicsObject.Clear(this.BackColor); 60 61 // показ изображения 62 graphicsObj ect.Drawlmage( 63 image, 5, 5, with, height); 64 65 } // окончание метода setButton_Click 66 67 } // окончание класса DisplayLogoForm Рис. 13.15. Изменение размера изображения: а — первый вариант: б — второй вариант В строках 23 и 24 объявляется ссылка image класса Image. После этого static-метод FromFile класса Image при- нимает изображение, хранящееся на диске, и присваивает его image (строка 24). В строке 31 используется метод CreateGraphics для создания объекта Graphics, связанного с Form; этот объект служит для рисования на Form. Метод CreateGraphics унаследован из класса Control; все элементы управления Windows, например, Button и Panel также предоставляют этот метод. При нажатии пользователем кнопки Set подтверждается корректность параметров ширины и высоты с тем, чтобы они не были слишком большими. Если параметры верные, то в строке 59 вызывается метод clear класса Graphics для раскрашивания всей формы в текущий цвет фона. Стро-
438 Глава 13 ки 62 и 63 вызывают метод Drawimage класса Graphics со следующими параметрами: изображение для рисова- ния, координата х верхнего левого угла, координата у верхнего левого угла, ширина изображения и высота изо- бражения. Если ширина и высота не соответствуют размерам первоначального изображения, тогда оно масшта- бируется в соответствии с новыми спецификациями. 13.11. Анимация серии изображений В следующем примере анимируется серия изображений, сохраненных в массиве. В данном приложении исполь- зуются те же методики загрузки и показа изображений, что и в листинге 13.10. Изображения созданы в Adobe Photoshop. В коде из листинга 13.11 используется объект PictureBox, содержащий анимируемые изображения. Для прохо- да изображений по циклам предназначен объект Timer; при этом новое изображение появляется каждые 50 мс. Переменная count хранит количество текущих изображений и увеличивается на единицу всякий раз при показе нового изображения. В массив входят 30 изображений (пронумерованных от 0 до 29); когда программа достига- ет изображения под номером 29, она возвращается к изображению под номером 0. 30 изображений были подго- товлены заранее и размещены в папке images в каталоге bin\Debug проекта. Листинг 13.11. Анимация серии изображений , Z..,. 7W 1 1 // Листинг 13.11: LogoAnimator.es 2 // Программа, анимирующая серию изображений 3 4 using System; 5 using System.Drawing; 6 using System.Collections; 7 using System.ComponentModel; 8 using System.Windows.Forms; 9 using System.Data; 10 11 // анимация серии из 30 изображений 12 public class LogoAnimator : System.Windows.Forms.Form 13 { 14 private System.Windows.Forms.PictureBox logoPictureBox; 15 private System.Windows.Forms.Timer Timer; 16 private System.ComponentModel.Icontainer components; 17 18 private ArrayList images = new ArrayList (J ; 19 private int count = -1; 20 21 public LogoAnimator() 22 { 23 InitializeComponent(); 24 25 for (int i = 0; i < 30; i++) 26 images.Add(Image.FromFile ("images/deitel" + i + 27 ’’.gif’)); 28 29 // загрузка первого изображения 30 logoPictureBox.Image = (Image) images[0]; 31 32 11 установка размера PictureBox одинакового c Image 33 logoPictureBox.Size = logoPictureBox.Image.Size; 34 35 } // окончание конструктора 36 37 [STAThread] 38 static void Mainf) 39 { 40 . Application.Run(new LogoAnimator()); 41 } 42
Графика и мультимедиа 439 43 // код, сгенерированный Visual Studio .NET 44 45 private void Timer_Tick( 46 object sender, System.EventArgs e) 47 { 48 // приращение счетчика 49 count = (count +1) % 30; 50 51 // загрузка следующего изображения 52 logoPictureBox. Image = (Image) .image[count]; 53 54 ) // окончание метода Timer_Tick 55 56 } // окончание класса LogoAnimator В строках 25—27 загружается по 30 изображений, которые помещаются в ArrayList. Метод Add класса ArrayList позволяет добавлять объекты в ArrayList (строки 26 и 27). В строке 30 первое изображение помеща- ется в PictureBox с помощью индексатора ArrayList. В строке 33 размер PictureBox изменяется так, что он становится равным размеру выводимого изображения. После этого обработчик события Tick класса timer (строки 45—54) показывает следующее изображение из ArrayList. Результат работы программы представлен на рис. 13.16. а б в Рис. 13.16. Анимация серии изображений: а — первый кадр; б — второй кадр; в — третий кадр (нумерация кадров условная) Совет по повышению производительности_______________________________________________________ Гораздо эффективнее загружать кадры (фреймы) анимации как единое изображение, нежели раздельно. (Для объединения кадров анимации в единое изображение можно воспользоваться программами работы с графикой: Adobe Photoshop, Jasc Paint Shop Pro.) Если изображения загружаются из Web раздельно, тогда каждое загру- жаемое изображение потребует отдельного выхода на сайт, где оно сохранено; это плохо сказывается на произ- водительности. Загрузка кадров анимации может замедлить быстродействие программы, поскольку перед отображением требу- ется время на загрузку Пример, приведенный далее, с шахматами демонстрирует возможности GDI+ в том, что касается разработки шахматных программ. Сюда входят методики двумерного обнаружения конфликтов, выбора отдельных кад- ров из многокадрового изображения и частичного воспроизведения (обновления только нужных участков экра- на) для повышения производительности. Двумерное обнаружение конфликтов — это фиксирование наложения двух фигур. Рассмотрим простейшую форму обнаружения конфликтов, определяющую выполнение щелчка кнопкой мыши, когда указатель мыши установлен в пределах прямоугольника (изображения шахматной фи- гуры). Класс Chesspiece (листинг 13.12) является классом-контейнером для отдельных шахматных фигур. Строки 11— 19 определяют общедоступное перечисление констант, идентифицирующих тип каждой шахматной фигуры. Эти константы также служат для определения местоположения каждой фигуры в файле изображений шахмат- ных фигур. Объект targetRectangle класса Rectangle (строки 25 и 26) идентифицирует место изображения на шахматной доске. Свойства х и у прямоугольника присвоены в конструкторе chesspiece, и все изображения шахматных фигур имеют высоту и ширину 75. Конструктор ChessPiece (строки 29—40) требует, чтобы вызывающий класс определил тип шахматной фигуры, ее координаты х и у и объект Bitmap, содержащий все изображения шахматных фигур. Вместо загрузки изобра- жения шахматной фигуры в рамках класса, вызывающему классу предоставляется возможность передачи изо- бражения При этом устраняются непроизводительные издержки на загрузку изображения каждой фигуры. Так- же повышается гибкость класса, потому что пользователь может менять изображения; например, в данном слу-
440 Глава 13 чае класс используется как для белых, так и для черных фигур. Строки 37—39 выделяют часть изображения, содержащую только растровые данные текущей фигуры. Шахматные фигуры определены специфически: одно изображение содержит шесть фигур, каждая из которых определена в рамках 75-пиксельного блока, в результа- те чего общий размер изображения составляет 450x75 пикселов. Отдельное изображение получаем с помощью метода clone класса Bitmap, позволяющего задавать местоположение прямоугольного изображения, а также нужный пиксельный формат. Местоположение — блок пикселов 75x75 с координатой х верхнего левого угла, равной 75*type, и соответствующей координатой — 0. Для пиксельного формата задается константа Dontcare, при этом формат остается неизменным. Метод Draw (строки 43—46) заставляет chessPiece переместить piecelmage в объекте targetRectangle на пере- данный объект Graphics. Метод GetBounds возвращает объект targetRectangle для использования при обнару- жении столкновений, a SetLocation позволяет вызывающему классу задавать новое местоположение шахмат- ной фигуры. 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 // Листинг 13.12: ChessPiece.cs // Класс хранения для атрибутов шахматных фигур using System; using System.Drawing; // представление шахматной фигуры public class ChessPiece { // определение констант типа шахматной фигуры public enum Types { KING, QUEEN, BISHOP, KNIGHT, ROOK, PAWN } private int currentType; // тип данного объекта private Bitmap pieceimage; // изображение данного объекта // отображения местоположения по умолчанию private Rectangle targetRectangle = new Rectangle(0, 0, 75, 75); // построение фигуры public ChessPiece(int type, int xLocation, int yLocation, Bitmap sourceImage) { currentType = type; // установка текущего типа targetRectangle.X = xLocation; // установка текущей коорд. x targetRectangle.Y = yLocation; // установка текущей коорд. у // получение pieceimage из раздела sourceimage pieceimage = sourceimage.Clone( new Rectangle(type * 75, 0, 75, 75), System.Drawing.Imaging.PixelFormat.DontCare); } // изображение шахматной фигуры public void Draw(Graphics graphicsObjecr) { graphicsObject.Drawlmage(pieceimage, targetRectangle); }
Графика и мультимедиа 441 48 // получение прямоугольника местоположения данной фигуры 49 public Rectangle GetBoundsQ 50 { 51 return'targetRectangle; 52 } // окончание метода GetBounds 53 54 // задание местоположения данной фигуры 55 public void SetLocation(int xLocation, int yLocation) 56 { 57 targetRectangle.X = xLocation; 58 targetRectangle.Y = yLocation; 59 60 } // окончание метода’ SetLocation 61 62 } // окончание класса ChessPiece Класс ChessGame (листинг 13.13) определяет игру и графический код шахматной игры. В строках 23—33 опре- деляются необходимые программе переменные на уровне класса. Объект ArrayList chessTile (строка 23) хра- нит изображения клеток доски. Этот класс содержит четыре изображения: две белые и две черные клетки (для разнообразия доски). ArrayListchessPieces (строка 26) сохраняет все активные объекты ChessPiece, a int selectedlndex (строка 29) идентифицирует указатель в chesspieces текущей выбранной фигуры, board (стро- ка 30) — двумерный целочисленный массив 8x8, соответствующий клеткам доски. Каждый элемент доски — это целое число от 0 до 3, соответствующее указателю в chessTile и используемое для задания изображения клетки шахматной доски, const int tilesize (строка 33) определяет размер каждой клетки в пикселах. Шахматная игра GUI состоит из формы ChessGame — области, в которой рисуются клетки, PictureBoxpieceBox — окна, в котором рисуются фигуры (обратите внимание, что фоновый цвет pieceBox ус- тановлен на "transparent"), и Menu, из которого пользователь может начать новую игру. Несмотря на то, что фигуры и клетки можно изобразить на одной форме, это снизит производительность. Тогда придется обновлять доску и фигуры всякий раз при обновлении элемента управления. 1 // Листинг 13.13: ChessGame.cs 2 // Код графического отображения шахматной игры 3 4 using System; 5 using System.Drawing; 6 using System.Collections; 7 using System.ComponentModel; 8 using System.Windows.Forms; 9 using System.Data; 10 11 // Игра в шахматы для двух игроков 12 public class ChessGame : System.Windows.Forms.Form 13 { 14 private System.Windows.Forms.PictureBox pieceBox; 15 private System.Windows.Forms.MainMenu GameMenu; 16 private System.ComponentModel.Menuitem gameitem; 17 private System.ComponentModel.Menuitem NewGameltem; 18 19 private 20 System.ComponentModel.Container components = null; 21 22 // ArrayList для изображений клеток доски . 23 ArrayList chessTile = new ArrayList(); 24 25 11 ArrayList для шахматных фигур 26 ArrayList chessPieces = new ArrayList(); 27 28 // определение указателя для выбранной фигуры 29 int selectedlndex = -1; 30 int[,] board = new int[8, 8]; // массив доски 31
442 Глава 13 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 // определение размера шахматной клетки в пикселах private const int TILESIZE = 75; [STAThread] static void MainO { Application.Run(new ChessGame()); } // код, сгенерированный Visual Studio .NET // загрузка растровых изображений клеток и перезагрузка игры private void ChessGame_Load( object sender, System.EventArgs e) { // загрузка клеток доски chessTileAdd(Bitmap.FromFile("lightTilel.png")); chessTileAdd(Bitmap.FromFile("lightTile2.png")); chessTileAdd(Bitmap.FromFile("darkTilel.png")); chessTileAdd(Bitmap.FromFile("darkTilel.png")); ResetBoard(); // инициализация доски Invalidate(); // обновление формы } // окончание метода ChessGame_Load // инициализация фигур для начала и перестроения доски private void ResetBoard() { int current = -1; ChessPiece piece; Random random = new RandomO; bool light = true; int type; // создание пустого arrayList chessPieces = new ArrayList(); // загрузка изображений белых фигур Bitmap whitePieces = (Bitmap)Image.FromFile("whitepieces.png); // загрузка изображений черных фигур Bitmap blackPieces = (Bitmap)Image.FromFile("blackPieces.png); // набор белых фигур, изображенный первым Bitmap selected ='whitePieces; // прохождение рядов доски во внешнем цикле for (int row = 0; row <= board.GetUpperBoundO ; row++) { // если нижний ряд, установить на изображения черных фигур if (row > 5) selected = blackPieces; // прохождение столбцов доски во внутреннем цикле for (int column =0; • column <=board.GetUpperBound(1); column++) { // если первый или последний ряд, расставить фигуры if (row ~0!l row «= 7)
Графика и мультимедиа 443 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 { switch(column) { case 0: case 7: // задать фигуру ладьи current = (int)ChessPiece.Types.ROOK; break case 1: case 6: Il задать фигуру коня current = (int)ChessPiece.Types.KNIGHT; break; case 2: case 5: // задать фигуру слона current = (int)ChessPiece.Types.BISHOP; break; case 3; // задать фигуру короля current = (int)ChessPiece.Types.KING; break; case 4: // задать фигуру ферзя current = (int) ChessPiece.Types.QUEEN; break; } // создать текущую фигуру в начальной позиции piece = new ChessPiece(current, column * TILESIZE, row * TILESIZE,, selected); // добавить фигуру в arrayList chessPieces.Add(piece); } 11 если второй или седьмой ряд, расставить пешки if (row — 1 || row = 6) { piece = new ChessPiece( (int)ChessPiece.Types.PAWN, column * TILESIZE, row * TILESIZE, selected); 11 добавить фигуру в arrayList chessPiece.Add(piece); } // определение типа фигуры доски type = random.NEXT(0, 2); if (light) { // установка б'елой клетки board[row, column] = type; light = false;
444 Глава 13 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 else { // установка черной клетки board[row, column] = type + 2; light = true; } } // переход к новому цвету клетки в ряду light = !light; } } // окончание метода ResetBoard // отображение доски в форме через событие OnPaint private void ChessGame_Paint( object sender, System.Windows.Forms.PaintEventArgs e) { // получение графического объекта Graphics graphicsObject = e.Graphics; for (int row = 0; row <= board.GetUpperBound(O); row++) { for (int column = 0; column <= board.GetUpperBound(l); column++) { // нарисовать изображение, указанное в массиве доски graphicsObject.Drawlmage( (Image)chessTile[board[row, column]], new Point(TILESIZE * column, TILESIZE * row)); } } } // окончание метода ChessGame_Paint // возвращение индекса клетки, в которой произошел // щелчок мышью private int CheckBounds(Point point, int exclude) { Rectangle rectangle; // текущий ограничивающий прямоугольник for (int I «-0; I < chessPieces.Count; i++) { // получение прямоугольника фигуры rectangle = Get Piece (i) .GetBoundsO ; // проверить, содержит ли прямоугольник точку if (rectangle.Contains(point) && i !e exclude) return i; } return -1; } // окончание метода CheckBounds // обработка события pieceBox_Paint private void pieceBox_Paint( object sender, System.Windows.Forms.PaintEventArgs e) { // изображение всех фигур for (int i = 0; i < chessPieces.Count; i++) GetPiece(i).Draw(e.Graphics); } // окончание метода pieceBox_Paint
Графика и мультимедиа 445 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 . 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 private void pieceBox_MouseDown( object sender, System.Windows.Forms.MouseEventArgs e) { // определение выбранной фигуры selectedlndex = CheckBounds(new Point(e.X, e.Y), -1); } // окончание метода pieceBox_MouseDown // если фигура выбрана, передвинуть ее private void pieceBox_MouseMove( object sender, System.Windows.Forms.MouseEventArgs y) { if (selectedlndex > -1) { Rectangle region = new Rectangle( e.X - TILESIZE * 2, e.Y - TILESIZE * 2, TILESIZE * 4, TILESIZE * 4); // установить центр фигуры в точку щелчка GetPiece(selectedlndex).SetLocation( e.X - TILESIZE / 2, e.Y - TILESIZE / 2); // обновление ближайшей области. pieceBox.Invalidate(region); } } // окончание метода pieceBox_MouseMove _ 11 отменить выделение фигуры и удалить "взятую" фигуру private void pieceBox_MouseUp( object sender, System.Windows.Forms.MouseEventArgs e) { int remove = -1; // если выбрана фигура if (selectedlndex > -1) { Point current * new Point(e.X, e.Y); Point newPoint = new Point( current.X - (current.X % TILESIZE), current.Y - (current.Y % TILESIZE)); // проверка границ с точкой, исключение выделенной фигуры remove = CheckBounds(current, selectedlndex); // перемещение фигуры в центр ближайшей клетки GetPiece(selectedlndex).SetLocation(newPoint.X, newPoint.Y); t // отмена выделения фигуры selectIndex = -1; 11 удаление "взятой" фигуры if (remove > -1) chessPieces.RemoveAt(remove); } // обновление pieceBox pieceBox.Invalidate(); } // окончание метода pieceBox_MouseUp
446 Глава 13 287 // функция справки для преобразования 288 // объекта ArrayList в ChessPiece 289 private ChessPiece GetPiece(int i) 290 { 291 return (ChessPiece)ChessPieces [i]; 292 } // окончание метода GetPiece 293 294 // обновление NewGame при выборе соответствующего пункта меню 295 private void newGameItem_Click( 296 object sender, System.EventArgs y) 297 { 298 ResetBoard(); // повторная инициализация шахматной доски 299 Invalidate(); // обновление формы 300 301 } // окончание метода newGameItem_Click 302 303 } // окончание класса ChessGame Событие ChessGameLoad (строки 44—56) загружает каждое изображение клетки в chessTile. После этого вызы- вается метод ResetBoard для обновления формы и начала игры. Метод ResetBoard (строки 59—169) присваива- ет chesspieces новому ArrayList с загрузкой изображений для наборов белых и черных фигур и создает Bitmap selected для определения текущего выбранного набора Bitmap. Строки 82—167 повторяют цикл по 64-м пози- циям на шахматной доске с заданием цвета клетки и фигуры для каждой клетки. В строках 86 и 87 текущее вы- бранное изображение переключается на blackpieces после пятого ряда. Если счетчик рядов находится на пер- вом или последнем ряду, то строками 94—143 добавляется новая фигура в chesspieces. Тип фигур основан на текущем инициализируемом столбце. Фигуры в шахматах располагаются в следующем порядке слева направо: ладья, конь, слон, ферзь, король, слон, конь и ладья. В строках 137—146 добавляется новая пешка в текущее местоположение, если текущий ряд — второй или седьмой. Шахматная доска определяется чередованием белых и черных клеток в ряду в последовательности, когда цвет клетки, начинающей каждый ряд, тот же, что и цвет последней клетки предыдущего ряда. В строках 151—162 присваивается цвет текущей клетки доски как указатель в массиве board. Исходя из чередующегося значения булевой (bool) переменной light и результата операции получения случайного числа в строке 149, о и 1 — бе- лые клетки, а 2 и 3 — черные. В строке 166 значение light инвертируется в конце каждой строки для поддер- жания "шашечного" рисунка доски. Метод chessGame_Paint (строки 172—192) обрабатывает событие Paint формы этого класса и изображает клет- ки шахматной доски в соответствии с их значениями в массиве доски. Метод pieceBox_Paint, обрабатывающий событие pieceBoxPictureBox paint, итерируется по каждому элементу chessPieceArrayList и вызывает его метод Draw. Обработчик событий MouseDown (строки 224—231) вызывает метод checkBounds с координатами точки, в кото- рой произошел щелчок кнопкой мыши, для определения, выбрал ли играющий фигуру. CheckBounds возвращает целое число, фиксирующее конфликт от заданной точки. Обработчик событий MouseMove (строки 234—251) перемещает Выделенную фигуру с помощью мыши. В стро- ках 244 и 245 устанавливается местоположение выделенной фигуры в точку щелчка мышью. В строках 239— 241 определяется и обновляется область PictureBox, охватывающая две клетки в любом направлении от курсо- ра мыши. Как упоминалось выше, метод invalidate срабатывает достаточно медленно. Это означает, что обра- ботчик событий MouseMove можно вызвать еще несколько раз до тех пор, пока не завершится исполнение метода invalidate. Если пользователь, работающий на компьютере с пониженным быстродействием, быстро переме- щает курсор мыши, то программа может создавать так называемые артефакты. Артефактом называется любой непреднамеренный визуальный эффект в графической программе. Если добиться того, чтобы программа обнов- ляла прямоугольник, состоящий из двух клеток (в большинстве случаев этого достаточно), то результатом будет заметное повышение производительности при обновлении всего компонента во время каждого события MouseMove. В строках 254—285 определяется обработчик события Mouseup. Если выбрана шахматная фигура, строки 160— 180 определяют указатель в chesspieces любой конфликт фигур, удаляют фигуру, с которой имел место кон- фликт, перемещают (выравнивают) выбранную фигуру в нужное местоположение и отменяют ее выделение. Проверка конфликтов фигур выполняется для того, чтобы шахматная фигура могла "брать" другие фигуры. В строке 268 проверяется, находится ли какая-либо фигура (за исключением выделенной) под текущим распо- ложением курсора мыши. При обнаружении конфликта возвращенный указатель фигуры присваивается int remove. В строках 271 и 272 определяется ближайшая подходящая клетка доски, и выделенная фигура "переме- щается" на эту клетку. Если remove содержит положительное значение, тогда в строке 279 удаляется объект в этом указателе из chesspieces ArrayList. Наконец, в строке 283 весь объект PictureBox обновляется
Графика и мультимедиа 447 (invalidate) для отображения нового местоположения фигуры и удаления любых артефактов, имевших место во время выполнения хода. CheckBounds (строки 196—212)— метод, способствующий обнаружению конфликтов. Он итерируется через chessPiecesArrayList и возвращает указатель любого прямоугольника фигуры, содержащего значение точки, переданное в метод (в данном примере местоположение курсора мыши). Дополнительно метод CheckBounds может исключить указатель отдельной фигуры (в данном примере для игнорирования выбранного указателя в обработчике события Mouseup). В строках 289—292 определяется вспомогательная функция Get Piece, упрощающая преобразование из объек- тов в ArrayList chessPieces В ТИПЫ ChessPiece. Метод newGameItem_Click обрабатывает событие щелчка кноп- кой мыши в меню NewGame, обращается к RefreshBoard для сброса игры и обновляет (invalidate) всю форму. Результат работы программы представлен на рис. 13.17. Рис. 13.17. Игра в шахматы: а — вид шахматной доски в начале игры; б — вид шахматной доски после нескольких ходов; в — выбор команды меню для начала новой игры
448 Глава 13 13.12. Windows Media Player Элемент управления Windows Media Player позволяет программе воспроизводить видео- и аудиоинформацию во многих мультимедийных форматах. В их число входит аудио и видео MPEG (Motion Pictures Experts Group, экс- пертная группа по вопросам движущихся изображений), AVI-видео (Audio-Video Interleave, стандартный фор- мат файла, содержащего перемежающиеся аудио- и видеоданные), WAV-аудио (Windows Wave-File, формат волновых файлов Windows) и MIDI-аудио (Musical Instrument Digital Interface, цифровой интерфейс музыкаль- ных инструментов). Пользователи могут скачивать готовые аудио- и видеоматериалы из Интернета, либо созда- вать свои файлы с помощью доступных программных пакетов обработки звуковой и графической информации. Приложение из листинга 13.14 демонстрирует элемент управления Windows Media Player, обеспечивающий воспроизведение мультимедийных файлов. Для использования Windows Media Player программист должен до- бавить этот элемент управления в Toolbox. Для этого следует выбрать в меню Tools команду Customize Tool- box для открытия одноименного диалогового окна. В этом окне в списке элементов нужно отметить Windows .Media Player, а затем нажать кнопку ОК для закрытия диалогового окна. В нижней части Toolbox появится пиктограмма элемента управления Windows Media Player. г................................................................................................ ............... ..................................................................................................................................................................... • Листинг 13.14. Демонстрация Windows Media Player t k. — —.... .— ..— 1....г..МЬ.ЖХХАа.к>к.^ССмкЧк>..Ч.'.. Дл 1 // Листинг 13.14: MediaPlayerTest.cs 2 // Демонстрация элемента управления Windows Media Player 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 , 33 34 35 36 37 38 39 40 41 42 43 using System; using System.Drawing; using System.Collections; using System.ComponentModel; using System.Windows. Forms; using System.Data; // позволяет воспроизводить мультимедийные файлы с помощью // элемецта управления Media Player public class MediaPlayer : System.Windows.Forms.Form { private System.Windows.Forms.MainMenu applicationMenu; private System.Windows.Forms.Menuitem fileitem; private System.Windows.Forms.Menuitem openitem; private System .Windows.Forms.Menuitem exitItern; private System.Windows.Forms.Menuitem aboutltem; private System.Windows.Forms.Menuitem aboutmessageltem; private System.Windows.Forms.OpenFileDialog openMediaFileDialog; private AxMediaPlayer.AxMediaPlayer player; private System.ComponentModel.Container components = null; [STAThread] static void MainO { Application.Run(new MediaPlayer()); } // код, сгенерированный Visual Studio .NET // открытие нового мультимедийного файла в Windows Media Player private void openitem_Click( object sender, System.EventArgs e) { openMediaFileDialog.ShowDialog(); player.FileName = openMediaFileDialog.Filename;
Графика и мультимедиа 44g 44' // настройка размеров элемента управления 45 // Media Player в соответствии с размером изображения 46 player.Size = new Size(player.ImageSourceWidth, 47 player.Imagesourceheight); 48 49 this.Size = new Size(player.Size.Width + 20, 50 player.Size.Height + 60); 51 52 ) // окончание метода openItem_Click 53 54 private void exitItem_Click( 55 object sender. System.EventArgs e) 56 { 57 Application.Exit(); 58 59 } // окончание метода exitItem Click 60 61 private void aboutMessageItem_Click( 62 object sender, System.EventArgs e) 63 { 64 player.AboutBox(); 65 66 ) // окончание метода aboutMessageItem_Click 67 68 } // окончание класса MediaPlayer Элемент управления Windows Media Player (рис. 13.18) предоставляет несколько кнопок, с помощью которых пользователь может воспроизводить текущий файл, устанавливать паузу, выполнять полную остановку, зада- вать воспроизведение предыдущего файла, осуществлять перемотку вперед и назад, а также воспроизводить следующий файл. Сюда также входит регулирование громкости и панель треков для выбора нужного места мультимедийного файла. а б Рис. 13.18. Окно программы Windows Media Player: а — выбор команды для открытия файла; б — проигрывание файла в окне программы Программное приложение имеет главное меню с пунктами File и About. Меню File содержит команды Open и Exit; меню About содержит команду About Windows Media Player. При выборе пользователем команды Open в меню File выполняется обработчик события openitem_ciick (стро- ки 37—52). Открывается диалоговое окно OpenrlleDialog (строка 40), где пользователь может выбрать нужный файл (рис. 13.19). После этого программа задает свойство FileName проигрывателя (объект элемента управления Windows Medial Player типа AxMediaPlayer) имени выбранного файла. Свойство FileName задает файл, исполь- зуемый в текущий момент элементом Windows Media Player. В строках 46—50 настраивается размер player и приложение для отображения содержащейся в файле среды. Обработчик события, выполняющийся при выборе пользователем команды Exit в меню File (строки 54—59) просто вызывает Application.Exit для остановки выполнения приложения. Обработчик события, выполняю- 29 Зак. 3333
450 Глава 13 щийся при выборе пользователем команды About Windows Media Player в меню About (строки 61—66), вызы- вает метод AboutBox проигрывателя. AboutBox отображает предварительно заданное окно сообщений, содержа- щее информацию о Windows Media Player (рис. 13.20). Рис. 13.19. Диалоговое окно для загрузки файла Рис. 13.20. Диалоговое окно с сообщением о Windows Media Player 13.13. Microsoft Agent Microsoft Agent — это технология, применяемая для добавления интерактивных анимированных персонажей в приложения Windows или Web-страницы. Интерактивность — ключевая функция технологии Microsoft Agent: персонажи Microsoft Agent могут "разговаривать" вводимыми пользователем данными и "отвечать" на вопросы через механизмы распознавания речи и синтез. Microsoft применяет технологию Agent в таких программах, как Word, Excell и PowerPoint. Агенты этих программ помогают пользователям находить ответы на различные во- просы, а также разбираться в вопросах функционирования приложений. Элемент управления Microsoft Agent обеспечивает программистов доступом к четырем заранее заданным сим- волам: Джини (джин), Мерлин (волшебник), Пиди (попугай) и Робби (робот). Каждый персонаж имеет свой уникальный набор анимаций, которые программисты могут вводить в приложения для иллюстрации разных аспектов и функций. Например, набор анимаций персонажа Пиди включает в себя различные "летающие" ани- мации, которые можно использовать для перемещения Пиди на экране. Базовая информация о технологии Agent представлена на Web-сайте Microsoft www.microsoft.com/msagent. Технология Microsoft Agent обеспечивает пользователей возможностью взаимодействия с программными при- ложениями и Web-страницами с помощью речи— самой естественной формы человеческого общения. При произнесении пользователем слов в микрофон элемент управления активизирует механизм распознавания ре- чи — программное приложение, переводящее входящий звуковой сигнал с микрофона в понятный компьютеру язык. В элементе управления Microsoft Agent также применяется механизм перевода текста в речь, генери- рующий озвученные ответы персонажа. Механизм перевода текста в речь — это программа, переводящая напе- чатанные слова в звуки, которые пользователь слышит в наушниках или в акустических системах, подключен- ных к компьютеру. На корпоративном Web-сайте Microsoft представлены механизмы распознавания речи и пе- ревода текста в звуки для нескольких языков: www.microsoft.com/products/msagent/downloads.htm Разработчики могут создавать собственных анимированных персонажей с помощью Microsoft Agent Character Editor (Редактор персонажей Microsoft Agent) и Microsoft Linguistic Sound Editing Tool (Редактор звучания языка Microsoft). Эти продукты доступны бесплатно по электронному адресу: www.microsoft.com/products/msagent/devdownloads.htm В данном разделе представлены основные возможности элемента управления Microsoft Agent. Подробности по загрузке представлены по следующему электронному адресу: www.microsoft.com/products/msagent/downloads.htm Приведенный ниже пример — Peedy's Pizza Palace (Пиццерия "У Пиди") — разработан корпорацией Microsoft для иллюстрирования возможностей элемента управления Microsoft Agent. Peedy's Pizza Palace — это интерак- тивная пиццерия, где пользователи могут размещать заказы с помощью голосовых команд. Персонаж Пиди об- щается с пользователями, помогая им выбрать начинку пиццы с последующим подсчетом общей суммы заказа.
Графика и мультимедиа 451 С примером можно ознакомиться по электронному адресу: agent.microsoft.com/agent2/sdk/samples/htinl/peedypza.htin Для запуска примера необходимо загрузить файл персонажа Пиди, механизм перевода текста в речь и механизм распознавания речи. При загрузке страницы браузер выдает подсказки по процессу скачивания. Для завершения установки выполняйте инструкции Microsoft. После открытия окна Пиди представляется (рис. 13.21), и произносимые им слова отображаются в ’.'мультяш- ном" виде над его головой. Обратите внимание на то, что анимация Пиди соответствует произносимым им словам. Программисты могут синхронизировать анимацию персонажа с выходом речи для наглядного представления того или иного аспекта, либо для передачи настроения персонажа. Например, на рис. 13.22 представлена анима- ция довольного Пиди. В набор анимаций попугая входят 85 различных анимаций, каждая из которых относится только к данному персонажу. * Здесь отображается текстовый эквивалент речи Пиди Рис. 13.21. Представление Пиди при открытии окна Рис. 13.22. Анимация довольного Пиди Замечание о "впечатлениях и ощущениях"___________________________________________________ Персонажи-агенты остаются поверх всех активных окон во время работы программы Microsoft Agent. Их пере- мещения не ограничиваются окнами браузера или программы.
452 Глава 13 Пиди также реагирует на ввод с клавиатуры и щелчки мышью. На рис. 12.23 показано, что происходит, когда пользователь щелкает по изображению Пиди. Пиди подпрыгивает, взъерошивается и восклицает: "Неу that tick- les" ("Эй, кто меня щекочет?") или "Be careful with that pointer" ("Поаккуратнее с этим указателем"). Пользова- тель может переместить Пиди на экране щелчком и перетаскиванием его мышью. Однако когда пользователь передвигает персонаж в различные части экрана, Пиди продолжает выполнять анимированные действия. Щелчок кнопкой мыши на Пиди Рис. 13.23. Реакция Пиди при щелчке на нем кнопкой мыши Создание анимации включает в себя постоянное изменение местоположения персонажа. Например, Пиди может перепрыгивать из одного места экрана в другое или летать (рис. 13.24). Рис. 13.24. Анимация летающего Пиди После того, как Пиди закончит ввод команд заказа, под ним появляется текстовая область с указанием на то, что он готов выслушать голосовую команду (рис. 13.25). Пользователь может заказать сорт пиццы, либо произнести ее название в микрофон, либо отметить соответствующий переключатель. Если пользователь выбирает "голосовой" заказ, под изображением Пиди появляется область, в которой отобра- жаются "услышанные" им слова (т. е. переведенные для программы механизмом распознавания речи). Распо- знав сказанное пользователем, Пиди предоставляет ему описание выбранной пиццы. На рис. 13.26 показано, что происходит, когда пользователь выбирает сорт пиццы "Seattle". После этого Пиди просит выбрать дополнительную начинку (или соус). Пользователь может произнести назва- ние вслух, либо воспользоваться мышью, чтобы сделать свой выбор. Для этого есть флажки, соответствующие соусами, подаваемыми с выбранным сортом пиццы. На рис. 13.27 показано, что происходит, когда пользователь выбирает анчоусы. Пиди остроумно комментирует выбор пользователя.
Графика и мультимедиа 453 Всплывающая подсказка сигнализирует, что Пиди ожидает ввода т пользователя Переключатели для выбора сорта пиццы Рис. 13.25. Пиди, ожидающий ввода голосовой команды Всплывающая подсказка дублирует голосовое сообщение Пиди Рис. 13.26. Пиди, повторяющий заказ пиццы "Seattle" Рис. 13.27. Пиди, повторяющий заказ пользователя на анчоусы в качестве дополнительной начинки
454 Глава 13 Пользователь может разместить заказ либо нажатием кнопки Place Му Order, либо произнесением в микрофон фразы "Place Oder” ("Принять заказ"). Пиди подсчитывает сумму заказа с записью наименований в блокнот (рис. 13.28), После этого он подсчитывает сумму на калькуляторе и сообщает пользователю общую сумму (рис. 13.29). Рис. 13.28. Пиди, пересчитывающий сумму заказа Рис. 13.29. Пиди, подсчитывающий общую сумму заказа Следующий пример (листинг 13.15) демонстрирует создание простого программного приложения с помощью элемента управления Microsoft Agent. Это приложение содержит два раскрывающихся списка, в которых поль- зователь может выбрать персонажа Agent и его анимацию. При выборе в этих списках появляется выбранный персонаж и выполняет анимацию и воспроизводит речь: пользователи могут задавать анимацию персонажа нажатием клавиши <Scroll Lock> (запрет прокрутки) с последующим произнесением названия анимаций в мик- рофон. Данный пример дает пользователю возможность переключения на нового персонажа путем произнесения его имени, а также создает специальную команду — MoveToMouse. Кроме этого, персонажи могут произносить лю- бой текст, который пользователь вводит в текстовое окно. Перед запуском примера читателям рекомендуется загрузить и установить с сайта Microsoft Agent упоминавшиеся ранее элемент управления, механизм распозна- вания речи, преобразования текста в речь и определения персонажей. Для использования Microsoft Agent программист сначала должен добавить его в панель инструментов (Toolbox). Для этого выберите элемент Customize Toolbox из меню Tools для открытия окна Customize Toolbox. В этом диалоговом окне воспользуйтесь полосой прокрутки и выберите опцию Microsoft Agent Control 2.0. При правильном выборе данной опции слева от нее появится флажок. После этого нажмите кноп-
Графика и мультимедиа 455 ку ОК для закрытия окна. Теперь пиктограмма Microsoft Agent должна появиться в нижней части панели инст- рументов. Помимо объекта mainAgent Microsoft Agent (тип AxAgent), управляющего всеми персонажами, также потребует- ся объект (типа lAgentctlCharacter) для представления текущего персонажа. Такой объект под именем speaker создается в строке 30. 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 // Листинг 13.15: Agent.cs » 11 Демонстрация Microsoft Agent using System; using System.Drawing; using System.Collections; using System.ComponentModel; using System, Windows. Forms ; using System.Data; using System.IO; public class Agent : System.Windows.Forms.Form { // опции private System.Windows.Forms.ComboBox actionsCombo; private System.Windows.Forms.ComboBox characterCombo; private System.Windows.Forms.Button speakButton; private System.Windows.Forms.GroupBox- characterGroup; private AxAgentObjects.AxAgent mainAgent; // входные данные private System.Windows.Forms.TextBox speechTextBox; private System.Windows.Forms.TextBox locationTextBox; private System.ComponentModel.Container components = null; // объект текущего агента private AgentObjectS.iAgentCtlCharacter speaker; [STAThread] static void Maxn() { Application.Run(new Agent{)); } // код, сгенерированный Visual Studio .NET // обработчик события KeyDown для locationTextBox private void locationTextBox_KeyDown( object sender, System.Windows.Forms.KeyEventArgs e) { if (e.KeyCode — Keys.Enter) { // задать положение персонажа значением в текстовом поле string location = locationTextBox.Text; 11 инициализация персонажей try { // загрузка персонажей в объект агента mainAgent.Characters.Load("Genie", location + "Genie.acs");
456 Глава 13 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 32 83 84 85 86 87 88 89 90 ‘ 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 mainAgent.Characters.Load("Merlin", location + "Merlin.acs"); mainAgent.Characters.Load("Peedy", location + "Peedy.acs"); mainAgent.Characters.Load("Robby", location + "Robby.acs"); 11 отключение TextBox для ввода местоположения //и активизация других элементов управления locationTextBox.Enabled = false; speechTextBox.Enabled » true; speakButton.Enabled = true; characterCombo.Enabled = true; actionCombo.Enabled = true; 11 установка текущего персонажа Джини и его отображение speaker = mainAgent.Characters [”Genie’r] ; 11 получение списка названия анимаций •GetAnimationNames () ; speaker.Show(0); } catch(FileNotFoundException) { MessageBox.Show("Invalid character location", "Error", MessageBoxButton.OK, MessageBoxIcon.Error); ) ) } 11 окончание метода locationTextBox_KeyDown private void speakerButton_Click( object sender, System.EventArgs ey { // если текстовое поле пустое, заставить персонаж // попросить пользователя ввести слова; в противном случае, // заставить персонаж произнести слова if (speechTextBox.text == " ") speaker.Speak( "Please, type the words you want me to speak", i« . else speaker.Speak(speechTextBox.Text, " "); } // окончание метода speakButton_Click // событие щелчка мыши для агента private void mainAgent_ClickEvent(object sender, AxAgentObjects._AgentEvents_ClickEvent e) { speaker.Play("Confused"); speaker.Speak("Why are you poking me?", " "); speaker.Play("RestPose”); } // окончание метода mainAgent_ClickEvent // событие combobox changed, переключение активного агента private void characterCombojSelectedlndexChanged( object sender, SystemEventArgs e)
Графика и мультимедиа 457 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 ’ 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 { Changecharacter(characterCombo.Text); } // окончание метода characterCombo_SelectedIndexChanged private void Changecharacter(string name) { speaker.Hide(0); speaker • mainAgent.Characters[name]; // повторное создание списка анимаций GetAnimationNames (); speaker.Show(0); } // окончание метода Changecharacter // получение названий анимаций и сохранение в arraylist private void GetAnimationNames() { // обеспечение защиты потока lock(this) { 11 получение названий анимации lEnumerator enumerator = mainAgent.Characters[ speaker .Name] .AnimationNames .GetEnumerator (); string voicestring; // сброс actionsCombo actionsCombo.Items.Clear(); speaker.Commands.RemoveAll(); // копирование перечисления в ArrayList while (enumerator.MoveNext()) { // удаление подчеркивания в строке речи voicestring « (string)enumerator.Current; voicestring « voicestring.Replace("_", "underscore"); actionsCombo.Items .Add(enumerator.Current); // добавление всех анимаций как голосовых команд speaker.Commands.Add( (string)enumerator.Current, enumerator.Current, voicestring, true, false); } i // добавление специальной команды speaker.Commands.Add( "MoveToMouse", "MoveToMouse", "MoveToMouse", true, true); } } // окончание метода GetAnimationNames // пользователь выбирает новое действие private void actionsCombo_SelectedIndexChanged( object sender. System.EventArgs e)
458 Глава 13 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 { speaker.StopAll("Play"); speaker.Play(actionsCombo.Text); speaker.Play("RestPose"); } // окончание метода actionsCombojSelectedlndexChanged // обработка команд агента private void mainAgent_Command(object sender, AxAgentObjects._AgentEvents_CommandEvent e) { // получение свойства userInput Agentobjects.LAgentCtlUserInput command = (AgentObjects.lAgentCtlUserlnput)e.userinput; // изменение персонажа при произнесении пользователем его имени if (command.Voice = "Peedy" || command.Voice == "Robby" || command.Voice — "Merlin" || command.Voice == "Genie") { Changecharacter(command.Voice); return; ) // отправка агента на млпь if (command.Voice === "MoveToMouse"’) -{ speaker.MoveTo( Convert.Toint16(Cursor.Position.X — 60), Convert.Tolntl6(Cursor.Position.Y — 60), 5); return; ) // проигрывание новой анимации speaker.StopAll("Play"); speaker.Play(command.Name); v } // окончание метода-mainAgent_Conmand ) // окончание класса Agent На рис. 13.30 представлен результат работы программы. При запуске программы единственным активизированным элементом управления является LocationTextBox. Это текстовое поле содержит путь к файлам с персонажами, но пользователь может изменить это значение, если файлы располагаются где-либо в другом месте на компьютере. При нажатии пользователем клавиши <Enter> в TextBox выполняется обработчик события locationTextBox KeyDown (строки 41—88). Строки 53—63 загружают описания персонажей для предопределенных анимированных персонажей. Если указанное местоположение персонажей некорректно, либо какой-либо персонаж отсутствует, выдается исключение FileNotFoundException. В строках 67—71 деактивизируется LocationTextBox и активизируются остальные элементы управления. В строках 74—78 устанавливается Джини как персонаж по умолчанию, генерируются все названия анимаций через метод GetAnimationNames, после чего вызывается метод Show класса lAgentctlCharacter для отображения персонажа. Доступ к персонажам осуществляется через свойство characters класса mainAgent, содержащее все загруженные персонажи. Для указания имени персонажа, который должен быть загружен (Джини), использует- ся указатель свойства Characters. Когда пользователь щелкает кнопкой мыши на персонаже (т. е. "толкает" его курсором мыши), выполняется обработчик события mainAgent_clickEvent (строки 106—113). Сначала метод Play класса speaker воспроизво- дит анимацию. Этот метод принимает в качестве аргумента строку, представляющую одну из предопределен- ных анимаций для персонажа (список анимаций для каждого персонажа имеется на Web-сайте Microsoft; для каждого персонажа существует свыше 70 анимаций). В приведенном примере аргумент для Play — "Confused" (озадаченный); эта анимация определена для всех четырех персонажей, каждый из которых выражает данную
Графика и мультимедиа 459 Раскрывающийся список, в котором пользователь может выбрать характер анимации Анимация Writing а б Всплывающая подсказка------ дублирует слова, которые произносятся в г Пиди повторяет слова, введенные _ пользователем. Речь Пиди можно услышать, воспользовавшись наушниками или колонками Рис. 13.30. Демонстрация использования Microsoft Agent: а — Джини, выполняющий анимацию Writing; б — Мерлин, отвечающий на поданную голосом команду анимации; в — Пиди, просящий ввести слова для его озвучивания; а — Пиди, повторяющий введенные пользователем слова; д — Робби, отвечающий на щелчок на нем кнопкой мыши; е — Робби и его контекстное меню
460 Глава 13 эмоцию по-своему. Затем персонаж произносит фразу: "Why are you poking me?" ("Зачем ты меня толкаешь?") посредством обращения к методу Speak. Наконец, воспроизводится анимация RestPose, возвращающая персо- наж в нейтральную позу "передышки". Список действующих команд для персонажа содержится в свойстве commands объекта IAgentCtlCharacter (в данном примере speaker — громкоговоритель). Команды для персонажа Agent (агент) можно просматривать в контекстном меню, появляющемся при щелчке пользователем правой кнопкой мыши на персонаже Agent (см. рис. 13.36). Метод Add свойства Commands добавляет в список команд новую команду. Метод Add принимает три аргумента string и два аргумента bool. Первый аргумент string задает название команды, используемое для ее идентификации программным способом. Второй аргумент string определяет название команды так, как оно отображено в контекстном меню. Третий аргумент string определяет входные голосовые данные, запус- кающие выполнение команды. Первый аргумент типа bool проверяет команду на предмет активности, а второй аргумент bool определяет отображение команды в контекстном меню. Команда запускается при выборе ее пользователем в контекстном меню или при голосовой подаче в микрофон. Логика команд обрабатывается в событии Command элемента управления AxAgent (в данном примере — mainAgent). Кроме того, Agent определяет несколько глобальных команд, имеющих предварительно заданные функции (например, произнесенйе имени персонажа заставляет его появиться на экране). Метод GetAnimationName (строки 135—137) определяет действующие команды для выбранного персонажа, ко- торые можно с ним использовать. Данный метод содержит блок lock для предотвращения ошибок, могущих возникнуть при быстрой смене персонажей. Метод принимает анимации выбранного персонажа в виде перечис- ления (строки 142—144), затем удаляет существующие элементы в ComboBox и свойстве Commands персонажа. В строках 153—167 выполняется цикл по всем элементам в перечислении названий анимаций. Для каждой ани- мации в строке 156 название анимации присваивается string voicestring. В строках 157 и 158 удаляются все символы подчеркивания (_) и заменяются на строку "underscore"; при этом строка изменяется так, что пользо- ватель может ее произнести и использовать в качестве активизатора команды. Метод Add (строки 163—166) свойства Commands добавляет новую команду текущему выбранному персонажу. Метод Add добавляет все ани- мации в качестве команд путем предоставления следующих аргументов: название анимации в качестве нового имени команды и заголовка и voicestring для строки голосовой активизации. Аргументы bool метода активи- зируют команду, но делают ее недоступной из контекстного меню. Таким образом, команду можно активизиро- вать только голосом. Строки 170—172 создают новую команду с именем MoveToMouse, которую можно видеть в контекстном меню. После обращения к методу GetAnimationNames пользователь может выбрать значение из actionsCombo ComboBox. Обработчик события actionsCombo.SelectedindexChanged останавливает воспроизведение любой текущей ани- мации, после чего отображает анимацию, выбранную пользователем в ComboBox. Пользователь также может ввести текст в TextBox и нажать кнопку Speak. При этом обработчик события speakButton Click (строки 90—103) вызывает метод Speak класса speaker; в качестве аргумента в speechTextBox передается текст. Если пользователь нажимает кнопку Speak без текста, то персонаж произносит фразу: "Please, type the words you want me to speak" ("Введите, пожалуйста, слова, которые вы хотите, чтобы я произнес"). В любой точке программы пользователь может выбрать из ComboBox отображение другого персонажа. При этом выполняется обработчик события SelectedindexChanged для charactercombo (строки 116—121). Обработчик события вызывает метод Changecharacter (строки 123—132) с текстом в charactercombo ComboBox в качестве аргумента. Метод Changecharacter вызывает метод Hide класса speaker (строка 125) для удаления из вида те- кущего выбранного персонажа. В строке 126 присваивается вновь выбранный персонаж в speaker, в строке 129 генерируются названия анимации и команды персонажа, а в строке 130 отображается персонаж обращением к методу show. Всякий раз при нажатии пользователем клавиши <Scroll Lock> и произнесении текста в микрофон, либо при выборе команды из контекстного меню вызывается обработчик события mainAgent_Command. Этот метод пере- дает аргумент типа AxAgentObjects._AgentEvents_CommandEvent, содержащий одно свойство— userinput. Свойство userinput возвращает Object, который можно преобразовать в тип Agentobjects.lAgentCtlUserlnput. Объект userinput присваивается объекту command класса lAgentCtlUserlnput, используемому для идентифика- ции команды и выполнения соответствующей операции. В строках 196—204 используется метод Changecharacter для изменения текущего персонажа Agent, если пользователь произносит его имя. Microsoft Agent всегда отображает персонаж при произнесении пользователем его имени; впрочем, при управлении сме- ной персонажа можно сделать так, что одновременно будет появляться только один персонаж Agent. В строках 207—213 персонаж перемещается в текущее местоположение курсора мыши, если пользователь активизирует команду MoveToMouse. Метод MoveTo агента принимает аргументы координат х и у и перемещает персонаж в за- данное положение экрана с применением соответствующей анимации движений. Для всех прочих команд в строке 217 в качестве анимации воспроизводится (Play) имя команды.
Графика и мультимедиа 461 В данной главе рассматривались различные графические возможности GDI+, включая инструментальные сред- ства: кисть и перо, а также некоторые мультимедийные возможности библиотеки классов .NET Framework. В следующей главе описываются процессы считывания, записи файлов последовательного и произвольного доступа, а также собственно доступ к ним. Также будут изучены несколько типов потоков, имеющихся в Visual Studio .NET. 13.14. Резюме Система координат идентифицирует любую точку на экране. Верхний левый угол компонента GUI имеет коор- динаты (0, 0). Пара координат состоит из координаты х (горизонтальная координата) и координаты у (верти- кальная координата). Единицы координат измеряются в пикселах. Пиксел — это наименьшая единица разреше- ния на мониторе. Графический контекст представляет область рисования на экране. Объект Graphics обеспечивает доступ к гра- фическому контексту элемента управления. Объекты Graphics содержат методы для рисования, манипуляций гарнитурами шрифтов, цветами и другими операциями, имеющими отношение к обработке графики. Класс Graphics предоставляет методы DrawLine, DrawRectangle, DrawEllipse, DrawArc, DrawLines, DrawPolygon и DrawPie, с помощью которых на экране можно изображать линии и контуры фигур. С помощью методов FillRectangle, FillEllipse, FillPolygon и FillPie пользователь может изображать закрашенные фигуры. Классы HatchBrush, LinearGradientBrush, PathGradientBrush и TextureBrush являются производными от клас- са Brush и представляют стили заливки фигур. Метод OnPaint, как правило, вызывается в ответ на событие, например, на открытие окна. Данный метод, в свою очередь, запускает событие Paint. Структура color определяет константы для манипуляций с цветами в программе С#. Свойства R, G и в структу- ры color возвращают целочисленные значения от 0 до 255, обозначающие количество, красного, зеленого и синего цвета, соответственно, имеющихся в color. Чем больше значение того или иного цвета, тем больше при- сутствует этого цвета. Все конструкторы класса Font принимают, как минимум, три аргумента— название гарнитуры шрифта, его размер и стиль. Название гарнитуры может быть названием любого шрифта, поддерживаемого системой. Стиль гарнитуры является членом перечисления Fontstyle. Класс FontMetrics определяет несколько методов для по- лучения метрики гарнитуры (например, высота строчного знака и высота нижнего выносного элемента по от- ношению к базовой линии строки). Класс FontFamily предоставляет информацию о такой метрике гарнитуры, как разрядка семейства шрифтов и высота. С помощью Visual Studio .NET и C# программисты могут создавать приложения, в которых применяются такие компоненты, как Windows Media Player и Microsoft Agent. Media Player позволяет создавать приложения с воз- можностью воспроизведения мультимедийных файлов. Microsoft Agent — это технология, позволяющая вклю- чать в программы анимированные изображения интерактивных персонажей.
ГЛАВА 14 (Ш Файлы и потоки Могу только предположить, что документ с грифом "Не регистрировать" зарегистрирован в деле "Не регистрировать". Сенатор Фрэнк Черч Заседание информационной сенаторской подкомиссии, 1975 г. Сознание... не рассматривает себя делимым на куски... "Река" или "поток" — метафоры, с помощью которых они лучше всего описываются Уильям Джеймс Все это время я читал только одну часть. Сэмюэл Голдвин I Темы данной главы: □ создание, считывание, запись и обновление файлов; □ иерархия потоковых классов в С#; □ использование классов File и Directory; □ использование классов Filestream и BinaryFormatter для считывания объектов из файлов и записи объектов в файлы; □ ознакомление с процессом обработки файлов с последовательной и произвольной выборкой. 14.1. Введение Переменные и массивы обеспечивают лишь временное хранение данных: они теряются, когда объект удаляется в "мусорную корзину", либо при остановке работы программы. В отличие от переменных и массивов файлы служат для долгосрочного хранения больших объемов данных даже после прекращения работы программы, их создавшей. Сохраненные i файлах данные часто называются долговременными. Компьютеры могут сохранять данные на вторичных носителях, например, на магнитных, оптических дисках или на магнитной ленте. В дан- ной главе описываются процессы создания, обновления и обработки файлов данных в программах С#. Рассмат- риваются как файлы с последовательной выборкой, так и файлы с произвольной выборкой, указывающие на типы программных приложений, для которых они лучше всего подходят. В главе поставлены две основные за- дачи: ознакомление с парадигмами обработки файлов с последовательной и произвольной выборкой и предос- тавление читателям сведений об обработке потоков, достаточных дл^ поддержки сетевых функций, описывае- мых в главе 19. Обработка файлов — это одна из наиболее важных функций языка программирования, потому что она позволя- ет языку поддерживать коммерческие приложения, в которых, как правило, приходится обрабатывать очень большие объемы долговременных данных. В данной главе рассматриваются мощные и расширенные возможно- сти языка C# по обработке файлов и потоков ввода/вывода. 14.2. Иерархия данных Разумеется, что все обрабатываемые компьютером элементы данных переводятся в комбинации нулей и еди- ниц. Это сделано из-за простоты и экономичности создания электронных устройств, предполагающих два ус- тойчивых состояния: 0 обозначает одно состояние, al — другое. Примечательно, что глобальные функции, ре- шаемые компьютерами, включают в себя только фундаментальные манипуляции с нулями и единицами. Наименьшие элементы данных, поддерживаемые компьютерами, называются битами (от англ binary digit — двоичный знак, предполагающий одно или два значения). Каждый элемент данных — бит — может иметь либо
Файлы и потоки 463 значение 0, либо 1. Компьютерная схематика выполняет различные простые манипуляции битами, например, определение значения бита, задание значения бита и обращение бита (из 1 в 0 или из 0 в 1). Программирование с помощью битовых данных на низком уровне достаточно громоздко. Более предпочтитель- но программирование с использованием данных в таких формах, как десятичные цифры (т. е. О, 1, 2, 3,4, 5, 6, 7, 8 и 9), буквы (т. е. от А до Z и от а до z) и специальные символы (т. е. $, @, %, &, *,'(,), -, +, ", / и многие другие). Цифры, буквы и специальные символы называются символами. Набор всех символов, используемых для написания программ и представления элементов данных на конкретном компьютере, называется набором символов данного компьютера. Из-за того что компьютеры могут обрабатывать только единицы и нули, каждый символ в наборе^символов компьютера представлен комбинацией единиц и нулей. Байты состоят из восьми битов (символы в C# являются символами стандарта Unicode, состоящими из 2 байтов). Разработчики создают программные приложения и элементы данных е символами; компьютеры манипулируют этими символами и преобразуют в комбинации битов. Точно так же, как символы состоят из битов, поля состоят из символов. Полем называется группа символов, передающая какое-либо значение. Например, поле, состоящее из прописных и строчных букв, может обозначать имя человека. Разные типы элементов данных, обрабатываемых компьютерами, образуют иерархию данных (рис. 14.1), где элементы данных становятся расширенными и сложными по структуре по мере перехода от битов к символам, полям и более крупным структурам данных. Файл Judy Green Judy Поле 01001010 Байт (ASCII-символ J) 1 Бит Рис. 14.1. Иерархия данных Обычно запись состоит из нескольких полей. Например, в системе начисления заработной платы запись для конкретного служащего может включать следующие поля: □ идентификационный номер служащего; □ имя; □ адрес; □ ставку почасовой оплаты; □ количество заявленных вычетов; □ доход за текущий год; □ сумму удержанных налогов. Итак, запись — это группа взаимосвязанных (по смыслу) полей. В предыдущем примере каждое поле относится к одному и тому же служащему. Файл же представляет собой группу взаимосвязанных записей'. Файл расчета 1 В более общем смысле, файл может содержать произвольные данные в произвольных форматах. В некоторых операционных системах файл рассматривается как всего лишь набор байтов. В такой операционной системе любая компоновка байтов в файл (например, организация данных в записи) организуется программистом.
464 Глава 14 зарплаты в компании, как правило, содержит одну запись на каждого служащего. Таким образом, файл расчета зарплаты в небольшой компании может содержать всего 22 записи, а в крупной — 100 000. Часто в компаниях создается много файлов, некоторые из которых содержат миллионы, миллиарды или даже триллионы битов информации. Для упрощения процесса получения записей из файла минимум одно поле в каждой записи выбирается в каче- стве уникального ключа записи. Ключ идентифицирует запись, как относящуюся к конкретному человеку или подразделению, и служит отличительным признаком этой записи. В описанной выше системе расчета зарплаты в качестве ключа записи обычно выбирается идентификационный номер служащего. Существует много способов организации записей в файл. Самый распространенный тип организации называет- ся последовательным файлом, где записи, как правило, сохраняются в порядке убывания или возрастания зна- чения ключа записи. В файле расчета зарплаты записи располагаются в порядке, определенном идентификаци- онными номерами служащих. Запись первого служащего в файле имеет наименьший идентификационный но- мер, а идентификационные номера всех последующих записей постепенно увеличиваются. На большинстве предприятий для хранения данных используются разные файлы. Например, в компании могут быть файлы расчета зарплат, файлы дебиторской задолженности (средства к получению от клиентов), файлы кредиторской задолженности (средства к выплате поставщикам), файлы инвентаризации (вся информация о номенклатуре товаров) и многие другие типы файлов. Иногда группа связанных между собой файлов называет- ся базой данных. Набор программ, предназначенных для создания баз данных и управления ими, называется системой управления базами данных (СУБД). Более подробно базы данных рассматриваются в главе 16. 14.3. Файлы и потоки В C# каждый файл рассматривается как последовательный поток байтов (рис. 14.2). Каждый файл заканчивает- ся либо маркером конца файла, либо байтом с особым номером, записываемым в поддерживаемой системой структуре административных данных. При открытии файла C# создает объект, после чего ассоциирует поток с этим объектом. При выполнении программы среда исполнения создает три объекта потока, доступ к которым осуществляется посредством свойств Console.Out, Console.In и Console.Error соответственно. Свойство Console.in возвращает объект потока стандартного ввода, позволяющий программе воспринимать вводимые данные с клавиатуры. Свойство Console.Out возвращает объект потока стандартного вывода, позволяющий программе выводить данные на монитор. Свойство Console.Error возвращает объект потока стандартной ошибки, обеспечивающий вывод на монитор сообщений об ошибках. В консольных приложениях применялись свойства Console.Out и Console.In: методы Write и WriteLine класса Console используют свойство Console.Out для выполнения вывода, а методы Read и ReadLine используют свойство Console.In для выполне- ния ввода. 0 1 2 3 4 5 6 7 8 9 ... л-1 маркер конца файла Рис. 14.2. Представление л-байтового файла в C# Для выполнения обработки файлов в C# ссылка делается на пространство имен System, ю. В это пространство имен входят определения для классов потоков, таких как streamReader (для ввода текста из файла), Streamwriter (для вывода текста в файл) и Filestream (для ввода и вывода в файл). Файлы открываются созда- нием объектов этих классов потоков, наследующих из abstract-классов TextReader, Textwriter и Stream соот- ветственно. Фактически, Console.in и Console.Out являются свойствами классов TextReader и Textwriter со- ответственно. C# предоставляет класс BinaryFormatter, используемый вместе с объектом stream для выполнения ввода и вы- вода объектов. Упорядочиванием называется преобразование объекта в формат, который можно записать в файл без потери каких бы то ни было данных объекта. Снятие упорядочивания заключается в считывании этого фор- мата из файла и воссоздание из него первоначального объекта. Класс BinaryFormatter может перевести объек- ты в последовательную форму (упорядочить) и снять упорядочивание из заданного объекта stream. Класс System, ю.stream обеспечивает функциональность для представления потоков в виде байтов. Этот класс принадлежит к типу abstract, поэтому объекты данного класса не могут обрабатываться. Классы Filestream, Memorystream и BufferedStream (все принадлежат пространству имен System, ю) наследуют из класса stream. Далее в главе для считывания данных из файлов с последовательной и произвольной выборкой, а также для записи в них данных используется класс Filestream. Класс Memorystream обеспечивает прямую передачу дан- ных в память и получение их из нее: такой тип передачи намного оперативнее других типов передачи данных
Файлы и потоки 465 (например, на жесткий диск и обратно). Класс Bufferstream использует буферизацию для передачи данных в поток и из потока. Буферизация — это повышающая производительность методика ввода/вывода, когда каждая операция вывода направляется в область памяти, называемую буфером и достаточно обширную для хранения данных многих операций вывода. После этого одной общей операцией физической передачи осуществляется фактическая передача данных на устройство вывода всякий раз при заполнении буфера. Операции вывода, на- правляемые в буфер вывода памяти, часто называются операциями логического вывода. В C# имеется много классов для выполнения операций ввода и вывода. В данной главе рассматриваются не- сколько ключевых классов потоков для реализации различных программ обработки файлов, в которых создают- ся, обрабатываются и уничтожаются файлы с последовательной и произвольной выборкой. В главе 19 широко используются классы потоков для реализации сетевых приложений. 14.4. Классы File и Directory Информация на компьютерах хранится в файлах, организованных в каталоги. Класс File предназначен для ма- нипуляций файлами, а класс Directory—для манипуляций каталогами. Класс File не может осуществлять счи- тывание из файлов или запись в них напрямую; в последующих разделах обсуждаются методы считывания и записи. Обратите внимание, что символ разделения \ (или обратный слэш) разделяет каталоги и файлы в пути. В систе- мах UNIX символ разделения обращен в противоположную сторону— / (прямой слэш или просто слэш). В имени пути C# обрабатывает оба символа одинаково. Это означает, что при указании пути c:\C_SharpZREADME, в котором используются оба символа разделения, C# все равно обработает файл корректно. В табл. 14.1 перечислены некоторые методы, содержащиеся в классе File, для определения информации о тех или иных файлах и для манипуляций с ней. Класс File содержит только методы static: создавать объекты типа File нельзя. В листинге 14.1 используются некоторые из этих методов. Таблица 14.1. Методы класса File (неполный список) — Метод static Описание AppendText Возвращение класса streamwriter, добавляемого к существующему файлу, либо созда- ние файла, если он не существует Copy Копирование файла в новый файл Create Создание файла и возвращение связанного с ним класса Filestream CreateText Создание текстового файла и возвращение связанного с ним класса streamwriter Delete Удаление выбранного файла GetCreationTime Возвращение объекта DateTime, представляющего время создания файла GetLastAccessTime Возвращение объекта DateTime, представляющего время последнего доступа к файлу GetLastWriteTime Возвращение объекта DateTime, представляющего время поиледнегм изменения файла Move Перемещение выбранного файла в заданное местоположение Open Возвращение класса Filestream, связанного с выбранным файлом и снабженного задан- ными разрешениями на считывание/запись OpenRead Возвращение класса Filestream, предназначенного только для чтения и связанного с вы- бранным файлом OpenText Возвращение класса StreamReader, связанного с выбранным файлом OpenWrite Возвращение класса Filestream для чтения/записи, связанного с выбранным файлом Класс Directory обеспечивает возможности для манипуляций каталогами. В табл. 14.2 приведено несколько методов, которые можно применять для действий с каталогами. Некоторые из этих методов используются в листинге 14.1. Таблица 14.2. Методы класса Directory (неполный список) Метод static Описание CreateDirectory Создание каталога и возвращение связанного с ним объекта Directoryinfo Delete Удаление указанного каталога 30 Зак. 3333
466 Глава 14 Таблица 14.2 (окончание) Метод static Описание Exists Возвращение true при наличии указанного каталога; в противном случае возвращение false GetLastWriteTime Возвращение объекта DateTime, представляющего время последнего внесения изменений в каталог * GetDirectories Возвращение массива типа string, представляющего имена подкаталогов в указанном каталоге GetFiles Возвращение массива типа string, представляющего имена файлов в указанном каталоге GetCreationTime Возвращение объекта DateTime, представляющего время создания каталога GetLastAccessTime Возвращение объекта DateTime, представляющего время последнего открытия каталога GetLastWriteTime Возвращение объекта DateTime, представляющего время последней записи элементов в каталог Move Перемещение указанного каталога в заданное местоположение Объект Directoryinfo, возвращаемый методом CreateDirectory, содержит информацию о каталоге. Доступ к большей части содержащейся в этом классе информации осуществляется посредством методов класса Directory. Листинг 14.1. Тестирование классов File и Directory 1 // Листинг 14.1: FileTest.cs 2 // Использование классов File и Directory 3 4 using System; 5 using System.Drawing; 6 using System.Collections; 7 using System.ComponentModel; 8 using System.Windows.Forms; 9 using System.Data; 10 using System.IO 11 12 // отображение содержимого файлов и каталогов 13 public class FileTestForm : System.Windows.Forms.Form 14 { 15 private System.Windows.Forms.Label directionsLabel; 16 17 private System*Windows.Forms.TextBox outputTextBox; 18 private System.Windows.Forms.TextBox inputTextBox; 19 20 private.System.ComponentModel.Container components = null; 21 22 [STAThread] 23 static void Main() 24 { 25 Application.Ruh(new FileTestForm()); 26 } 27 28 // код, сгенерированный Visual Studio .NET 29 30 // активизируется при нажатии пользователем клавиши 31 private void inputTextBox_KeyDown( 32 object sender. System.Windows.Forms.KeyEventArgs e) 33 { 34 // определение нажатия пользователем клавиши <Enter> 35 if (e.KeyCode = Keys.Enter) 36 { 37 string filename; // имя файла или каталога 38
Файлы и потоки 467 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 ’ 70 71 72 73 ' 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 // получение заданного пользователем файла или каталога fileName = inputTextBox.Text; // определение, является ли fileName файлом if (File.Exists(filename)) { // получение даты создания файла, // изменения файла и т. д. outputTextBox.Text = GetInformation(filename) ; // отображение содержимого файла через StreamReader try { // получение класса reader и содержимого файла StreamReader stream = new StreamReader(filename); outputTextBox.Text += stream.ReadToEnd(); } // обработка исключения при недоступном StreamReader catch(lOException) ( MessageBox.Show("File Error", "File Error", MessageBoxButtons.OK, MessageBoxIcon.Error); } } // определение, является ли fileName каталогом else if (Directory.Exists(filename)) I // массив для каталогов string[] directoryList; // получение даты создания каталога, // даты изменений и т. д. outputTextBox.Text = GetInformation(fileName); // получение списка файлов/каталогов указанного каталога directoryList = Directory.GetDirectories(filename); outputTextBox.Text += "\r\n\r\nDirectory contents:\r\n"; // содержимое вывода directoryList for (int i = 0; i < directoryList.Length; i++) outputTextBox.text += directoryList[i] + "\r\n"; ) else { // сообщение, что не существует ни файл, ни каталог MessageBox.Show(inputTextBox.Text + " does not exist", "File Error", MessageBoxButtons.OK, MessageBoxIcon.Error); ) } // конец if } // конец метода inputTextBoxJKeyDown // получение информации о файле или каталоге private string GetInformation(string fileName) { // вывод сообщения о наличии файла или каталога string information = filename + " exists\r\n\r\n";
468 Глава 14 101 // вывод даты создания файла или каталога 102 information += "Created: " + 103 FiLe.GetCreationTime(filename I + "\r\n"; 104 105 // вывод даты изменения файла или каталога 106 information += "Last modified: " + 107 File.GetLastWriteTime(filename) + "\r\n"; 108 109 // вывод даты последнего открытия файла или каталога 110 information += "Last accessed: " + 111 File.GetLastAccessTime(filename) + "\r\n" + "\r\n"; 112 113 return information; 114 115 } // конец метода Getlnformation 116 117 } // конец класса FileTestForm В классе FileTestForm (листинг 14.1) используются методы, описанные в табл. 14.1 и 14.2, для доступа к ин- формации о файлах и каталогах. Этот класс содержит метод TextBox inputTextBox (строка 18), позволяющий пользователю вводить имя файла или каталога. Для каждой клавиши, нажимаемой пользователем при вводе текста в текстовое поле, программа вызывает метод inputTextBox KeyDown (строки 31—93). При нажатии поль- зователем клавиши <Enter> (строка 35) этот метод отображает содержимое файла или каталога (рис. 14.3, а), в зависимости от текста, введенного пользователем в текстовом поле TextBox. (Обратите внимание, что если пользователь не нажимает клавишу <Enter>, то этот метод завершается без отображения содержимого.) В строке 43 используется метод Exists класса File для определения, является ли введенный пользователем текст именем существующего файла. Если пользователь указывает существующий файл, тогда в строке 47 вы- зывается private-метод Getlnformation (строки 96—115), вызывающий методы GetCreationTime (строка 103), GetLastWriteTime (строка 107) и GetLastAccessTime (строка 111) класса File для доступа к информации о фай- ле. По возвращении метода Getlnformation в строке 53 активизируется класс streamReader для считывания тек- ста из файла. Конструктор класса streamReader принимает в качестве аргумента строку, содержащую имя от- крываемого файла. В строке 54 вызывается метод ReadToEnd класса StreamReader для считывания файла, после чего отображается его содержимое. В строке 43 определяется, что указанный пользователем текст не является файлом; в строке 65 с помощью ме- тода Exists класса Directory определяется то, является ли данный текст каталогом. Если пользователь указал существующий каталог, тогда в строке 72 активизируется метод Getlnformation для доступа к информации о каталоге. В строке 75 вызывается метод GetDirectories класса Directory для получения массива string, со- держащего имена подкаталогов в указанном каталоге. В строках 81—82 отображается каждый элемент в масси- ве string (рис 14.3, б). Обратите внимание, что если в строке 65 определяется, что указанный пользователем текст не является ни файлом, ни каталогом (14.4, о), тогда строки 87—89 уведомляют пользователя (посредст- вом метода MessageBox) о том, что файла или каталога не существует (рис. 14.4, б). Рис. 14.3. Сведения об указанном файле или каталоге: а — вывод содержимого указанного файла; б — вывод списка каталогов
Файлы и потоки 469 б а Рис. 14.4. Проверка существования файла: а — ввод имени файла; б — сообщение об отсутствии файла Рассмотрим другой пример, в котором реализуются возможности манипуляций в C# файлами и каталогами. В классе FileSearchForm (листинг 14.2) используются классы File и Directory совместно с классами для вы- полнения регулярных выражений с целью выяснения количества файлов каждого типа, существующих в задан- ном пути к каталогу. Программа также служит утилитой "сборки мусора": при нахождении файла с расширени- ем bak (т. е. резервного файла) программа отображает окно сообщения MessageBox с запросом об удалении это- го файла и реагирует на введенную пользователем команду. При нажатии пользователем клавиши <Enter> или кнопки Search Directory программой активизируется метод searchButton Click (строки 52—92), осуществляющий рекурсивный поиск каталога в заданном пользователем пути. Если пользователь вводит текст в текстовое поле TextBox, тогда в строке 59 вызывается метод Exists класса Directory для определения, указывает ли введенный текст существующий каталог. Если пользователь указывает отсутствующий каталог, тогда в строках 70 и 71 программа сообщает об ошибке. ***"*'• '• .......Л—у. • - -у;-у........уу у ..у... ’ Листинг 14.2 Использование регулярных выражений дня определения типов файлов 1 ..— ........ —...«... ... • •<•....>•»« .и.... . , ». ' ,ц» •- >; . 1 // Листинг 14.2:FileSearch.cs 2 // Использование регулярных выражений для определения типов файлов 3 4 using System; 5 using System.Drawing; 6 using System.Collections; 7 using System.ComponentModel; 8 using System.Windows.Forms; 9 using System.Data; 10 using System.IO 11 using System.Text.RegularExpressions; 12 using System.Collections.Specialized; 13 14 public class DileSearchForm : Systern.Windows,Forms.Form 15 { 16 private System.Windows.Forms.Label directionsLabel; 17 private System.Windows.Forms.Label directoryLabel; 18 19 private System.Windows.Forms.Button searchButton; 20 21 private System.Windows.Forms.TextBox outputTextBox; 22 private System.Windows.Forms.TextBox inputTextBox; 23 24 private System.ComponentModel.Container components = null; 25 26 string currentDirectory = Directory.GetCurrentDirectory(); 27 string[] directoryList; // подкаталоги 28 string[] fileArray; 29 30 // сохранение обнаруженных расширений и чисел 31 NameValueCollection found = new NameValueCollectionO; 32 33 [STAThread] 34 static void Main() 35 { 36 Application.Run(new FileSearchForm()); 37 }
470 Глава 14 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 • 99 100 101 11 код, сгенерированный Visual Studio .NET // активизируется при вводе пользователем текста в текстовом поле private void inputTextBox_KeyDown( object sender, System.Windows.Forms.KeyEventArgs e) { t // определение нажатия пользователем клавиши <Enter> if (e.KeyCode = Keys.Enter) searchButton_Click(sender, e); } // конец метода inputTextBox_KeyDown // .активизируется при нажатии кнопки Search Directory private void searchButton_Click( object sender, System.EventArgs e) { // проверка ввода; по умолчанию — текущий каталог if (inputTextBox.Text != "") { // подтверждение, что введено имя существующего каталога if (Directory.Exists(inputTextBox.Text)) { currentDirectory = inputTextBox.Text; // очистить текстовое пол< ввода и обновить экран directoryLabel.Text = "Current Directory: " + "\r\n" + currentDirectory; \ } else { // сообщение, если пользователь указал отсутствующий каталог MessageBox.Show("Invalid Directory", "Error", MessageBoxButtons.OK, MessageBoxIcon.Error); } } // очистить текстовые поля inputTextBox.Clear(); outputTextBox.Clear(); // поиск каталога SearchDirectory(currentDirectory); // подведение итогов и вывод результатов foreach (string current in found) { outputTextBox.text += "* Found " + found[current] + ”" + current + " files.\r\n"; ) // сброс вывода для нового поиска found.Clear(); } // конец метода searchButton_Click // поиск каталога с помощью регулярного выражения private void SearchDirectory(string currentDirectory) ( // поиск каталога try { string filename = "";
Файлы и потоки 471 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 •118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 - 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 // регулярное выражение для расширений, соответствующих модели Regex regularExpression = new Regex( "[a-zA-Z0-9]+\\.(?<extension>\\w+)"); // сохранение результата совпадения с регулярным выражением Match matchResult; string fileExtension; // содержит расширения файлов // количество файлов с заданными расширениями в каталоге int extensioncount; // получение перечня каталогов directoryList = Directory.GetDirectories(currentDirectory); // получение списка файлов в текущем каталоге fileArray = Directory.GetFiles(currentDirectory); // итерация через список файлов foreach (string myFile in fileArray) { 11 удаление пути каталога из полного имени файла fileName = myFile.Substring( myFile.LastlndexOf("\\") +1); , // получение результата поиска регулярного выражения matchResult = regularExpression.Match(filename); I // проверка соответствия if (matchResult.Success) fileExtension = matchResult.result("${extension)"); else fileExtension - "[no extension]"; // сохранение значения из контейнера if (fgund[fileExtension] == null) found.Add(fileExtension, "I"); else { extensioncount = Int32.Parse( found[fileExtension]) + 1; found[fileExtension] = extensioncount.ToString(); } // поиск резервных файлов (bak) if (fileExtension — "bak") { // подсказка пользователю удалить bak-файл DialogResult result = * MessageBox.Show("Found backup file " + fileName + ". Delete?", "Delete Backup", MessageBoxButtons.YesNo, MessageBoxIcon.Question); // удаление файла при нажатии пользователем кнопки Yes if (result == DialogResult.Yes) { . File.Delete(myFile);
472 Глава 14 164 extensioncount = 165 Int32.Parse(found["bak"]) — 1; 166 167 found["bak"] = extensioncount.ToStringO; ’ 168 } 169 } 170 } 171 172 // рекурсивное обращение на поиск файлов в подкаталоге 173 foreach (string myDirectory in directoryList) 174 SearchDirectory(myDirectory); 175 } 176 177 // обработка исключения, если доступ к файлам не санкционирован 178 catch(UnauthorizedAccessException) 179 { 180 MessageBox.Show("Some files may not be visible" + 181 " due to permission settings", "Warning", 182 MessageBoxButtons.OK, MessageBoxIcon.Information); 183 } 184 185 } // конец метода SearchDirectory 186 187 } // конец класса FileSearchForm Если пользователь указывает существующий каталог, строка 80 передает имя каталога в качестве аргумента private-метода SearchDirectory (строки 95—185). Этот метод выясняет местоположение файлов, соответст- вующих регулярному выражению, определенному в строках 103 и 104, которое соответствует любой последова- тельности цифр или букв, отделенных точкой от одной или нескольких букв. Обратите внимание на подстроку формата {?<ехЪепз1оп>регулфное_вьражение) в аргументе к конструктору Regex (строка 104). Всем строкам с подстрокой регулярное_въ^>ажение добавлено имя extension. В данной программе переменная extension при- сваивается любой строке, совпадающей с одним или более символов. В строках 115 и 116 вызывается метод GetDirectories класса Directory для получения имен всех подкаталогов, принадлежащих текущему каталогу. В строке 119 вызывается метод GetFiles класса Directory для сохранения в массиве fileArray объекта string имена файлов текущего каталога. Цикл foreach в строках 122—170 выпол- няет поиск всех файлов с расширением bak; после этого осуществляется рекурсивное обращение к SearchDirectory для каждого подкаталога в текущем каталоге. В строках 125—126 удаляется путь каталога, так что программа может проверить только имя файла при использовании регулярного выражения. В строке 129 вызывается метод Match объекта Regex для соответствия регулярного выражения имени файла, после чего ре- зультат возвращается объекту matchResult типа Match. При успешном соответствии в строках 133 и 134 исполь- зуется метод Result объекта matchResult для сохранения расширения объекта matchResult в fileExtension (строку, которая будет содержать расширение текущего файла). Если соответствия нет, тогда в строке 136 зада- ется объект fileExtension для сохранения значения " [no extension]". В классе FileSearchForm используется экземпляр класса NameValueCollection (объявлен в строке 31) для со- хранения расширения файла и числа файлов каждого типа (рис. 14.5). Класс NameValueCollection содержит коллекцию пар "ключ/значение", каждая из которых является строкой и предоставляет метод Add для добавле- ния пары "ключ/значение". Нумерация этих пар может задаваться в соответствии с порядком, в котором добав- лялись элементы, либо в соответствии с ключевой записью. В строке 139 используется объект NameValueCollection found для определения того, первое ли это появление данного расширения файла. Если да, то в строке 140 это расширение добавляется в found в качестве ключа со значением 1. Если расширение уже имеется в found, тогда строки 143 и 144 увеличивают значение в found, относящееся к расширению, для указа- ния на очередное появление данного расширения файла. В строке 150 определяется, равен ли объект fileExtension значению "bak", т. е. является ли данный файл ре- зервным. Если равен, то в строках 153—157 пользователю подается подсказка, указывающая на то, что данный файл должен быть удален (рис. 14.6); при нажатии пользователем кнопки Yes (строка 160) строки 162—167 удаляют файл и уменьшают значение в found для типа файла bak. В строках 173 и 174 для каждого подкаталога вызывается метод SearchDirectory. С помощью рекурсии под- тверждается, что программа выполняем одну и ту же логику для обнаружения файлов с расширением bak в каж- дом подкаталоге. После проверки каждого подкаталога на предмет наличия в них файлов с расширением bak метод SearchDirectory завершается, и в строках 83—87 отображается результат.
Файлы и потоки 473 Enter Path to Search: " Search Directory Жйй «S' . .. Current Directory C:\JnetPub fusing Regular expressions 4—— . « •Found 24 vbsfitee. 'Found lasafib»; Found? ।irt*. “'owe 69fu₽W*. * Found 2МЙМ 'Foundlhtmfuee. -FoundZi gMfiles; 'Founo I mdbftias Found 10 inc tiles i* Found9 ties61a* ' Found 2 html tiles * Found 1 vtcficco file. . • »” j?w ... ' Рис. 14.5. Окно с выводом результатов поиска Рис. 14.6. Диалоговое окно с сообщением 14.5. Создание файла с последовательным доступом В C# файлы не организуются в структуры. Поэтому в файлах C# не существует таких понятий, как "запись” Это означает, что программист должен структурировать файлы под требования программных приложений. В данном примере авторы использовали текст и специальные символы для организации собственной концепции "записи". В приведенных ниже примерах демонстрируется обработка файлов в программном приложении поддержки банковских счетов. Эти программы имеют похожие пользовательские интерфейсы, поэтому для инкапсулирова- ния GUI базового класса (см. изображение окна на рис. 14.7) был создан класс BankuiForm (листинг 14.3). Класс BankuiForm содержит четыре метки (строки 15, 18, 21 и 24) и четыре текстовых поля (строки 16, 19, 22 и 25) Методы ClearTextBoxes (строки 49—64), Set text BoxValues (строки 67—91) и GetTextBoxValues (строки 94— 110) сбрасывают, задают значения и получают значения текста в текстовых полях соответственно. Для повторного использования класса BankuiForm GUI компилируется в библиотеку DLL путем создания проек- та типа Windows Control Library (созданная библиотека DLL имеет имя BankLibrary). Так же, как все коды в книге, эта библиотека имеется на Web-сайте www.deitel.com в ссылке Downloads/Resources. Впрочем, читате- лям может потребоваться изменить ссылку на эту библиотеку, поскольку, вероятнее всего, в их системах она расположена в другом месте 1 // Листинг 14.3: BankUI.cs 2 // Многократно используемая форма Windows для примеров главы 3 4 using System; 5 using System.Drawing; 6 using System.Collections; 7 using System.ComponentModel; 8 using System.Windows.Forms; 9 using System.Data; 10 11 public class BankuiForm : System.Windows.Forms.Form 12 { 13 private System.ComponentModel.Container components = null; 14 15 public System.Windows.Forms.Label accountLabel; 16 public System.Windows.Forms.TextBox accountTextBox; 17 18 public System.Windows.Forms.Label firstNameLabel; 19 public System.Windows.Forms.TextBox firstNameTextBox; 20
474 Глава 14 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 ‘ 66а 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 public System.Windows.Forms.Label lastNameLabel; public System.Windows.Forms.TextBox lastNameTextBox; public System.Windows.Forms.Label balanceLabel; public System.Windows.Forms.TextBox balancetextBox; // количество текстовых полей на форме protected int textBoxCount = 4; // перечисление констант указывает индексы TextBox public enum TextBoxIndices { ACCOUNT, FIRST, LAST, BALANCE } // конец enum [STAThread] static void Main() { Application.Run(new BankUIForm()); } // код, сгенерированный Visual Studio .NET 11 сброс содержимого всех текстовых полей public void ClearTextBoxes() { // итерация по каждому Control на форме for (int i = 0; i < Controls.Count; i++) { Control meControl = Controls[i]; // элемент управления // определение, является ли Control текстовым полем if (myControl is textbox) { // сброс свойства Text (установка в пустую строку) myControl.Text = } } } // конец метода CleartextBoxes // установка значений текстового поля // на значения строкового массива public void SetTextBoxValues(string[] value) { // определение корректной длины строкового массива if (values.Length != TextBoxCount) { // сброс исключения при некорректной длине throw (new ArgumentException ("There must be " + (TextBoxCount +1) + " strings in the array")); } // установка значений массива, если массив имеет корректную длину else { // присвоение элементу массива значения текстового поля accountTextBox.Text = values[(int)TextBoxIndices.ACCOUNT];
Файлы и потоки 475 8 3 firstNameTextBox.Text = 84 values[(int)TextBoxIndices.FIRST]; 85 lastNameTextBox.Text = 86 values[(int)TextBoxIndices.LAST] ; 87 balanceTextBox.Text = 88 values[(int)TextBoxIndices.BALANCE]; 89 } 90 91 ) // конец метода SetTextBoxValues 92 93 94 95 96 97 9t 99 100 101 102 103 104 105 106 107 108 109 110 111 112 // возвращение значений текстового поля в виде строк массива public string[] GetTextBoxValues() { string[] values - new string[TextBoxCount]; // копирование текстовых полей в строковый массив values[(int)TextBoxIndices.ACCOUNT] = accountTextBox.Text = values[(int)TextBoxIndices.FIRST] = firstTextBox.Text = values[(int)TextBoxIndices.LAST] = lastTextBox.Text = values[(int)TextBoxIndices.BALANCE] = balanceTextBox.Text = return values; } // конец метода GetTextBoxValues Рис. 14.7. Вид формы в режиме конструктора ) // конец класса BankUIForm Вид формы для разрабатываемого приложения представлен на рис. 14.7. В листинге 14.4 содержится класс Record, используемый в листингах 14.5—14.7 для последовательного считы- вания записей из файла и их записи в файл. Данный класс также принадлежит библиотеке BankLibrary, поэтому он расположен в том же проекте, что и класс BankUIForm. Атрибут Serialize (строка 6) указывает компилятору, что объекты класса Record могут быть упорядочены (се- риализованы), т. е. представлены в виде наборов байтов: эти байты можно считывать и записывать в потоки. Объекты, которые нужно записать в поток или считать из него, должны включать этот атрибут в свои определе- ния классов. Класс Record содержит private-элементы данных— account, firstName, lastName. и balance (строки 9—12), которые коллективно представляют всю информацию, необходимую для сохранения данных записи. Конструк- тор по умолчанию (строки 15—17) задает этим элементам их значения по умолчанию (т. е. пустые), а перегру- женный конструктор (строки 20—28) присваивает этим элементам значения параметров. Класс Record также предоставляет свойства Account (строки 31—43), FirstName (строки 46—58), LastName (строки 61—73) и Balance (строки 76—88) для доступа к номеру счета, имени, фамилии и сальдо каждого клиента соответст- венно. 1 // Листинг 14.4: Record.cs 2 // Упорядочиваемый класс, представляющий запись данных 3 4 using System; 5 6 [Serializable] 7 public class Record 8 { 9 private int account; 10 private string firstName; 11 private string lastName; 12 private double balance;
476 Глава 14 13 14 14а 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 . 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 II конструктор по умолчанию присваивает // элементам значения по умолчанию public Record() : this(0, 0.0) { ) // перегруженный конструктор присваивает элементам значения параметров public Record(int accountvalue, string firstNameValue, string lastNameValue, double balancevalue) { Account = accountValue; FirstName = firstNameValue; LastName = lastNameValue; Balance - balancevalue; } // конец конструктора // свойство Account public int Account { get ( return account; } set { account = value; } } II конец свойства Account // свойство FirstName public string FirstName { get { return firstName; } set { firstName = rvalue; } ) // конец свойства FirstName // свойство LastName public string LastName { get { return lastName; } set ( lastName = value; } } // конец свойства LastName
Файлы и потоки 477 75 // свойство Balance 76 public double Balance 77 { 78 get 79 { 80 return balance; 81 } 82 83 set 84 { 85 balance = value; 86 } 87 88 } // конец свойства Balance 89 90 } // конец класса Record В классе CreateFileForm (листинг 14.5) используются экземпляры класса Record для создания файла с последо- вательной выборкой, который можно использовать в системе счетов дебиторской задолженности, т. е. в про- грамме, структурирующей данные о финансовых средствах дебиторов компании. Программа получает номер счета каждого клиента, его имя, фамилию и сальдо (сумма задолженности клиента за полученные товары или услуги)— рис. 14.8. Данные каждого клиента составляют его запись. В программном приложении номер счета является ключем записи: файлы создаются и поддерживаются в порядке номеров счетов. Программа предпола- гает, что пользователь будет вводить записи в порядке номеров счетов. Впрочем, более совершенные системы расчета дебиторской задолженности имеют возможность сортировки. Пользователь может вводить записи в любом порядке, и впоследствии их можно отсортировать и записать в файл в нужном порядке (Обратите вни- мание, что все выходные данные главы следует считывать построчно слева направо.) Форма BankUI Рис. 14.8. Форма, в которую вводятся сведения о клиенте Листинг 14.5 содержит код для класса CreateFileForm, создающий или открывающий файл (если он существу- ет) — рис. 14.9, после чего пользователь получает возможность ввода в этот файл банковской информации. В строке 16 импортируется пространство имен BankLibrary; оно содержит класс BankUIForm, из которого насле- дует класс CreateFileForm (строка 18). Из-за этого отношения наследования пользовательский интерфейс клас- са CreateFileForm похож на GUI класса BankUIForm (рис. 14.10) за исключением того, что унаследованный класс имеет кнопки Save As, Enter и Exit. • При нажатии пользователем кнопки Save As программой активизируется метод saveButtonClick (строки 41— 85). В строке 45 создается объект класса SaveFiieDialog, принадлежащий пространству имен System.windows.Forms. Объекты этого класса используются для выбора файлов (см. рис. 14.9). В строке 46 вы- зывается метод ShowDialog объекта SaveFiieDialog для его отображения. После отображения объект SaveFiieDialog запрещает пользователю взаимодействие с любыми другими окнами в программе до тех пор, пока он не закроет объект SaveFiieDialog нажатием либо кнопки Save, либо Cancel. Диалоговые окна с таким поведением называются модальными. Пользователь выбирает нужный диск, каталог и имя файла, после чего нажимает кнопку Save. Метод ShowDialog возвращает целое число, указывающее нажатую кнопку (Save или Cancel) для закрытия диалогового окна. В данном примере свойство DialogResult формы (Form) получает это целое число. В строке 53 проверяется, нажал ли пользователь кнопку Cancel, путем сравнения значения, воз- вращенного свойством DialogResult, с константой DialogResult .Cancel. Если эти значения равны, тогда метод saveButton Click возвращает управление (строка 54). Если значения не равны (т. е. пользователь нажал кнопку Save вместо Cancel), то в строке 57 используется свойство FileName класса SaveFiieDialog для получения фай- ла, выбранного пользователем.
478 Глава 14 Рис. 14.9. Диалоговое окно сохранения/открытия файла 1 // Листинг 14.5: CreateSequentialAccessFile.cs 2 // Создание файла с последовательной’ выборкой 3 4 // пространство имен C# 5 using System; 6 using System. Drawing; 7 using System.Collections; 8 using System.ComponentModel; 9 using System.Windows.Forms; 10 using System.Data; 11 using System.IO; 12 using System.Runtime.Serialization.Fonrtatters.Binary; 13 using System.Runtime.Serialization; 14 15 // пространство имен Deitel 16 using BankLibrary; 17 18 public class CreateFileForm : BankUIForm 19 { 20 private System.Windows.Forms.Button saveButton; 21 private System.Windows.Forms.Button enterButton; 22 private System.Windows.Forms.Button exitButton 23 24 - ' private System ComponentModel.Container components = null; 25 26 // упорядочивание записей в двоичном формате 27 private BinaryFormatter formatter = new BinaryFormatter(); 28 29 // поток, через который упорядочиваемая область записывается в файл 30 private Filestream output; 31 32 [STAThread] 33 static void Main() 34 { 35 Application.Run(new CreateFileForm()); 36 } 37 38 // код, сгенерированный Visual Studio .JNET 39 40 // активизируется при нажатии пользователем кнопки Save 41 private void saveButton_Click( 42 object sender, System.EventArgs e)
Файлы и потоки 479 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 { // создание диалогового окна, обеспечивающего сохранение файла SaveFileDialog filechooser = new SaveFileDialog{); DialogResult result = fileChooser.ShowDialogO; string filename; // имя файла для сохранения данных // пользователь создает файл filechooser.ChecFileExists = false; // выход из обработчика события при нажатии кнопки Cancel if (result = DialogResult.Cancel) return; // получение заданного имени файла fileName = filechooser.FileName; // сообщение, если пользователь указал отсутствующий файл if (fileName = || filename — null) MessageBox.Show("Invalid File Name", "Error", MessageBoxButtons.OK, MessageBoxIcon.Error); else { // сохранение файла через Filestream, если файл существует try { 11 открытие файла с доступом к записи output = new FileStream(filename, FileMode.OpenOrCreate, FileAccess.Write); // отключение кнопки Save As и активизация кнопки Enter saveButton.Enabled = false; enterButton.Enabled = true; t } // обработка исключения, если файл не существует catch (FileNotFoundException) { // уведомление пользователя, если файл не существует MessageBox.Show/"File Does'Not Exist", "Error", MessageBoxBut tons.OK, MessageBoxIcon.Error), ) ) } // конец метода saveButton_Click // активизация при нажатии кнопки Enter private void enterButton_Click( object sender, System.EventArgs e) { // сохранение строкового массива значений TextBox string[] values - GetTextBoxValues(); // запись, содержащая значения TextBox для упорядочивания Record record = new Record(); // определение, пустое ли текстовое поле счета if (values{ (int)TextBoxIndices.ACCOUNT] != ""). { // сохранение значений текстовых полей в записи и упоряд. записей try { // получение значения номера счета из текстового поля int accountNumber = Int32.Parse( values[(int)TextBoxIndices.ACCOUNT]);
480 Глава 14 107 108 109 110 111 112 113 114 115 .116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 /l определение корректности accountNumber if (accountNumber > 0) { // сохранение значений текстовых полей в записи record.Account « accountNumber; record.FirstName = values[(int)TextBoxIndices.FIRST]; record.LastName - values[(int)TextBoxIndices.LAST]; record.Balance = Double.Parse(values[ (int)TextBoxIndices.BALANCE]); // запись Record в Filestream (упорядочивание объекта) formatter.Serialize(output, record); ) else { // уведомление пользователя при неверном номере счета MessageBox.Show("Invalid Account Number", "Error", MessageBoxButtons.OK, MessageBoxIcon.Error); } } // уведомление при ошибке во время упорядочивания catch(SerializationException) { MessageBox.Show("Error Writing to Fil*e", "Error", MessageBoxButtons.OK, MessageBoxIcon.Error); } 11 уведомление при ошибке в формате параметра catch(FormatException) { MessageBox.Show("Invalid Format", "Error", MessageBoxButtons.OK, MessageBoxIcon.Error); } ) ClearTextBoxes(); // сброс значений текстовых полей ) // конец метода enterButton_Click // активизация при нажатии кнопки Exit private void exitButton_Click{ object sender, System.EventArgs e) { // определение существования файла if (output •= null) { // закрытие файла try { output.Close() ; } // уведомление пользователя об ошибке при закрытии файла catch(lOException) { MessageBox.Show("Cannot close file", "Error", MessageBoxButtons.OK, MessageBoxIcon.Error); )
Файлы и потоки 481 170 Application.Exit(); 171 172 } // конец метода exitButton_Click 173 174 } // конец класса CreateFileForm б Рис. 14.10. Ввод данных в форму: а — первая запись!; б — вторая запись; в — третья запись; а — четвертая запись; д — пятая запись; е — пустая запись и нажатие кнопки Exit для закрытия формы Как отмечалось выше, файлы можно открывать для выполнения манипуляций с текстом созданием объектов классов Filestream. В данном примере необходимо, чтобы файл был открыт для выходных данных, поэтому в строках 69 и 70 создается объект Filestream. Используемый конструктор Filestream принимает три аргумента: строку, содержащую имя файла для открытия, константу, описывающую процесс открытия файла, и константу, описывающую разрешение для записи в файл. Строка 70 передает константу FileMode.OpenOrCreate в конст- руктор Filestream в качестве его второго аргумента. Эта константа указывает на то, что объект Filestream должен открывать файл, если файл существует, либо создавать файл в противном случае. C# предлагает другие константы FileMode, описывающие процессы открытия файлов; они будут рассматриваться при использовании в примерах. Строка 70 передает константу FileAccess.write в конструктор Filestream в качестве его третьего аргумента. Данная константа обеспечивает выполнение программой только операций записи в объект FileStream. Для этого параметра в C# имеются две другие константы: FileAccess.Read доступа только для чте- ния и FileAccess.ReadWrite — доступа для считывания и записи. О хорошем стиле программирования__________________________________________________________ При открытии файлов пользуйтесь перечислением FileAccess для управления пользовательским доступом к этим файлам. После ввода информации в каждое текстовое поле пользователь нажимает кнопку Enter; при этом вызывается метод enterButton Click (строки 88—147) для сохранения данных TextBox в указанный пользователем файл. При вводе корректного номера счета (т. е. целого числа больше нуля) в строках 112—118 значения текстовых полей сохраняются как объект типа Record. При вводе неверных данных в одно из текстовых полей (например, нечисловых символов в поле Balance) программа выдает исключение FormatException. Блок catch в стро- ках 138—142 обрабатывает это исключение уведомлением пользователя (посредством окна сообщения MessageBox) о вводе недопустимого формата. Если пользователь ввел корректные данные, в строке 120 делает запись в файл активизацией метода Serialize объекта BinaryFormatter (создан в строке 27). Класс BinaryFormatter использует методы Serialize и Deserialize для записи и считывания объектов в потоки. Ме- тод Serialize записывает в файл представление объекта. Метод Deserialize считывает это представление из файла и воспроизводит первоначальный объект. Оба метода выдают исключение SerializationException при 31 Зак. 3333
482 Глава 14 появлении ошибки во время упорядочивания или отмены упорядочивания (ошибки возникают, когда методы делают попытки доступа к потокам или записям, которых не существует). Оба метода serialize и Deserialize требуют объект Stream (например, Filestream) В качестве параметра С тем, чтобы класс BinaryFormatter мог получить доступ к нужному файлу; класс BinaryFormatter должен получить экземпляр класса, производного от класса Stream, потому что класс Stream принадлежит к типу abstract. Класс BinaryFormatter принадлежит К пространству имен System.Runtime.Serialization.Formatters.Binary. Распространенная ошибка программирования_________________________________________________ Невозможность открытия файла до попытки ссылки на него в программе является логической ошибкой. При нажатии пользователем кнопки Exit программа вызывает метод exitButton Click (строки 150—172) для выхода из приложения. Класс Filestream закрывается i строке 159, если он был открыт, а в строке 170 осуще- ствляется выход из программы. Совет по повышению производительности____________________________________________________ Если программа больше не делает ссылок на файл, закройте его явно. При этом освободятся ресурсы про- грамм, продолжающих выполнение еще долгое время после окончания использования того или иного файла. Практика явного закрытия файлов повышает удобочитаемость программ. Совет по повышению производительности____________________________________________________ Явное освобождение ресурсов при отсутствии необходимости в них автоматически делает их доступными для повторного использования программой, что повышает эффективность их загрузки. В примере-образце программы листинга 14.5 информация вводилась для пяти счетов (табл. 14.3). Программа не демонстрирует процесса воспроизведения данных записи в файле. Для подтверждения успешного создания файла в следующем разделе рассматривается программа для считывания и отображения файла. Таблица 14.3. Примерные данные для программы в листинге 14.5 Номер счета (Account) Имя (First Name) Фамилия (Last Name) Сальдо (Balance) 100 Nancy Brown -25.54 200 Stacey Dunn 314.33 300 Doug Barker 0.00 400 Dave Smith 258.34 500 Sam Stone 34.98 14.6. Считывание данных из файла с последовательным доступом Данные сохраняются в файле так, что при необходимости их можно извлечь для обработки. В предыдущем раз- деле было продемонстрировано создание файла для использования в программных приложениях с последова- тельной выборкой. В данном разделе рассмотрим процесс последовательного считывания данных из файла (форма для отображения данных из файла представлена на рис. 14.11). Класс ReadSequentialAccessFileFormat (листинг 14.6) считывает записи из файла, созданного в листинге 14.5, после чего отображает содержимое каждой записи. Большая часть кода в данном примере сходна с кодом в лис- тинге 14.5, поэтому опишем только отличительные аспекты данного программного приложения. Листинг 14.6. Считывание из файла с последовательным доступом - - ..... •• • *... .......... 1 // Листинг 14.6: ReadSequentialAccessFile.cs 2 // Считывание из файла с последовательным доступом 3 4 // пространство имен C# 5 using System; 6 using System.Drawing; 7 using System.Collections;
Файлы и потоки 483 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 using System.ComponentModel; using System.Windows.Forms; using System.Data; using System.IO; using System. Runtime. Serialization. Formatters. Binary; using System.Runtime.Serialization; // пространство имен Deitel using BankLibrary; public class ReadSequentialAccessFileForm : BankuiForm { private System.Windows.Forms.Button openButton; private System.Windows.Forms.Button nextButton; Рис. 14.11. Форма, в которую выводятся сведения о клиенте private System ComponentModel.Container components = null; // поток, по которому из файла считываются упорядочиваемые данные private Filestream input; I // объект для снятия упорядочения с записи в двоичном формате private BinaryFormatter reader = new BinaryFormatter(); [STAThread] static void Main() { Application.Run(new ReadSequentialAccessFileForm()); } // код, сгенерированный Visual Studio .NET // активизируется при нажатии кнопки Open private void openButton_Click( object sender, System.EventArgs e) { 11 создание диалогового окна, обеспечивающего открытие файла OpenFileDialog fileChooser = new OpenFileDialog(); DialogResult result - fileChooser.ShowDialogO; string fileName; // имя файла, содержащего данные 11 выход из обработчика событий в случае нажатия кнопки Cancel if (result = DialogResult.Cancel) return; // получение указанного имени файла fileName = fileChooser.FileName; ClearTextBoxes(); // сообщение при указании некорректного имени файла if (filename == "" || filename — null) MessageBox.Show("Invalid File Name", "Error", MessageBoxButtons.OK, MessageBoxIcon.Error); else { // создание Filestream для получения доступа к чтению файла input = new Filestream(filename, FileMode.Open, FileAccess.Read); // активизация кнопки Next Record nextButton.Enabled = true; } } // конец метода OpenButton_Click
484 Глава 14 72 // активизация при нажатии кнопки Next Record 73 private void nextButton_Click( 74 object sender, Syste.EventArgs e) 75 { 76 // отмена упорядочения записи и сохранение данных в текстовых полях 77 try 78 { 79 // получение следующей доступной в файле записи 80 Record record = 81 (Record)reader.Deserialize(input); 82 83 // сохранение значений записи во временном строковом массиве 84 stringl] values = new string!] { 85 record.Account.Tostring(), 8 6 record.FirstName.Tostring(), 87 record.LastName.ToString(), 188 record. Balance.Tostring () }; 89 90 // копирование значений строкового массива в текстовые поля 91 SetTextBoxValues(values); 92 } 93 94 // обработка исключения при отсутствии записей в файле 95 catch(SerializationException) 96 { 97 // закрытие класса FilcStream при отсутствии записей в файле 98 input.Close(); 99 100 // активизация кнопки Open File 101 openButton.Enabled = true; 102 103 // деактивизация кнопки Next Record 104 nextButton.Enabled = false; 105 106 ClearTextBoxes(); 107 108 // уведомление пользователя об отсутствии записей в файле 109 MessageBox.Show("No more records in file", 110 MessageBoxButtons.OK, MessageBoxIcon.Information); 111 } 112 113 ) // конец метода nextButton_Click 114 115 } // конец класса readSequentialAccessFileForm При нажатии пользователем на кнопку Open File программа вызывает метод openButton_ciick (строки 40—70). В строке 44 создается объект класса OpenFileDialog, а в строке 45 вызывается метод объекта ShowDialog для отображения диалогового окна Open (рис. 14.12). Поведение и GUI двух типов диалоговых окон одинаковы за исключением того, что кнопка Save заменяется на кнопку Open. При вводе пользователем корректного имени файла в строках 63 и 64 создается объект Filestream и присваивается ссылке input. Константа FileMode.Open передается как второй аргумент конструктору Filestream. Эта константа указывает на то, что объект Filestream должен открывать файл, если последний существует, либо выдавать исключение FileNotFoundException, если файла не существует. (В данном примере конструктор Filestream не будет выда- вать исключение FileNotFoundException, потому что класс OpenFileDialog требует от пользователя ввода име- ни существующего файла.) В последнем примере (см. листинг 14.5) текст в файл записывался с помощью объ- екта Filestream с доступом только для записи. В данном примере (см. листинг 14.6) файлу задается доступ только для чтения передачей константы FileAccess.Read в качестве третьего аргумента конструктору Filestream. Совет по тестированию и отладке_________________________________________________________________ Файл следует открывать в режиме только для чтения FileAccess.Read, если содержимое файла не будет из- меняться. Это предохранит содержимое от нежелательных изменений.
Файлы и потоки 485 Рис. 14.12. Диалоговое окно открытия файла При нажатии пользователем кнопки Next Record (рис. 14.13) программа вызывает метод nextButton Click (строки 73—113), считывающий очередную запись из указанного пользователем файла. (Кнопку Next Record следует нажать после открытия файла для просмотра первой записи.) В строках 80 и 81 вызывается метод Deserialize объекта BinaryFormatter для считывания следующей записи. Метод Deserialize считывает дан- ные и приводит результат в Record: такое приведение необходимо, потому что метод Deserialize возвращает ссылку типа Object. Затем в строках 84—91 отображаются значения записи Record в текстовых полях. При по- пытке метода Deserialize снять упорядочение с не существующей в файле записи (т. е. в программе показаны все записи файла) метод выдает исключение SerializationException. Блок catch (строки 95—111), обрабаты- вающий это исключение, закрывает объект Filestream (строка 98) и уведомляет пользователя о том, что запи- сей больше нет (строки 109 и 110) — рис. 14.14. г Рис. 14.13. Вывод данных в форму: а — пререход от первой записи ко второй: б — переход от второй записи к третьей; в — переход от третьей записи к четвертой; г — переход от четвертой записи к пятой; д — переход от пятой записи к шестой (несуществующей) Рис. 14.14. Сообщение об отсутствии записей
486 Глава 14 Для последовательного извлечения данных из файла, как правило, программы начинают обработку с начала файла, последовательно считывая данные до тех пор, пока не будут найдены нужные. Иногда во время выпол- нения программы бывает необходимо несколько раз обработать файл последовательно (с начала файла). Объект Fi lest ream может изменить позицию указателя местоположения файла (содержащего число следующего бай- та для считывания из файла или записи в файл) с размещением его в любом месте; эта особенность будет рас- смотрена при представлении программ обработки файлов с произвольной выборкой. После открытия объекта Filestream его указатель файла устанавливается на ноль (т. е. в начало файла). Совет по повышению производительности________________________________________________ Закрытие и повторное открытие файла для перемещения его указателя в начало файла занимает достаточно много времени; производительность программы при этом часто снижается. А теперь рассмотрим более надежную программу, построенную по принципам программы из листинга 14.6. Класс creditinquiryForm (листинг 14.7) представляет собой программное приложение заявок на кредиты, по- зволяющее руководителям кредитных отделов просматривать информацию о клиентах с кредитовыми сальдо (тех, кому задолжала компания), с нулевыми сальдо (тех, кто ничего не должен компании) и о клиентах с дебе- товыми сальдо (тех, кто остался должен финансовые средства за полученные ранее товары или услуги). Обрати- те внимание, что в строке 21 объявлен класс RichTextBox, в котором будет отображена информация о счете. Класс RichTextBox обеспечивает большую функциональность, нежели обычные текстовые поля (TextBox): на- пример, поля RichTextBox имеют метод Find для поиска отдельных строк и метод LoadFile для отображения содержимого файлов. Класс RichTextBox не наследуется из класса TextBox; эти два класса, скорее, наследуются из класса System.windows.Forms.TextBoxBase типа abstract. В данном примере используется класс RichTextBox, потому что он по умолчанию отображает несколько строк текста, тогда как объект TextBox — только одну. С другой стороны, установкой его свойства Multiline на true можно задат^ отображение объек- том TextBox нескольких строк текста. В программе, представленной на рис. 14.15, показаны кнопки, позволяющие руководителю кредитного отдела получать информацию о кредитах. Нажатием кнопки Open File открывается файл для сбора данных. Нажатием кнопки Credit Balances открывается список счетов, имеющих кредитовые сальдо, Debit Balances — дебетовые, а при нажатии кнопки Zero Balances выводится список счетов с нулевыми сальдо. При нажатии на кнопку Done пользователь выходит из программы. / Рис. 14.15. Вид формы для рассматриваемого примера При нажатии кнопки Open File в программе вызывается метод openButton ciick (строки 49—76). В строке 53 создается объект класса OpenFileDialog, а в строке 54 вызывается метод объекта showDialog для отображения диалогового окна Open (рис. 14.16), где пользователь вводит имя файла для открытия. При нажатии кнопки Credit Balances, Debit Balances или Zero Balances программой активизируется метод get_ciick (строки 80—142). В строке 83 в объект Button вводится параметр sender, являющийся ссылкой на объект, отправивший событие. В строке 86 извлекается текст объекта Button, используемый программой для определения того, какую кнопку GUI нажал пользователь. В строках 96 и 97 создается объект Filestream с дос- тупом только для чтения и присваивается ссылке input. В строках 102—125 запрограммирован цикл while, ис- пользующий метод ShouldDisplay типа private (строки 145—170) для определения необходимости отображе- ния всех записей в файле. Цикл while получает каждую запись многократным вызовом метода Deserialize объ- екта Filestream (строка 105). Когда указатель положения файла достигает его конца, метод Deserialize выдает исключение serializationException, обрабатываемое блоком catch в строках 136—140: в строке 139 вызыва- ется метод Close класса Filestream для закрытия файла, и метод get_ciick возвращает управление.
Файлы и потоки 487 Рис. 14.16. Диалоговое окно для открытия файла 1 // Листинг 14.7: Creditlnquiry.cs 2 // Последовательное считывание файла и отображение содержимого 3 //на основании типа счета, указанного пользователем За // (кредитовое, дебетовое или нулевое сальдо) 4 5 // пространство имен C# 6 using System; 7 using System.Drawing; 8 using System.Collections; 9 using System. ComponentModel; 10 using System.Windows.Forms; 11 using System.Data; 12 using System.10; 13 using System.Runtime.Serialization.Formatters.Binary; 14 using System.Runtime.Serialization; 15 16 // пространство имен Deitel 17 using BankLibrary; 18 19 public class CreditInquiryForm : Systemwindows.Forms.Form 20 { 21 ^private System.Windows.Forms.RichTextBox displayTextBox; 22 23 private System.Windows.Forms.Button doneButton; 24 private System.Windows.Forms.Button zeroButton; 25 private System.Windows.Forms.Button debitButton; 26 private System.Windows.Forms.Button creditButton; 27 private System.Windows.Forms.Button openButton; 28 29 private System.ComponentModel.Container components = null; 30 31 // поток, по которому из файла считываются упорядочиваемые данные 32 private Filestream input; 33 34 // объект для снятия упорядочения с записи в двоичном формате 35 private BinaryFormatter reader = new BinaryFormatter();32 36 37 // имя файла, сохраняющего кредитовое, 37а // дебетовое и нулевое сальдо 38 private string fileName 39 40 [STAThread] 41 static void Main()
488 Глава 14 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 95а 96 97 98 99 100 101 102 103 104 105 106 { Application.Run(new CreditInquiryForm ()); } // код, сгенерированный Visual Studio .NET // активизируется при нажатии кнопки Open File private void openButton_Click( object sender, System.EventArgs e) { // создание диалогового окна, обеспечивающего открытие файла OpenFileDialog fileChooser = new OpenFileDialog(); DialogResult result = fileChooser.ShowDialogO; // выход из обработки событий, если пользователь нажал Cancel if (result = DialogResult.Cancel) return; // получение имени от пользователя fileName = fileChooser.FileName; // сообщение при указании пользователем отсутствующего файла if (fileName — || filename = null) MessageBox.Show("Invalid File Name", "Error”, MessageBoxButtons.OK, MessageBoxIcon.Error); else { // активизация всех кнопок GUI, кроме кнопки Open File openButton.Enabled = false; creaditButton.Enabled = true; debitButton.Enabled = true; zeroButton.Enabled = true; ) ) // конец метода openButton_Click 11 активизируется при нажатии пользователем кнопки кредитового, // дебетового или нулевого сальдо private void get_Click(object sender, System.EventArgs e) < // явное преобразование sender на объект типа Button Button senderButton = (Button)sender; // получение текста от нажатой кнопки, сохраняющей тип счета string accountType = senderButton.Text; // считывание и отображение информации файла try { // закрытие файла предьщущей операции if (input != null) input.Close(); // создание класса Filestream // для получения доступа к чтению файла input = new Filestream(filename, FileMode.Open, FileAccess.Read); displayTextBox.Text = "The accounts are:\r\nrt; // прохождение файла до конца while (true) { // получение следующей имеющейся в файле записи Record record = (Record)reader.Deserialize(input);
Файлы и потоки 489 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 // сохранение в сальдо последнего поля записи Double balance = record.Balance; // определение необходимости отображения сальдо if (ShouldDisplay(balance, accountType)) { // отображение записи string output = record.Account + "\t" + record.FirstName + "\t" + record.LastName + new string(’ ’, 6) + "\t"; // отображение сальдо в нужном денежном формате output += String.Format( ”{0:F}", balance) + "W; // копирование выходных данных на экран displayTextBox.Text += output; } ) } // обработка исключения, когда файл невозможно закрыть catch(lOException) { MessageBox.Show("Cannot Close File", "Error", MessageBoxButtons.OK, MessageBoxIcon.Error); ) // обработка исключения при отсутствии записей catch(SerializationException) { // закрытие класса Filestream при отсутствии записей в файле input.Close(); } ) // конец метода get_Click // определение необходимости отображения заданной записи private bool ShouldDisplay(double balance, string accountType) { - if (balance > 0) { // отображение кредитовых сальдо if (accountType == "Credit Balances") return true; } else if (balance < 0) { // отображение дебетовых сальдо if (accountType == "Debit Balances") return true; } else // сальдо == 0 { 11 отображение нулевых сальдо if (accountType == "Zero Balances") return true; ) return false; } // конец метода ShouldDisplay
490 Глава 14 172 173 174 175 176 177 178 179 180 181 182 183 184 // активизируется при нажатии кнопки Done private void doneButton_Click( object sender, System.EventArgs e) { // определение существования файла if (input != null) { // закрытие файла try { input.Close(); ) 185 // обр-ка исключения, если класса Filestream не существует 186 catch(lOException) 187 { 188 // уведомление пользователя об ошибке закрытия файла 189 MessageBox.Show("Cannot close file", "Error", 190 MessageBoxButtons.OK, MessageBoxIcon.Error); 191 } 192 } 193 194 Application.Exit(); 195 196 } // конец метода doneButton_Click 197 198 } // конец класса CreditInquiryFrom Результат работы программы представлен на рис. 14.17. Рис. 14.17. Вывод данных после нажатия разных кнопок: а — нажатие кнопки Credit Balances; б — нажатие кнопки Debit Balances; в — нажатие кнопки Zero Balances
Файлы и потоки 491 14.7. Файлы с произвольном доступом До сих пор рассматривалось создание файлов с последовательным доступом к данным и поиск в таких файлах конкретной информации. Однако файлы с последовательным доступом непригодны для так называемых про- граммных приложений "мгновенной выборки", где конкретная запись информации должна находиться незамед- лительно. В число популярных программ мгновенной выборки входят системы бронирования авиабилетов, бан- ковские системы, системы автоматизации учета в торговле, СУБД, автоматические кассовые машины и другие типы систем обработки транзакций, требующие оперативного доступа к особым данным. Банк, в котором у человека открыт счет, может иметь сотни тысяч и даже миллионы других клиентов, однако при использовании им "автоматического кассира" сальдо нужного счета проверяется в считанные секунды Такой тип мгновенного доступа стал возможным благодаря файлам с произвольным доступом. К отдельным записям файла с произ- вольным доступом может осуществляться прямой (и оперативный) доступ без поиска в потенциально большом количестве других записей, как это происходит в случае с файлами с последовательным доступом. Иногда фай- лы с произвольным доступом называются файлами прямого доступа. Ранее в данной главе уже говорилось о том, что в C# файлам не "навязывается" никакой структуры, поэтому приложения, в которых используются файлы с произвольным доступом, должны реализовывать данную воз- можность. Существует много способов создания файлов с произвольной выборкой. Возможно, самый простой требует, чтобы все зациси в файле были одной фиксированной длины. Использование записей фиксированной длины позволяет программе рассчитать (как функцию размера записи и ключевой записи) точное местоположе- ние любой записи относительно начала файла. Вскоре мы продемонстрируем, как данная функция ускоряет дос- туп к нужным записям даже в файлах большого размера. На рис. 14 18 наглядно представлена организация файла с произвольным доступом, состоящего из записей оди- наковой длины (каждая запись на рисунке имеет длину 100 байтов). Читатели могут рассматривать файл с про- извольным доступом по аналогии с железнодорожными платформами, перевозящими автомобили, одни из ко- торых — порожние, а другие — с грузом. 0 100 200 300 400 I I I I I 500 I Смещение байтов 100 100 100 100 100 100 байтов байтов байтов байтов байтов байтов Рис. 14.18. Файл с произвольной выборкой с записями фиксированной длины Данные в файл с произвольным доступом можно вставлять без уничтожения других данных в файле. Кроме то- го, сохраненные ранее данные можно обновлять или удалять без необходимости переписывания всего файла. В последующих разделах описывается процесс создания файла с произвольным доступом, записи в него дан- ных, их считывания в последовательной и произвольной форме, обновления и удаления ненужных данных. Листинг 14.8 содержит класс RandomAccessRecord, используемый в рассматриваемых в главе приложениях об- работки файлов с произвольным доступом. Данный класс также принадлежит DLL-библиотеке BankLibrary, т. е. является частью проекта, содержащего классы BankUiForm и Record. (При добавлении класса RandomAccessRecord в проект, содержащий BankUiForm и Record, проект следует скомпоновать заново.) Листинг 14.8. Обработка файлов с произвольным доступом 1 // Листинг 14.8: RandomAccessRecord.cs 2 // Класс-ваписей данных для прилож. с произвольным доступом к файлам 3 4 using System; 5 6 public class RandomAccessRecord 7 { 8 // длина элементов firstName и lastName 9 private const int CHARARRAY_LENGTH = 15; 10
492 Глава 14 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 private const int SIZE_OF_CHAR =2; private const int SIZE_OF_CHAR = 4; private const int SIZE_OF_CHAR =8; Il длина записи public const int SIZE = SIZE_OF_INT32 + ? * (SIZE_OF_CHAR * CHAR_ARRAY_LENGTH) + SIZE_OF_DOUBLE; // данные записи private int account; private char[] firstName - new char[CHAR_ARRAY_LENGTH]; private char[] lastName = new char[CHAR_ARRAY_LENGTH]; private double balance; // конструктор задает элементам значения по умолчанию public RandomAccessRecord() : this(0, 0.0) { ) // перегруженный конструктор задает элементам значения параметров public RandomAccessRecord(int accountValue, string firstNameValue, string lastNameValue, double balancevalue) { Account = accpuntValue; FirstName - firstNameValue; LastName = lastNameValue; Balance = balancevalue; } // конец конструктора // свойство Account public int Account { get { return account; set { account = value; } } // конец свойства Account // свойство FirstName public string FirstName { get { return new string(firstName); } set { 11 определение длины параметра string int stringSize = value.Length; // строковое представление firstName string firstNameString « value;
Файлы и потоки 493 73 74 75 // добавление пробелов в параметр string, если он короткий if (CHAR_ARRAY_LENGTH >= stringsize) { 16 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 firstNameString = value + new string(' ', CHAR_ARRAY_LENGTH — stringSize); } else { // удаление символов из параметра string, если он длинный firstNameString = value.Substring(0, CHAR_ARRAY_LENGTH) ; } 7/ преобразование параметра string в массив char firstName = firstNameString.ToCharArray(); ) -// конец set ) // конец свойства FirstName // свойство LastName public string LastName { get { return new string(lastName); ) set { // определение длины параметра string int stringSize = value.Length; // строковое представление lastName string lastNameString =- value; // добавление пробелов в параметр string, если он короткий if (CHAR_ARRAY_LENGTH >= stringSize) { lastNameString value + new string(’ ’, CHAR-ARRAY_LENGTH - stringSize); } else { // удаление символов из параметра string, если он длинный lastNameString = value.Substring(0, CHAR_ARRAY_LENGTH); ) // преобразование параметра string в массив char lastName = lastNameString.ToCharArray(); } // конец set ) // конец свойства LastName // свойство Balance public double Balance < get { return balance; }
494 Глава 14 137 set 138 { 139 balance = value; 140 } 141 142 } // конец свойства Balance 143 144 } // конец класса RandomAccessRecord Подобно классу Record (см. листинг 14.4), класс RandomAccessRecord содержит элементы данных типа private (строки 20—23) для сохранения информации записей, два конструктора для задания этим элементам значений по умолчанию и параметрических, соответственно, а также свойства для доступа к этим элементам. Впрочем, класс RandomAccessRecord не содержит атрибута [Serializable] перед определением класса. Этот класс не сериализуется, потому что C# не предоставляет во время прогона программы средств получения данных о раз- мере объекта. Это означает, что при сериализации данного класса нельзя гарантировать фиксированной длины записей. Вместо сериализации класса фиксируется длина элементов данных типа private, после чего эти данные запи- сываются в файл как поток байтов. Для фиксирования длины средства доступа set свойства FirstName (стро- ки 58—91) и LastName (строки 94—127) подтверждают, что элементы firstName и lastName являются массива- ми типа char, состоящими точно из 15 символов. Каждое средство доступа set принимают в качестве аргумента строку, представляющую имя и фамилию. Если параметр string содержит менее 15 символов, тогда средство доступа set свойства копирует значения string в массив char, после чего заполняют остаток пробелами. Если параметр string содержит более 15 символов, тогда средство доступа set сохраняет в массиве char только пер- вые 15 символов параметра string. В строках 16 и 17 объявляется константа size, задающая длину записи. Каждая запись содержит account (int — 4 байта), firstName и lastName (два 15-элементных массива типа char, где каждый символ char занимает два байта; всего получается 60 байтов) и balance (double — 8 байтов). В данном примере каждая запись (т. е. четы- ре элемента данных типа private, которые программа будет считывать в файлы и осуществлять в них запись) занимает 72 байта (4 байта + 60 байтов + 8 байтов). 14.8. Создание файла с произвольном доступом Рассмотрим следующую постановку задачи для приложения обработки кредитов. Предположим, что следует создать программу обработки транзакций с возможностью сохранения максимум 100 записей фиксированной длины для компании, которая может иметь максимум 100 клиентов. Каждая запись состоит из номера счета (он выполняет роль ключевой записи), фамилии, имени клиента и сальдо. Программа может обновлять, создавать и удалять счета. В следующих нескольких разделах представлены методики, необходимые для создания программы обработки кредитов. Сейчас рассмотрим приложение для создания файла с произвольным доступом, используемого в лис- тингах 14.10 и 14.11, а также в программе обработки транзакций для манипуляций с данными. Класс CreateRandomAccessFile (листинг 14.9) создает файл с произвольной выборкой. Листинг 14.9. Создание файлов с произвольным доступом ЯИНюИВЗИКж21ИИЗЯявЯйяЕЕзЯиЯЯвЯИ^^Ег ....... 1 // Листинг 14.9: CreateRandomAccessFile.cs 2 " // Создание файла с произвольным доступом 3 4 // пространство имен C# 5 using System; б using System.IO; 7 using System.Windows.Forms; 8 9 // пространство имен Deitel 10 using BankLibrary; 11
Файлы и потоки 495 12 13 14 15 16 17 18 19 20 20а 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 class CreateRandomAccessFile { // количество записей для сохранения на диске private const int NUMBER_OF_RECORDS = 100; [STAThread] static void Main(string[] args) { // создание файла с произвольной выборкой // с последующим сохранением на диске CreateRandomAccessFile file = new CreateRandomAccessFile(); file.SaveFile(); } 11 конец метода Main // сохранить записи на диск private void SaveFile() { // запись для сохранения на диске f RandomAccessRecord blankRecord = new RandomAccessRecord(); // поток, по которому упорядочиваемые данные записываются в файл Filestream fileOutput = null; // поток для записи байтов в файл Binarywriter binaryoutput e nul;; // создание диалогового окна, обеспечивающего сохранение файла SaveFileDialog fileChooser * new SaveFileDialog(); DialogResult result = fileChooser.ShowDialogO; // получение имени файла от пользователя string filename = fileChooser.FileName; // выход из обработчика событий при нажатии кнопки Cancel if (result = DialogResult.Cancel) return; // сообщение, если пользователь указал некорректное имя файла ’ if (filename ** " " I f filename « null) MessageBox.Show("Invalid File Name", "Error", MessageBoxButtons.OK, MessageBoxIcon.Error); else { // ввод записей в файл try { // создание класса Filestream для сохранения записей fileOutput = new Filestream(fileName, FileMode.Create, FileAccess.Write); // задание длины файла fileOutput.SetLength(RandomAccessRecord.SIZE * NUMBER_OF_RECORDS); // создание объекта для записи байтов в файл binaryoutput = new Binarywriter(fileOutput); // ввод пустых записей в файл for (int i « О; i < NUMBER_OF_RECORDS; i++) { // установка указателя в файле fileOutput.Position = i * RandomAccessRecord.SIZE;
496 Глава 14 75 // ввод пустой записи в файл 7 6 binaryoutput.Write(blankRecord.Account); 77 • binaryoutput.Write(blankRecord.FirstName); 7 8 binaryoutput.Write(blankRecord.LastName); 7 9 binaryoutput.Write(blankRecord.Balance); 80 } 81 82 // уведомление пользователя об успехе 83 MessageBox.Show("File Created", "Success", 84 MessageBoxButtons.OK, MessageBoxIcon.Information); 85 } 86 87 // обработка события, если во время записи возникнет ошибка 88 catch(lOException) 89 { 90 // уведомление пользователя об ошибке 91 MessageBox.Show("Cannot write to file", "Error", 92 MessageBoxButtons.OK, MessageBoxIcon.Error); 93 } 94 ) 95 96 // закрытие Filestream 97 if (fileOutput = null) 98 fileOutput.Close(); 99 100 // закрытие Binarywriter 101 if (binaryoutput — null) 102 binaryOutput.Close(); 103 104 ) // конец метода SaveFile 105 } // конец класса CreateRandomAccessFile Приложение начинается с метода Main, создающего файл с произвольным доступом и вызывающего определен- ный пользователем метод SaveFile (строки 27—104). Метод SaveFile заполняет файл 100 копиями значений по умолчанию (пустыми) ДЛЯ элементов данных типа private: account, firstName, lastName и balance класса RandomAccessRecord. В строках 39 и 40 создается и отображается объект saveFiieDialog, позволяющий пользо- вателю задать файл, в который программа будет записывать данные (рис. 14.19 и 14.20). С помощью этого фай- ла в строках 59 и 60 создается класс Filestream. Обратите внимание, что в строке 60 передается константа FileMode.Create, либо создающая заданный файл, если его не существует^либо п<>д «сияющая указанный файл, если он существует. В строках 63 и 64 задается длина класса Filestream, равная размеру отдельного класса RandomAccessRecord (полученного через константу RandomAccessRecord. SIZE), умноженному на количество записей, которые необходимо скопировать (это количество получено через константу number_of_records в строке 15; задается значение 100). Рис. 14.19. Диалоговое окно сохранения файла Рис. 14.20. Сообщение о создании файла Теперь необходимо средство записи байтов в файл. Класс Binarywriter пространства имен System, ю предос- тавляет методы записи байтов в потоки. Конструктор Binarywriter принимает в качестве аргумента ссылку на
Файлы и потоки 497 экземпляр класса System, io.Stream, через который класс Binarywriter может записывать байты. Класс Filestream предоставляет методы для записи потоков в файлы и наследуется из класса stream, поэтому объект Filestream МОЖНО передать В качестве аргумента В конструктор Binarywriter (строка 67). Теперь Binarywriter можно использовать для записи байтов непосредственно в файл. Строки 70—80 заполняют файл 100 копиями пустых значений записей (т. е. значениями по умолчанию для эле- ментов данных типа private класса RandomAccessRecord). В строке 73 изменяется положение указателя файла для обозначения местоположения в файле, в которое будет введена следующая пустая запись. Теперь, когда работа ведется с файлом произвольной выборки, указатель файла необходимо установить явно с помощью свойства Position объекта Filestream. Данное свойство принимает в качестве аргумента значение long, описы- вающее местоположение указателя относительно начала файла; в данном примере указатель установлен так, что он "выдвигается" на число байтов, равное размеру записи (полученному константой RandomAccessRecord.size). В строках 76—79 вызывается метод Write объекта Binarywriter для записи данных. Метод write — это пере- груженный метод, принимающий в качестве аргумента любой тип простых данных с последующей записью это- го типа в поток байтов. После окончания выполнения цикла loop в строках 97—102 закрываются объекты Filestream и Binarywriter. 14.9. Произвольная запись данных в файл с произвольным доступом Теперь, когда создан файл с произвольным доступом, используется класс WriteRandomAccessFileForm (лис- тинг 14.10) для записи данных в этот файл. При нажатии пользователем кнопки Open File (рис. 14.21) програм- мой активизируется метод openButton_ciick (строки 41—84), отображающий диалоговое окно OpenFileDialog для указания файла (рис. 14.22) с упорядоченными данными (строки 45 и 46); после этого программа использу- ет упорядоченный файл для создания объекта Filestream с доступом только для записи (строки 65 и 66). В строке 69 используется ссылка Filestream для создания объекта класса Binarywriter, что позволит програм- ме записывать байты в файлы. Здесь применяется тот же принцип, что и при работе с классом CreateRandomAccess (см. ЛИСТИНГ 14.9). Листинг 14.10. Ввод записей в файлы с произвольной выборкой » ....... ........... .............____...... - _~______ 1 // Листинг 14.10: WriteRandomAccessFile.cs 2 // Запись данных в файл с произвольной выборкой 3 4 // пространство имен C# 5 using System; 6 using System.Drawing; 7 using System.Collections; 8 using System.ComponentModel; 9 using System.Windows.Forms; 10 using System.Data; 11 using System.IO; 12 13 // пространство имен Deitel 14 using BankLibrary; 15 16 public class WriteRandomAccessFileForm : BankUiForm 17 { 18 private System.Windows.Forms.Button openButton; 19 private System.Windows.Forms.Button enterButton; 20 21 private System.ComponentModel.Container components = null; » 22 23 // количество RandomAccessRecords для записи на диск 24 private const int NUMBER_OF_RECORDS = 100; 25 26 // поток, по которому данные записываются в файл 27 private Filestream fileOutput; 28 32 Зак. 3333
498 Глава 14 29 // поток для записей байт в файл 30 private Binarywriter binaryoutput; 31 32 [STAThread] 33 static void Main() 34 { 35 Application.Run(new WriteRandomAccessFileFormO); 36 } 37 38 // код, сгенерированный Visual Studio .NET 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 64a 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 // активизируется при нажатии пользователем кнопки Open private void openButton_Click( object sender, Syste.EventArgs e) ( // создание диалогового окна, обеспечивающего открытие файла OpenFileDialog fileChooser = new OpenFileDialog(); DialogResult result = fileChooser.ShowDialog(); // получение от пользователя имени файла string filename = fileChooser.FileName; // выход из обработчика событий при нажатии Cancel if (result = DialogResult.Cancel) return; // сообщение при указании некорректного имени файла if (filename == " " II fileName == null) MessageBox.Show("Invalid File Name", "Error", MessageBoxButtons.OK, MessageBoxIcon.Error); else { // открытие файла, если он существует try { // создание класса Filestream // для сохранения записей fileOutput = new FileStream(filename, FileMode.Open, FileAccess.Write); // создание объекта для записи байт в файл binaryoutput = new Binarywriter(fileOutput); // отключение кнопки Open и активизация кнопки Enter openButton.Enabled = false; enterButton.Enabled = true; ) // уведомление пользователя, если файл не существует catch(lOException) 4 MessageBox.Show("File Does Not Exist", "Error", MessageBoxButton.OK, MessageBoxIcon.Error); } ) } // конец метода openButton_Click // активизируется при нажатии кнопки Enter private void enterButton_Click( object sender, System.EventArgs e) { // строковый массив значений текстовых окон string[] values = GetTextBoxValues();
Файлы и потоки 499 93 // определение наличия значений в поле счета 94 if (values[(int)TextBoxIndices.ACCOUNT] != " ") 95 { 96 // ввод записи в файл в нужную позицию 97 try 98 { 99 // получение из текстового поля значение номера счета 100 int accountNumber = Int32/Parse( 101 values[(int)TextBoxIndices.ACCOUNT]); 102 103 // определение действительности accountNumber 104 if (accountNumber > 0 && 105 accountNumber <= NUMBER_OF_RECORDS) „ ,106 { 107 // перемещение указателя позиции файла 108 fileOutput.Seek((accountNumber — 1) * 109 RandomAccessRecord.SIZE, SeekOrigin.Begin); 110 111 // запись данных в файл 112 binaryoutput.Write(accountNumber); 113 binaryoutput.Write( 114 values[(int)TextBoxIndices.FIRST]); 115 binaryoutput.Write( 116 values[(int)TextBoxIndices. LAST]); 117 binaryoutput.Write(Double.Parse(values[ 118 (int)TextBoxIndices.BALANCE])); 119 } 120 else 121 { 122 // уведомление о некорректном номере счета 123 MessageBox.Show("Invalid Account Number", "Error", 124 MessageBoxButtons.OK, MessageBoxIcon.Error); 125 } 126 } 127 128 // обработка исключения формата номера 129 catch(FormatException) 130 { -г 131 // сообщение при ошибке во. время'форматирования номеров 132 MessageBox.Show("Invalid Balance", "Error", 133 MessageBoxButtons.OK, MessageBoxIcon.Error); 134 } 135 } 136 137 * ClearTextBoxes (); // сброс значений текстовых окон 138 139 ) // конец метода enterButton_Click 140 1 141 } // конец класса WriteRandomAccessFileForm Пользователь вводит значения в текстовые окна номера счета, имени, фамилии и сальдо. При нажатии кнопки Enter (рис. 14.23) программа активизирует метод eaterButton_ciick (строки 87—139), записывающий данные в текстовые поля файла. В строке 91 для извлечения данных вызывается метод GetTextBoxValues (предостав- ляемый базовым классом BankuiForm). Строки 104—105 определяют, содержится ли действительная информа- ция в текстовом поле Account Number (т. е. номер счета в диапазоне 1—100). '' А ‘ I Класс WriteRandomAccessFileForm должен определять местоположение в классе Filestream, в которое следует вставлять данные из текстовых полей. В строках 108 и 109 используется метод Seek объекта Filestream для нахождения точного местоположения в файле. В данном случае методом seek задает по- ложение указателя файла объекта Filestream для размещения байтов, рассчитанное по формуле (accountNumber - 1) х RandomAccessRecord.SIZE. Из-за того что диапазон номеров счетов— 1—100, при расчете местоположения байта записи из номера счета вычитается единица. Например, при использовании метода Seek указатель файла первой записи устанавливается на нулевой байт файла (в начало файла). Второй аргумент ме-
500 Глава 14 тода Seek является элементом перечисления seekorigin и указывает местоположение, с которого данный метод должен начать поиск. Константа const Seekorigin.Begin используется для того, чтобы метод осуществлял по- иск относительно начала файла. После определения программой местоположения в файле, в которое должна быть помещена запись, в строках 112—118 эта запись вводится в файл с помощью объекта Binarywriter (рас- сматривается в предыдущем разделе). □ CreateRandomAccessFile Л. . L.', s................... □bln □obj □App.ico ltJAssemblylnfo.es CreateRandomAecessFle.es QSCreateRandomAccessFie.csproj ijiCreateRandomAccessFie.csproj.user JlcreateRandomAccessFie.sln yjcreateRandomAccessFie.suo Рис. 14.21. Вид формы для ввода данных |< >pei» Fileitoftype [ jereditdat jr] Рис. 14.22. Диалоговое окно открытия файла Рис. 14.23. Ввод данных для рассматриваемого приложения: а — ввод первой записи; б — ввод второй записи; в — ввод третьей записи; а — ввод четвертой записи; д — ввод пятой записи 14.10. Последовательное считывание данных из файла с произвольным доступом В предыдущих разделах был создан файл с произвольной выборкой, в который записаны данные. В этом разде- ле рассматривается программа (листинг 14.11), с помощью которой файл открывается, из него считываются данные с отображением записей, содержащих данные (т. е. записи, в которых номер счета не равен нулю). В данной программе имеется дополнительное преимущество — читателям предлагается обнаружить его самим; в конце раздела авторы обратят на него особое внимание.
Файлы и потоки 501 Я"? .’rr ...“ —............ = Листинг *14.11. Последовательное считывание записей из файла с произвольной выборкой 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 // Листинг 14.11: ReadRandomAccessFile.cs // Считывание и отображение файла с произвольным доступом // пространство имен C# using System; using System.Drawing; using System.Collections; using System.ComponentModel; using System.Windows.Forms; using System.Data; using System.IO; // пространство имен Deitel using BankLibrary; public class ReadRandomAccessFileForm : BankuiForm { private System.Windows.Forms.Button openButton; private System.Windows.Forms.Button nextButton; private System.ComponentModel.Container components = null; // поток, по которому данные считываются из файла private Filestream fileinput; // поток для считывания байтов из файла private BinaryReader binaryInput; // указатель текущей записи для отображения private int currentRecordlndex; [STAThread] static void Main() { Application.Run(new ReadRandomAccessFileForm()); } // код, сгенерированный Visual Studio .NET // активизируется при нажатии пользователем кнопки Open private void openButton_Click( object sender, Syste.EventArgs e) { // создание диалогового окна, обеспечивающего открытие файла OpenFileDialog fileChooser • new OpenFileDialog(); DialogResult result = fileChooser.ShowDialogO; // получение от пользователя имени файла string filename = fileChooser.FileName; // выход из обработчика событий при нажатии кнопки Cancel if (result — DialogResult.Cancel) return; 11 сообщение при указании пользователем некорректного имени файла if (filename = " " I I fileName == null) MessageBox.Show("Invalid File Name", "Error", MessageBoxButtons.OK, MessageBoxIcon.Error);
502 Глава 14 59 else 60 { 61 // создание класса FileStream для получения доступа для чтения файла 62 fileinput = new FileStream(filename, 63 FileMode.Open, FileAccess.Read); 64 65 // FileStream для считывания классом Binarywriter байтов из файла 66 binaryinput = new BinaryReader(fileinput); 67 68 openButton.Enabled = false; // отключение кнопки Open File 69 nextButton.Enabled » true; // активизация кнопки Next 70 71 currentRecordlndex =0; 72 ClearTextBoxes(); 73 } 74 75 ] // конец метода openButton_Click 76 77 7/ активизируется при нажатии пользователем кнопки Next 78 private void nextButton_Click( 79 object sender, System.EventArgs e) 80 { 81 // запись для сохранения данных файла 82 RandomAccessRecord record = new RandomAccessRecord(); 83 84 // считывание записи и сохранение данных в текстовых полях 85 try 86 { 87 string[] values; // для сохранения значений текстовых полей 88 89 // получение следующей доступной в файле записи 90 * while (record.Account — 0) 91 . { 92 // установка указателя файла на следующую запись в файле 93 fileInput.Seek( 94 currentRecordlndex * EandomAccessRecord.SIZE, 0); 95 96 currentRecordlndex += 1; 97 98 // считывание данных из записи 99 record.Account = binaryinput.Readlnt32(); 100 record.FirstName = binaryInput.Readstring(); 101 record.LastName = binaryInput.ReadString(); 102 record.Balance = binaryinput.ReadDouble(); 103 } 104 105 //• сохранение значений, записи во временном строковом массиве 106 values = new string!] { 107 record .Account.Tostring(); 108 record.FirstName, 109 record.LastName, 110 record.Balance.ToStringO }; 111 112 // копирование значений строкового массива в текстовые поля 113 SetTextBoxValues(values); 114 } 115 116 // обработка исключения при отсутствии записей в файле 117 catch(lOException) 118 ! 119 // закрытие потоков при отсутствии записей в файле 120 fileinput.Close(); 121 binaryinput.Close(); 122
Файлы и потоки 503 123 openButton.Enabled = true; // активизация кнопки Open File 124 nextButton.Enabled = false; // отключение кнопки Next 125 ClearTextBoxes(); 126 127 // сообщение об отсутствии записей в файле 128 MessageBox.Show("No more records in file", " ", 129 MessageBoxButtons.OK, MessageBoxIcon.Information); 130 } 131 132 } конец метода nextButton_Click 133 134 } // конец класса ReadRandomAccessFileForm При нажатии пользователем кнопки Open File (рис. 14.24) класс ReadRandomAccessFileForm активизирует метод openButton_Click (строки 41—75), отображающий объект OpenFileDialog, указывающий файл, из которого следует считывать данные (рис. 14.25). В строках 62 и 63 создается объект Filestream, открывающий этот файл только для чтения. В строке 66 создается экземпляр класса BinaryReader, считывающий байты из потока. Объ- ект Filestream передается в качестве аргумента в конструктор BinaryReader, позволяя, таким образом, классу BinaryReader считывать байты из файла. Рис. 14.24. Вид формы рассматриваемого приложения Рис. 14.25. Диалоговое окно открытия файла При нажатии пользователем кнопки Next (рис. 14.26) в программе вызывается метод nextButton_ciick (стро- ки 78—132), считывающий следующую запись в файле. В строке 82 создается объект RandomAccessRecord для сохранения данных записи из файла. В строках 90—114 определяется цикл while, считывающий данные из файла до тех пор, пока не достигнет записи, имеющей ненулевое значение номера счета (о — первоначальное значение номера счета). В строках 93 и 94 вызывается метод Seek объекта Filestream, который переносит ука- затель файла в место, где должна считываться запись. Для решения этой задачи метод Seek использует объект int currentRecordlndex, сохраняющий количество считанных записей. В строках 99—102 используется объект BinaryReader для сохранения данных файла в объекте RandomAccesRecord. Помните, что класс Binarywriter имеет перегруженные методы Write для записи данных, но не предоставляет перегруженных методов Read для считывания данных. Это означает, что для считывания целочисленного значения следует использовать метод Readint32, для считывания строкового значения — метод Readstring и метод ReadDoubie — для считывания данных типа double. Обратите внимание, что порядок вызова этих методов должен соответствовать порядку, в котором объект Binarywriter записал каждый тип данных. Когда класс BinaryReader считывает корректный номер счета (т. е. ненулевое значение), выполнение цикла останавливается, и с помощью строк 106—113 в тек- стовых полях отображаются данные записи. После отображения программой всех записей метод Seek выдает исключение lOException (потому что метод Seek делает попытку размещения указателя файла в место, находя- щееся за указателем окончания файла). Блок catch (строки 117—130) обрабатывает это исключение закрытием класса FilrStream и объектов BinaryReader (строки 120 и 121) и уведомлением пользователя о том, что больше записей не существует (строки 128 и 129) — рис. 14.27. Что же за дополнительное преимущество, о котором шла речь в начале раздела? Если посмотреть на GUI во время выполнения программы, можно заметить, что программа отображает записи в порядке возрастания номе- ров счетов! Это — простое следствие использования методик прямого доступа при сохранении записей в файле. Повышенное быстродействие достигается благодаря тому, что файл имеет достаточно большой размер, чтобы
504 Глава 14 хранить любую возможную запись, которую может создать пользователь. Конечно, при этом большую часть времени такой файл будет "полупустым", занимая место на диске. Но здесь применяется еще один пример ком- промисса между занимаемым местом и временем, используя большие объемы пространства, можно разработать более оперативный алгоритм сортировки. Рис. 14.26. Отображение данных: а — второй записи: б — пятой записи; в — первой записи; г — четвертой записи; б — третьей записи Рис. 14.27. Сообщение о достижении конца файла 14.11. Учебный пример: программа обработки транзакций Теперь с помощью файла с произвольным доступом попробуем создать более сложную программу обработки транзакций (листинги 14.12—14.17) по принципу "мгновенного доступа". В этой программе хранится банков- ская учетная информация. Пользователи могут добавлять новые счета, обновлять существующие и удалять не- нужные счета. Сначала рассмотрим поведение обработки транзакций (т. е. класс, обеспечивающий добавление, обновление и удаление счетов). Затем обсудим GUI, в котором содержатся окна с информацией о счетах, даю- щие пользователю возможность активизации поведения обработки транзакций в программном приложении. 14.11.1. Поведение обработки транзакций В данном контрольном примере создается класс Transaction (листинг 14.12), выполняющий роль доверенного лица на обработку всех транзакций. Вместо обеспечения поведения обработки транзакций объекты в данном приложении используют экземпляр класса Transaction для получения нужной функциональности. С помощью "доверенного" объекта поведение обработки транзакций можно инкапсулировать только в одном классе с тем, чтобы другие классы приложения могли его многократно использовать. Более того, если потребуется изменить поведение, то изменения затронут только "доверенный" объект (т. е. класс Transaction), но не поведение всех классов, его использующих.
Файлы и потоки Листинг 14.12. Класс транзакции записей для контрольного примера программы обработки банковских транзакций ” Л'- 505 xl 1 2 3 4 5 б 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 35а 36 37 38 38а 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 55 56 57 // Листинг 14.12: Transaction.cs // Обработка транзакций записей // пространство имен C# using System; using System.10; using System.Windows.Forms; // пространство имен Deitel using BankLibrary; public class Transaction { // количество записей для сохранения на диске private const int NUMBER_OF_RECORDS = 100; // поток, по которому данные перемещаются из файла и в файл private Filestream file; // поток для считывания байтов из файла private BinaryReader binaryinput; // поток для записи байтов в файл private Binarywriter binaryoutput; // создание/открытие файла, содержащего пустые записи public void OpenFile(string filename) { // ввод пустых записей в файл try { // создание класса Filestream из нового или существующего файла file = new Filestream(filename, FileMode.OpenOrCreate); // использование класса Filestream для считывания // классом BinaryReader байтов из файла binaryinput = new BinaryReader(file); // использование класса Filestream для записи // классом Binarywriter байтов в файл binaryoutput = new Binarywriter (file); // определение, был ли файл создан if (file.Length = 0) { // запись для ввода в файл RandomAccessRecord blankRecord = new RandomAccessRecord() ; // новая запись может содержать NUMBER_OF_RECORDS записей file.SetLength(RandomAccessRecord.SIZE * NUMBER_OF_RECORDS); // ввод пустых записей в файл for (int i = 0; i < NUMBER_OF_RECORDS; i++) { // перемещение указателя файла // в следующее положение file.Position = i * RandomAccessRecord.SIZE;
506 Глава 14 58 // ввод в файл пустой записи 59 binaryoutput.Write(blankRecord.Account); 60 binaryoutput.Write(blankRecord.FirstName); 61 binaryoutput.Write(blankRecord.LastName); 62 binaryOutput.Write(blankRecord.Balance); 63 } 64 } 65 } 66 67 // сообщение об ошибке во время ввода пустых записей 68 catch(lOExcept ion) 69 { 70 MessageBox.Show("Cannot create file", "Error", 71 MessageBoxButtons.OK, MessageBoxIcon.Error); 72 } 73 74 } // конец метода OpenFile 75 76 // извлечение записи, в зависимости от корректности счета 77 public RandomAccessRecord GetRecord(string accountValue) 78 { *79 // сохранение данных файла, относящихся к счету в записи 80 try 81 { 82 // запись для сохранения данных файла 83 RandomAccessRecord record = new RandomAccessRecord(); 84 85 // получение значения из поля счета 86 int accountNumber - Int32.Parse(accountValue); 87 88 // если счет некорректен, не считывать данные 89 if (accountNumber < 1 I I 90 accountNumber > NUMBER_OF_RECORDS/ 91 { 92 // указание номера счета в поле счета записи 93 гecord.Account = accountNumber; 94 ) 95 96 // получение данных из файла при корректном счете 97 else 98 { 99 // нахождение позиции в файле, в котором существует запись 100 file.Seek((accountNumber — 1) * 101 RandomAccessRecord.SIZE, 0); 102 103 Il считывание данных из записи 104 record.Account - binaryinput.Readlnt32(); 105 record.FirstName = binaryinput.Readstring(); 106 record.LastName = binaryInput.Readstring(); 107 record.Balance = binaryInput.ReadDouble(); 108 ) 109 110 return record; 111 ) 112 113 // уведомление пользователя об ошибке во время считывания 114 catch(lOException) 115 { 116 MessageBox.Show("Cannot read file", "Error", 117 MessageBoxButtons.OK, MessageBoxIcon.Error); 118 } 119
Файлы и потоки 507 120 return null; 121 122 } // конец метода GetRecord 123 124 // добавление записи в файл в позицию, определенную номером счета 125 public bool AddRecord( 126 RandomAccessRecord record, int accountNumber) 127 { 128 // ввод записи в файл 129 try 130 { 131 // перемещение указателя файла в нужное положение 132 file.Seek((accountNumber — 1) * 133 RandomAccessRecord.SIZE, 0); 134 135 // запись данных в файл 136 binaryoutput.Write(record.Account); 137 binaryoutput.Write(record.FirstName); 138 binaryoutput.Write(record.LastName); 139 binaryoutput.Write(record.Balance); 140 } 141 142 // уведомление пользователя об ошибке во время записи 143 catch(lOException) 144 { 145 MessageBox.Show("Error Writing To File", "Error", 146 MessageBoxButtons.OK, MessageBoxIcon.Error); 147 148 return false; // сбой 149 } 150. 151 return true; // успешно 152 153 ) // конец метода AddRecord 154 155 } // конец класса Transaction Класс Transaction содержит методы OpenFile, GetRecord и AddRecord. В методе OpenFile (строки 27—24) ис- пользуется константа FileMode.OpenCreate (строка 33) для создания объекта Filestream либо из существующе- го файла, либо из еще несозданного. Этот объект используется в строках 36—39 для создания объектов BinaryReader и Binarywriter с целью считывания байтов из файла и их записи в файл соответственно. Если файл новый, тогда в строках 42—64 объект Filestream заполняется пустыми записями. Читатели должны пом- нить, что данная методика применялась в разд. 14.8. Методом GetRecord (строки 77—122) возвращается запись, ассоциированная с параметром номера счета. В строке 83 создается объект RandomAccessRecord, который будет сохранять данные файла. Если параметр счета корректен, тогда в строках 100 и 101 вызывается метод Seek объекта Filestream, использующий этот параметр для определения положения в файле указанной записи. После этого в строках 104—107 вызываются методы Readint32, Readstring и ReadDouble объекта BinaryReader для сохранения данных файла в объекте RandomAccessRecord. В строке 110 объект RandomAccessRecord возвращается. Эти методики применялись в разд. 14.10. С помощью метода AddRecord (строки 125—153) запись вводится в файл. В строках 132 и 133 вызывается метод Seek объекта Filestream, использующий параметр номера счета для определения положения в файле, в которое следует вставить запись. В строках 136—139 вызываются перегруженные методы Write объекта Binarywriter для записи в файл данных объекта RandomAccessRecord. Эти методики применялись в разд. 14.9. Обратите вни- мание, что если во время добавления записи возникла ошибка (т. е. либо Filestream, либо Binarywriter выдают исключение lOException), то в строках 145 и 146 пользователю направляется уведомление об ошибке и возвра- щается false (сбой). 14.11.2. GUI обработчика транзакций В GUI данной программы используется многодокументный интерфейс. Класс TransactionProcessorForm (лис- тинг 14.13) является окном-родителем и содержит соответствующие окна-потомки: startDialogForm (лис- тинг 14.14), NewDialogForm (листинг 14.15), UpdateDialogForm (листинг 14.16) И DeleteDialogForm (лис-
508 Глава 14 тинг 14.17). Окно StartDialogForm дает пользователю возможность открытия файла, содержащего информацию о счете и обеспечивает доступ к внутренним окнам NewDialogForm, UpdateDialogForm и DeleteDialogForm. Эти окна позволяют пользователям обновлять, создавать и удалять записи соответственно. 1 // Листинг 14.13: Transactionprocessor.cs 2 // Многодокументное окно-родитель программы обработки транзакций 3 4 using System; 5 using System.Drawing; 6 using System. Confections; 7 using System.ComponentModel; 8 using System.Windows.Forms; 9 using System.Data; 10 11 public class TransactionProcessorForm 12 : System.Windows.Forms.Fora 13 { 14 private System.ComponentModel. Container components = null; 15 private System.Windows.Dorms.MdiClient WdiClientl; 16 17 // ссылка на StartDialog 18 private StartDialogForm StartDialog; 19 20 // конструктор 21 public TransactionProcessorForm () 22 { 23 // необходим для поддержки Windows Form Designer 24 IntializeComponent(); 25 26 StartDialog = new StartDialogForm(); 27 startDialog.MdiParent e this; 28 StartDialog.Show(); 29 } 30 31 [STAThread] 32 static void Main () 33 { 34 Application.Run(new TransactionProcessorForm()); 35 } 36 37 // код, сгенерированный Visual Studio .NET 38 39 } // конец класса TransactionProcessorForm Первоначально TransactionProcessorForm отображает объект StartDialogForm; это окно обеспечивает пользо- вателя разнообразными возможностями. В нем имеются четыре кнопки, с помощью которых пользователь мо- жет создать или открыть файл, создать запись, обновить или удалить существующую запись (рис. 14.28). Перед изменением записей пользователь сначала должен создать или открыть файл. При нажатии пользовате- лем кнопки New/Open File программа вызывает метод openButton_ciick (строки 42—100), открывающий файл, используемый для внесения изменений в записи. В строках 42—62 отображается объект OpenFileDialog для указания файла (рис. 14.29), из которого будут считываться данные, после чего этот файл используется для соз- дания объекта Filestream. Обратите внимание, что строка 52 устанавливает свойство CheckFileExits объекта OpenFileDialog на false. Это дает пользователю возможность создать файл, если он не существует. Если бы данное свойство было равно true (значение по умолчанию), тогда диалоговое окно уведомило бы пользователя, что указанный файл не существует; таким образом, пользователь оказался бы избавленным от необходимости создания файла. Если пользователь указывает имя файла, то в строке 73 листинга 14.14 создается объект класса Transaction (листинг 14.12), выполняющий роль "уполномоченного" на создание, считывание записей из файлов с произ- вольной выборкой и ввода записей в такие файлы. В строке 74 вызывается метод OpenFile класса Transaction, который создает или открывает указанный файл, в зависимости от того, существует он или нет.
Файлы и потоки 509 Рис. 14.28. Стартовое окно Рис. 14.29. Диалоговое окно создания/открытия файла 1 // Листинг 14.14: StartDialog.cs 2 // Первоначальное отображаемое диалоговое окно. Имеет кнопки для 3 // создания/открытия файла, а также для добавления, обновления 4 //и удаления записей из файла 5 ' 6 // пространство имен C# 7 using System; 8 using System.Drawing; 9 using System.Collections; 10 using System.ComponentModel; 11 using System.Windows.Forms; 12 13 11 пространство имен Deitel 14 using BankLibrary; 15 16 public delegate void MyDelegate(); 17 18 public class StartDialogForm : System.Windows.Forms.Form 19 { 20 private System.Windows.Forms.Button updateButton; 21 private System.Windows.Forms.Button newButton; 22 private System.Windows.Forms.Button deleteButton; 23 private System.Windows.Forms.Button openButton; 24 25 private System.ComponentModel.Container components = null; 26 27 11 ссылка на диалоговое окно добавления записи '28 private NewDialogForm newDialog; 29 30 // ссылка на диалоговое окно обновления записи 31 private UpdateDialogForm updateDialog; 32 33 11 ссылка на диалоговое окно удаления записи 34 private DeleteDialogForm deleteDialog; 35 36 // ссылка на объект, управляющий транзакциями 37 private Transaction transactionproxy; 38 39 // код, сгенерированный Visual Studio .NET 40
510 Глава 14 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 ' 56 57 58 59 60 61 62 63 64 65 66 67 68 69 69а 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 // активизируется при нажатии кнопки New/Open File private void openButton__Click ( object sender, System.EventArgs e) { // диалоговое окно'создания или открытия файла OpenFileDialog fileChooser = new OpenFileDialog(); DialogResult result; string filename; // создание пользователем файла, если тот не существует fileChooser.Title = "Create File / Open File"; fileChooser.CheckFileExists = false; 11 отображение диалогового окна пользователю result = fileChooser.ShowDialog(); // выход из обработчика событий при нажатии кнопки Cancel if (result = DialogResult.Cancel) return; // получение от пользователя имени файла fileName = fileChooser.FileName; // сообщение при указании некорректного имени файла if (filename — " " || filename — null) MessageBox.Show("Invalid File Name", "Error", MessageBoxButtons.OK, MessetgeBoxIcon. Error); // открытие или создание файла, если пользователь // указал корректное имя файла else { // создание Transaction с указанным файлом transactionproxy = new Transaction(); transactionproxy.OpenFile(filename); // активизация кнопок GUI, за исключением New/Open File newButton.Enabled = true; updateButton.Enabled = true; deleteButton.Enabled = true; openButton.Enabled = false; // обработка диалогового окна для создания записей newDialog - new NewDialogForm(transactionproxy, new MyDelegate(ShowStartDialog)); // создание диалогового окна для обновления записей updateDialog = new UpdateDialogForm(transactionproxy, new MyDelegate(ShowStartDialog)); // создание диалогового окна для удаления записей deleteDialog = new DeleteDialogForm(transactionproxy, new MyDelegate(ShowStartDialog)); // установка StartDialog как MdiParent для диалоговых окон newDialog.MdiParent = this.MdiParent; updatoDialog.MdiParent = this.MdiParent; deleteDialog.MdiParent = this..MdiParent; } } // конец метода openButton_Click
Файлы и потоки 511 102 // активизация при нажатии кнопки New Record 103 private void newButton_Click( 104 object sender, System.EventArgs e) 105 { 106 Hide(); // скрыть StartDialog 107 newDialog.Show(); // показать NewDialog 108 109 } 11 конец метода newButton_Click 110 111 private void updateButton_Click( 112 object sender, System.EventArgs e) 113 { 114 Hide(); // скрыть StartDialog 115 updateDialog.Show(); // показать UpdateDialog 116 117 } // конец метода updateButton_Click 118 119 private void deleteBitton_Click( 120 object sender, System.EventArgs e) 121 { 122 Hide(); // скрыть StartDialog 123 deleteDialog.Show(); // показать DeleteDialog 124 125 } // конец метода deleteButton_Click 126 127 protected void ShowStartDialog() 128 { 129 Show(); 130 } 131 132 } // конец класса StartDialogForm С помощью класса StartDialogForm также создаются внутренние окна, дающие пользователю возможность создания, обновления и удаления записей. Для этих классов не используется конструктор по умолчанию, соз- данный Visual Studio .NET; вместо этого применяется перегруженный конструктор, принимающий в качестве аргументов объект Transaction и объект-делегат, осуществляющий ссылку на метод ShowStartDialog (стро- ки 127—130). В каждом окне-потомке используется второй параметр-делегат для отображения графического пользовательского интерфейса StartDialogForm при закрытии пользователем окна-потомка. В строках 83—92 создаются объекты классов UpdateDialogForm, NewDialogForm и DelegateDialogForm, служащие окнами- потомками. На рис. 14.30 представлено окно Start Dialog после выбора файла — заблокирована кнопка New/Open File. Рис. 14.30. Вид стартового окна после выбора файла Рис. 14.31. Нажатие кнопки New Record в стартовом окне При нажатии пользователем кнопки New Record в окне Start Dialog (рис. 14.31) программа активизирует метод newButton_C!ick класса StartDialogForm (листинг 14.14, строки 103—109), отображающий внутреннее окно NewDialogForm (листинг 14.15). Класс NewDialogForm дает пользователю возможность вносить записи в файл, открытый (созданный) классом StartDialogForm. В строке 25 листинга 14.15 объект MyDelegate определяется в качестве делегата метода, не возвращающего значения и не имеющего параметров; метод ShowStartDialog класса StartDialogForm (листинг 14.14, строки 127—130) соответствует этим требованиям. Класс NewDialogForm
512 Глава 14 принимает объект MyDelegate, делающий ссылку на этот метод как на параметр; следовательно, класс NewDialogForm может активизировать этот метод для отображения окна запуска, когда пользователь выходит из класса NewDialogForm. Классы JpdateDialogForm и DeleteDialogForm также принимают в качестве аргументов ссылки объекта MyDelegate, давая им возможность отображения startDialogForm после выполнения своих задач. После введения пользователем данных в текстовые поля и нажатия кнопки Save Record (рис. 14.32) программа активизирует метод saveButton_ciick (строки 51—66) для сохранения записи на диске. В строках 54—56 вызы- вается метод GetRecord объекта Transaction, который должен возвратить пустой класс RandomAccessRecord. Если метод GetRecord возвращает класс RandomAccessRecord, содержащий данные, тогда пользователь делает попытку подмены RandomAccessRecord новым классом. В строке 60 вызывается метод insertRecord типа private (строки 69—116). Если класс RandomAccessRecord пустой, тогда метод InsertRecord вызывает метод AddRecord объекта Transaction (строки 100 и 101), добавляющий в файл вновь созданный объект RandomAccessRecord. Если пользователь пытается подменить существующую Запись, тогда в строках 81—83 ему направляется уведомление о том, что запись уже существует, и выполняется возвращение из метода. При удачном добавлении записи на экране отображается соответствующее сообщение (рис. 14.33). Рис. 14.32. Нажатие кнопки Save Record после заполнения полей формы при вводе новых данных Рис. 14.33. Сообщение о создании новой записи 1 // Листинг 14.15: NewDialog.cs 2 // Вставка пользователем новой записи в файл 3 4 // пространства имен C# 5 using System; 6 using System.Drawing; 7 using System.Collections; 8 using System.ComponentModel; 9 using System.Windows.Forms; 10 11 // пространство имен Deitel 12 using BankLibrary; 13 14 public class NewDialogForm : BankuiForm 15 { 16 private System.Windows.Forms.Button saveButton; 17 private System.Windows.Forms.Button cancelButton; 18 19 private System.ComponentModel.Container components = null; 20 21 // ссыпка на объект, обрабатывающий транзакции 22 private Transaction transactionproxy; 23 24 // делегат для метода, отображающего предыдущее окно 25 private MyDelegate showPreviouslyWindow; 26
Файлы и потоки 513 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 7/ конструктор public NewDialogForm(Transaction transactionProxyValue, MyDelegate delegatevalue) < InitializeComponent(); showPreviousWindow = delegatevalue; 11 создание объекта, обрабатывающего транзакции transactionproxy transactionProxyValue; ) // код, сгенерированный Visual Studio .NET И активизация при нажатии пользователем кнопки Cancel private void cancelButton_Click( object sendee, System.EventArgs e) { Hide(); ClearTextBoxes(); showPreviousWindow(); } // конец метода cancelButton_Click // активизация при нажатии пользователем кнопки Save As private void saveButton_Click( object sender. System.EventArgs e) { . RandomAccessRecord record = transactionproxy.GetRecord(GetTextBoxValues() [(int)TextBoxIndices.ACCOUNT]); // добавление записи в файл, если она не пустая if (record != null) InsertRecord(record); Hide (); ClearTextBoxes(); showPreviousWindow(); } // конец метода saveButton_Click // вставить запись в файл в позицию, указанную accountNumber private void InsertRecord(RandomAccessRecord record) { // сохранение значений текстового поля в строковом массиве string[] textBoxValues = GetTextBoxValues(); // сохранение поля счета int accountNumber ж Int32.Parse( textBoxValues[(int)textBoxIndices.ACCOUNT]); // сообщение и возвращение, если запись счета не пустая if (record.Account != 0) { MessageBox.Show( "Record Already Exists or Invalid Number", "Error", MessageBoxButtons.OK, MessageBoxIcon.Error); return; ) // сохранение значений в записи record.Account « accountNumber; 33 Зак. 3333
514 Глава 14 90 record.FirstName = 91 textBoxValues[(int)TextBoxIndices.FIRST]; 92 record.LastName = 93 textBoxValues[(int)TextBoxIndices.LAST]; 94 record.Balance = Double.Parse( 95 textBoxValues[(int)TextBoxIndices.BALANCE]); 96 97 // добавление записи в файл 98 try 99 { 100 if (transactionproxy.AddRecord( 101 record, accountNumber _ = false) 102 103 return; // при ошибке 104 } 105 106 // сооощение об ошибке при несовпадении параметров 107 catch (FormatException) 108 { 109 MessageBox.Show("Invalid Balance", "Error", 110 MessageBoxButtons.OK, MessageBoxIcon.Error); 111 } 112 Z 113 MessageBox.Show("Record Created", "Success", 114 MessageBoxButtons.OK, MessageBoxIcon.Information); 115 116 } // конец метода InsertRecord 117 118 } // конец класса NewDialogForm Рис. 14.34. Нажатие кнопки Update Record в стартовом окне При нажатии пользователем кнопки Update Record в окне Start Dialog (рис. 14.34) программа активизирует метод UpdateButton Click класса StartDialogForm (листинг 14.14, строки 111—117), отображающий внутрен- нее окно UpdateDialogForm (листинг 14.16). Класс UpdateDialogForm дает пользователю возможность обновле- ния существующих в файле записей. 1 // Листинг 14.16: UpdateDialog.cs 2 // Обновление пользователями записей в файле 3 4 // пространства имен C# 5 using System; 6 using System.Drawing; 7 using System.Collections; 8 using System.ComponentModel; 9 using System.Windows.Forms; 10 11 // пространство имен Deitel 12 using BankLibrary; 13 14 public class UpdateDialogForm : BankUIForm 15 { 16 private System.Windows.Forms.Labe- transactionLabel; 17 private System.Windows.Forms.TextBox transactionTextBox; 18 19 private System.Windows.Forms.Button saveButton; 20 private System.Windows.Forms.Button cancelButton; 21 22 private System.ComponentModel.Container components = null; 23
Файлы и потоки 515 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 . 81 82 83 84 85 // ссылка на объект, обрабатывающий транзакции private Transaction transactionproxy; // делегат для метода, отображающего предыдущее окно private MyDelegate showPreviouslyWindow; // инициализация компонентов и задание элементам значений параметров public UpdateDialogForm( Transact ion transact ionProxyValue, MeDelegate delegatevalue)' { InitializeComponent(); showPreviousWindow = delegatevalue; // создание объекта, обрабатывающего транзакции transactionproxy - transactionProxyValue; } // код, сгенерированный Visual Studio .NET // активизируется при вводе текста в текстовое поле счета private void accountTextBox_KeyDown( object sender, System.Windows.Forms.KeyEventArgs e) { // определение нажатия пользователем клавиши <Enter> if (e.KeyCode = Keys.Enter) { // извлечение записи, относящейся к счету, из файла RandomAccessRecord record = transactionProxy.GetRecord(GetTextBoxValues() [(int)TextBoxIndices.ACCOUNT]); // возвращение, если запись не существует if (record == null) return; Il определение, пустая ли запись if /(record.Account != 0) { // сохранение значений записи в строковом массиве string[] values = { record.Account.ToString(), record.FirstName.ToString(), record.LastName.ToString(), i record.Balance.Tostring() }; // копирование значения строкового массива в текстовые поля SetTextBoxValues(values); transactionTextBox.text = "[Charge or Payment]"; } else { // уведомление пользователя, если запись не существует MessageBox.Show( "Record Does Not Exist”, "Error", MessageBoxButtons.OK, MessageBoxIcon.Error); } } } // конец метода accountTextBox_KeyDown
516 Глава 14 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 II активизация при вводе текста в текстовое поле транзакции private void transactionTextBoxJKeyDown( object sender, System.Windows.Forms.KeyEventArgs e) { // определение нажатия пользователем клавиши <Enter> if (e.KeyCode = Keys.Enter) { // расчет сальдо с помощью значения текстового поля транзакции try { // извлечение записи, относящейся к счету, из файла RandomAccessRecjord record = transactionProxy.GetRecord(GetTextBoxValues() [(int)TextBoxIndices. ACCOUNT]); II получение значения текстового поля транзакции double transactionvalue = Double.Parse(transactionTextBox.text); // расчет нового сальдо (старое сальдо + транзакция) double newBalance = record.Balance + transactionvalue; // сохранение значений записи в строковом массиве stringf] values = { record.Account.ToString(), record.FirstName.ToString(), record.LastName.Tostring(), newBalance.Tostring() }; // копирование значения строкового массива в текстовое поле SetTextBoxValues(values); // сброс текстового поля транзакции transactionTextBox.text « ""; } // сообщение об ошибке при несовпадении параметров catch(FormatException) { MessageBox.Show( "Invalid Transaction", "Error", MessageBoxButtons.OK, MessageBoxIcon.Error); } ) } // конец метода TransactionTextBox_KeyDown // активизация при нажатии пользователем кнопки Save Changes private void saveButton_Click( object sender, System.EventArgs e) { RandomAccessRecord record = transactionProxy.GetRecord(GetTextBoxValues() [(int)TextBoxIndices.ACCOUNT]); . // если запись существует, обновление в файле if (record != null) Updaterecord(record); Hide(); ClearTextBoxes(); showPreviousWindow(); ) // конец метода saveButton_Click •
Файлы и потоки 517 151 152 // активизация при нажатии пользователем кнопки Cancel 153 private void cancelButton_Click( 154 object sender, System.EventArgs e) 155 { 156 Hide(); 157 ClearTextBoxes(); 158 showPreviousWindow(); 159 160 } // конец метода cancelButton Click 161 162 // обновление записи в файле в позиции, указанной accoutNumber 163 public void UpdateRecord(RandomAccessRecord record) 164 { 165 // сохранение значений текстовых полей в записи и ввод записи в файл 166 try 167 { 168 int accountNumber = гecord.Account; 169 string[] values = GetTextBoxValues(); 170 171 // сохранение значений в записи 172 record.Account = accountNumber; 173 record.FirstName = 17 4 values[(int)TextBoxIndices.FIRST]; 175 record.LastName « 17 6 values[(int)TextBoxIndices.LAST] ; 177 record.Balance = 178 Double.Parse( 179 values[(int)TextBoxIndices.BALANCE]); 180 181 // добавление записи в файл 182 i f (transact ionProxy.AddRecord( 183 record, accountNumber) == false) 184 185 return; // при ошибке 186 ) 187 188 11 сообщение об ошиоке при несовпадении параметров 189 catch(FormatException) 190 { 191 MessageBox.Show("Invalid Transaction", "Error", 192 MessageBoxButtons.OK, MessageBoxIcon.Error); 193 194 return; 195 } 196 197 MessageBox.Show("Record Updated", "Success", 198 MessageBoxButtons.OK, 199 MessageBoxIcon.Information); 200 201 } // конец метода UpdateRecord 202 203 } // конец класса UpdateDialogForm । Для обновления записи пользователь должен ввести номер счета (рис. 14.35, а), относящийся к этой записи. После нажатия клавиши <Enter> класс UpdateDialogForm вызывает метод accountTextBox_KeyDown (строки 45— 84) для отображения содержимого записи. Данный метод вызывает метод GetRecord объекта Transaction (стро- ки 52—54) для извлечения указанного объекта RandomAccessRecord. Если запись не пустая, тогда строки 64—72 заполняют текстовые поля значениями объекта RandomAccessRecord. Текстовое поле Transaction изначально содержит строку Charge or Payment (сбор или платеж) — рис. 14.35, б. Пользователь должен выделить этот текст, ввести сумму транзакции (положительное значение для сбора или отрицательное значение для платежа) и нажать клавишу <Enter> (рис. 14.35, в). Программа вызовет метод
518 Глава 14 transactionTextBox KeyDown (строки 87—132) для добавления указанной пользователем суммы в текущее сальдо. Пользователь нажимает кнопку Save Changes (рис. 14.35, г) для записи в файл измененного содержимого тек- стовых полей (TextBox). (Обратите внимание, что при нажатии кнопки Save Changes информация в поле Balance не обновляется: для ее обновления пользователь должен нажать клавишу <Enter> до нажатия кнопки Save Changes.) При нажатии кнопки Save Changes программа активизирует метод saveButton Ciick (стро- ки 135—150), вызывающий, в свою очередь, метод UpdateRecord типа private (строки 163—201). Данный ме- тод вызывает метод AddRecord объекта Transaction (строки 182—183) для сохранения значений TextBox в клас- се RandomAccessRecord и подмены Существующей записи файла объектом RandomAccessRecord, содержащим новые данные. При удачном обновлении записи на экране отображается соответствующее сообщение (рис. 14.36). • Ti ansottion Processor Рис. 14.35. Окно, используемое для обновления записей: а — ввод номера счета; б — поля формы заполнены данными из существующей записи; в — ввод суммы транзакции; г — нажатие кнопки Save Changes Рис. 14.36. Сообщение об обновлении записи При нажатии пользователем кнопки Delete Record (рис. 14.37) окна Start Dialog программа активизирует метод deleteButton Click класса Start DialogForm (листинг 14.14, строки 119—125), отображающий внутреннее окно DeleteDialogForm (листинг 14.17). Класс DeleteDialogForm позволяет пользователю удалить существующие записи из файла. Для удаления записи следует ввести номер счета, относящийся к этой записи (рис. 14.38). При нажатии кнопки Delete Record (на этот раз из внутреннего окна DeleteDialogForm) класс DeleteDialogForm вы- зывает метод deleteButton Click (строки 44—57). Этот метод вызывает метод DeleteRecord (строки 69—102),
Файлы и потоки 519 подтверждающий существование записи для удаления, после чего вызывается метод AddRecord объекта Transaction (строки 87 и 88) для замены записи файла на пустую запись. При удачном удалении записи на экране отображается соответствующее сообщение (рис. 14.39). Рис. 14.37. Нажатие кнопки Delete Record в стартовом окне Рис. 14.38. Нажатие кнопки Delete Record после ввода номера счета Рис. 14.39. Сообщение об удалении записи I Листинг 14.17. Класс DeleteDialogForm обеспечивает удаление записей из файлов в примере обработки банковских транзакций J 1 // Листинг 14.17: DeleteDialog.cs 2 // Удаление пользователем записей из файла 3 4 // пространства имен C# 5 using System; 6 using System.Drawing; 7 using System.Collections; 8 using System.ComponentModel; 9 using System.Windows.Forms; -10 11 // пространство имен Deitel 12 using BankLibrary; 13 14 public class DeleteDialogForm : System.Windows.Forms.Form 15 { 16 private System.Windows.Forms.Label accountLabel; 17 private System.Windows.Forms.TextBox accountTextBox; 18 19 private System.Windows.Forms.Button deleteButton; 20 private System.Windows.Forms.Button cancelButton; 21 22 private System.ComponentModel.Container components = null; 23 24 // ссылка на объект, обрабатывающий транзакции 25 private Transaction transactionProxy; 26 11 делегат для метода, отображающего предыдущее окно 28 private MyDelegate showPreviouslyWindow; 29 S 30 // создание компонентов и задание числам значения параметров 31 public DeleteDialogForm(Transaction transactionProxyValue, 32 MyDelegate delegatevalue) 33 { 34 InitializeComponent(); 35 showPreviousWindow = delegatevalue; 36 37 // создание объекта, обрабатывающего транзакции 38 transactionProxy = transactionProxyValue; 39 } 40
520 Глава 14 41 42 43 44 ' 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 } // код, сгенерированный Visual Studio .NET // активизация при нажатии пользователем кнопки Delete Record private void deleteButton_Click( object sender, System.EventArgs e) { RandomAccessRecord record = transactionproxy.GetRecord(accountTextBox.Text); / // если запись существует, удалить ее из файла if (record != null) DeleteRecord(record); this.Hide(); showPreciousWindow(); } // конец метода deleteButton_Click // активизация при нажатии кнопки Cancel private void cancelButton_Click( object sender, System.EventArgs e) { this.Hide(); showPreviousWindow(); } // конец метода cancelButton_Click // удаление записи из файла в позиции, указанной accountNumber public void DeleteRecord(RandomAccessRecord record) { int accountNumber = record.Account; // отображение сообщения об ошибке, если записи не существует if (record.Account == 0) { Message.Show("Record Does Not Exist", "Error", MessageBoxButtons.OK, MessageBoxIcon.Error); aqcountTextBox.Clear(); • 9- return; ) // создание пустой записи record = new RandomAccessRecord(); // замена записи файла пустой записью i f (transactionProxy.AddRecord( record, accountNumber) == true) // уведомление пользователя об успешном удалении MessageBox.Show("Record Deleted", "Success", MessageBoxButtons.OK, MessageBoxIcon.Informat ion); else // уведомление пользователя о сбое удаления MessageBox.Show( "Record could not be deleted", "Error", MessageBoxButtons.OK, MessageBoxIcon.Error); accountTextBox.Clear(); } // конец метода DeleteRecord // конец класса DeleteDialogForm
Файлы и потоки 521 В данной главе продемонстрировано считывание данных из файлов и запись данных в файлы с использованием методик последовательной и произвольной обработки. С помощью класса BinaryFormatter объекты упорядочи- вались в потоки и выполнялось снятие упорядоченности; затем применялись классы Filestream, Binarywriter и BinaryReader для переноса байтового представления объектов в файлы и из них. В следующей главе описывается язык XML (extensible Markup Language, расширяемый язык разметок) — ши- роко распространенная технология описания данных. С помощью XML можно описать любой тип данных: ма- тематические формулы, музыку и финансовые отчеты. 14.12. Резюме Все элементы данных, обрабатываемые компьютером, сводятся к комбинациям нулей и единиц. Наименьшие элементы данных, поддерживаемые компьютерами, называются битами, принимающими значение 0 или 1. Цифры, буквы и специальные символы, называются символами. Набор всех символов, используемых для напи- сания программ и представления элементов данных на конкретном компьютере, называются набором символов данного компьютера. Каждый символ в наборе символов компьютера представлен комбинацией единиц и ну- лей. Полем называется группа символов, передающая определенное значение. Запись — это группа связанных меж- ду собой полей. Минимум одно поле записи выбирается в качестве ключа записи, определяющего, что запись относится к конкретному лицу или организации, и отличающего эту запись от всех прочих записей в файле. Файлом называется группа взаимосвязанных записей. Файлы используются для долгосрочного хранения боль- ших объемов данных, которые сохраняются после прекращения выполнения программы, создавшей эти данные. Данные, поддерживаемые в файлах, часто называются долговременными. В C# файлы не организуются в структуры. Поэтому в файлах C# не существует таких понятий, как "запись". Это означает, что программист должен структурировать файлы под требования программных приложений. В языке C# каждый файл рассматривается как последовательный поток байтов. Каждый файл заканчивается указателем окончания определенной машинозависимой формы. Для обработки файлов в C# необходима ссылка на пространство имен System, ю. В это пространство имен вхо- дят определения таких классов потоков, как streamReader, streamwriter и Filestream. Файлы открываются созданием объектов этих классов. При открытии файла создается объект, с которым ассоциируется поток. По- токи обеспечивают потоки связи между файлами и программами. Наиболее широко распространенным типом организации файлов являются файлы с последовательным досту- пом к данным, записи в которых обычно хранятся в порядке по полю ключевой записи. Для последовательного извлечения данных из файла программа начинает просмотр с начала файла, по порядку считывая все данные до нахождения нужных. При работе с файлами с последовательным доступом каждый последующий запрос на ввод или вывод считывает или записывает очередной набор данных в файле. Класс BinaryFormatter предоставляет методы Serialize и Deserialize для записи и считывания объектов соот- ветственно. Сериализовать и десериализовывать файлы могут только классы с атрибутом Serialize. Метод serialize записывает представление объекта в поток. Метод Deserialize считывает это представление из по- тока и восстанавливает первоначальный объект. Методы serialize и Deserialize требуют в качестве параметра объект stream, что обеспечивает классу BinaryFormatter доступ к нужному файлу. Моментальный доступ к данным возможен в файлах с произвольным доступом. Программа способна осуществ- лять прямой и оперативный доступ к отдельным записям файла с произвольным доступом без просмотра других записей. Иногда файлы с произвольным доступом называются файлами прямого доступа. С их помощью каж- дый очередной запрос на ввод/вывод может быть направлен в любую часть файла, находящуюся на произволь- ном расстоянии от части, на которую была сделана ссылка в предыдущем запросе. Существуют разные способы создания файлов с произвольным доступом. Возможно, самый простой заключа- ется в требовании, чтобы все записи в файле были фиксированной длины. Использование записей с фиксиро- ванной длиной упрощает задачу по расчету (в виде функции размера записи и ключевой записи) точного место- положения любой записи относительно начала файла. Классы BinaryReader и Binarywriter предоставляют методы для считывания байтов и их записи в потоки соот- ветственно. Конструкторы BinaryReader и Binarywriter получают в качестве аргументов ссылки на экземпляры класса System.IO.Stream. Класс Filestream наследует из класса Stream, поэтому объект Filestream МОЖНО пе- редать в качестве аргумента либо в конструктор BinaryReader, либо Binarywriter с целью создания объекта, который может напрямую переносить байты в файл и из файла.
ГЛАВА 15 Язык XML (ш Зная, что такое деревья, я понимаю значение терпения. Зная, что такое трава, я могу познать упорство. Хэл Борланд Как все метафизическое, гармонию между мыслью и реальностью следует искать в грамматике языка. Людвиг Виттгенштейн Я играл с идеей и заупрямился, подбросил ее в воздух, трансформировал, отпустил прочь и снова поймал, заставил ее переливаться всеми цветами фантазии и окрылил парадоксом. Оскар Уайлд Темы данной главы: □ разметка данных с помощью языка XML; □ понятие концепции пространства имен в языке XML; □ взаимоотношение между DTD, схемами и XML; О создание схем; □ создание и использование простых документов на языке XSLT; □ преобразование документов XML в XHTML с помощью класса xslTransorm; □ знакомство с BizTalk. 15.1. Введение Язык XML (Extensible Markup Language, расширяемый язык разметки) разработан в 1996 году рабочей группой XML Консорциума World Wide Web (World Wide Web Consortium, W3C). XML — это портативная широко под- держиваемая открытая технология (т. е. незапатентованная) описания данных. XML постепенно превращается в стандарт хранения данных, обмен которыми осуществляется между программными приложениями. С по- мощью языка XML автор документа может описать любой тип данных, включая математические формулы, ин- струкции по конфигурированию программных средств, музыку, рецепты и финансовые отчеты. Документы XML могут читать как люди, так и машины. Язык XML широко используется в .NET Framework. Библиотека классов FCL (Framework Class Library) предос- тавляет расширенный набор классов, относящихся к XML. В большей части внутренней реализации Visual Stu- dio также применяется XML. В данной главе представлен язык XML, связанные с ним технологии и основные классы для создания документов XML и манипуляций с ними. 15.2. Документы XML В данном разделе обсудим первый документ XML, описывающий статью (листинг 15.1). Примечание Номера строк не являются частью документа XML.
Язык XML 523 1 <?xml version = "1.0”?> 2 3 <!— Listing 15.1: article.xml —> 4 <!— Article structured with XML —> 5 6 <article> 7 8 <title>Simple XML</title> 9 10 <date>December 6, 2001</date> 11 12 <author> 13 <firstName>John</firstName> 14 <lastName>Doe</lastName> 15 <author> 16 17 <summary>XML is pretty easy.</summary> 18 ‘ 19 <content>In this chapter, we present a wide variety of examples 20 that use XML. 21 </content> 22 23 </article> Данный документ начинается с произвольного объявления XML (строка 1), идентифицирующего текст как до- кумент XML. Информационный параметр version указывает версию XML, использованную в документе. Ком- ментарии XML (строки 3 и 4), начинающиеся с < • — и заканчивающиеся —>, можно размещать в документе XML практически везде. Как и в программах С#, в XML комментарии используются в целях документирования. Распространенная ошибка программирования___________________________________________ Помещение любых символов, включая символ пробела, перед объявлением XML является ошибкой Совет по повышению переносимости_______________________________________________________ Несмотря на необязательность объявления XML, документ должен включать его в себя для идентификации ис- пользуемой версии XML. В противном случае в будущем документ без объявления XML может рассматриваться как соответствующий последней версии XML, в результате чего могут появиться ошибки, В XML данные маркируются с помощью тегов, представляющих собой имена, заключенные в угловые скобки (< >). Теги используются парами для выделения символьных данных (например, simple xml в строке 8). Тег, открывающий разметку (т. е. данные XML), называется начальным тегом (Или открывающим), а тег, закры- вающий разметку, — конечным тегом (или закрывающим). Примерами начальных тегов служат <articie> и <title> (строки 6 и 8 соответственно). Конечные теги отличаются от начальных тем, что они содержат символ левой косой черты (/) сразу за символом <. Примеры конечных тегов: </title> и </article> (строки 8 и 23 соответственно). В документе XML может содержаться любое количество тегов. Распространенная ошибка программирования_______________________________________________ Отсутствие конечного тега при наличии начального является ошибкой. Отдельные блоки разметки (т. е. все, что находится между начальным тегом и соответствующим ему конечным тегом) называются элементами. Документ XML имеет один элемент (называется корневым элементом), содер- жащий все прочие элементы. Корневой элемент должен стоять первым после объявления XML. В листинге 15.1 корневым элементом является article (строка 6). Элементы вложены друг в друга для образования иерархий; корневой элемент находится на самом верху иерархии. Это дает авторам документа возможность создавать яв- ные отношения между данными. Например, элементы title, date, author, summary и content вложены в корне- вой элемент article. Элементы firstName и lastName вложены в корневой элемент article. Распространенная ошибка программирования_______________________________________________ Попытка создания более одного корневого элемента в документе XML является синтаксической ошибкой. Элемент title (строка 8) содержит в качестве символьных данных заголовок статьи — simple xml. Точно так же элементы date (строка 10), summary (строка 17) и content (строки 19—21) содержат в качестве символьных
524 Глава 15 данных дату создания, резюме и содержание статьи соответственно. Имена элементов XML могут быть любой длины и содержать буквы, цифры, символы подчеркивания, дефисы и точки; начинаться имена должны с буквы или символа подчеркивания. Распространенная ошибка программирования_______________________________________'___________ В XML имеет значение регистр написания символов. Использование неверного регистра (прописные/строчные) при написании имени элемента XML является синтаксической ошибкой Сам по себе данный документ является простым текстовым файлом с именем article.xml. Большинство докумен- тов XML заканчиваются расширением xml, хотя оно и не обязательно. Обработка документов XML требует программы, называемой XML-анолизатором или XML-процессором. Анализаторы отвечают за проверку син- таксиса документа XML и за обеспечение доступности данных документа XML для программных приложений. Часто XML-анализаторы встраиваются в приложения (например, в Visual Studio), либо их можно загрузить из Интернета. В число популярных анализаторов входят msxml от Microsoft, Xerces от Apache Software Foundation и XML4J от корпорации IBM. В данной главе используется анализатор msxml. При загрузке пользователем файла article xml в Internet Explorer (IE)1 msxml анализирует документ и передает обработанные данные в IE. После этого IE использует встроенную таблицу стилей для форматирования дан- ных. Обратите внимание, что результирующий формат данных (рис. 15.1) схож с форматом документа XML, представленного в листинге 15.1. Далее мы продемонстрируем* важную и незаменимую роль таблиц стилей в преобразовании данных XML в форматы, подходящие для отображения. Знак Знак "+• Рис. 15.1. Файл article.xml, отображенный в Internet Explorer: а — с отображением элементов-потомков; б — с отображением только элементов-контейнеров Обратите внимание на символы минуса (-) и плюса (+) на рис. 15.1. Несмотря на то, что они не являются частью документа XML, IE помещает их рядом с каждым элементом-контейнером (т. е. рядом с элементами, содер- жащими другие элементы). Элементы-контейнеры еще называются элементами-родителями. Знак минуса ука- зывает на то, что отображаются элементы-потомки (т. е. вложенные элементы) элемента-родителя. Если щелк- нуть кнопкой мыши на значке -, он превращается в знак + (сворачивающий элемент-контейнер и скрывающий все элементы-потомки). И наоборот, при нажатии на знак + элемент-контейнер разворачивается, и плюс меняет- ся на минус. Такое поведение похоже на просмотр структуры каталогов в системе Windows с помощью про- 1 IE версии 5 и выше.
Язык XML 525 граммы Windows Explorer. На самом деле структура каталогов часто моделируется как серия структур деревьев, в котором литера каждого диска (например, С: и т. д.) обозначает корень дерева. Каждая папка— это "нарост" на дереве. Анализаторы часто размещают данные XML в структуры дерева, что упрощает манипуляцию ими (подробное описание см. в разд. 15.4). Распространенная ошибка программирования______________________________________________ Неправильное вложение тегов XML является синтаксической ошибкой. Например, выражение <x><y>hello</xx/y> является ошибочным, потому что тег </у> должен предшествовать тегу </х>. Теперь представим второй документ XML (листинг 15.2), в котором размечается деловое письмо. В данном до- кументе XML содержится значительно больше данных, чем в предыдущем. г Листинг 15.2. XML для разметки делового письма 1 <?xml version = "1.0"?> 2 3 <!— Листинг 15.2: letter.xml —> 4 <!— Деловое письмо, отформатированное с помощью XML —> 5 6 <letter> 7 <contact type = "from"> 8 <name>Jane Doe</name> 9 <addressl>Box 12345</addressl> 10 <address2>15 Any Ave.</address2> 11 <city>Othertown</city> 12 <state>Otherstate</state> 13 <zip>67890</zip> 14 <phone>555-4321</phone> 15 <flag gender = "F" /> 16 </contact> 17 18 ccontact type = "to"> 19 <name>John Doe</name> 20 <addressl>123 Main St.</addressl> 21 <address2x/address2> 22 <city>Anytown</city> 23 <state>Anystate</state> 24 <zip>12345</zip> 25 <phone>555-1234</phone> 26 <flag gender = "M" /> 27 </contact> 28 29 <salutation>Dear Sir:</salutation> 30 31 <paragraph> It is our privilege to inform you about our new 32 database managed with <technology>XML</technology>.This 33 new system allows you to reduce the load on 34 your inventory list server by having the client machine 35 perform the work of sorting and filtering the data. 36 </paragraph> 37 38 <paragraph>Please visit our Web site for availability 39 and pricing. 40 </paragraph> 41 42 <closing>Sincerely</closing> 43’ 44 <signature>Ms. Doe</signature> 45 </letter> Корневой элемент letter (строки 6—45) содержит элементы-потомки contact (строки 7—16 и 18—27), saluta- tion, paragraph (строки 31—36 и 38—40), closing и signature. Помимо помещения между тегами, данные можно поместить в атрибуты, являющиеся парами "имя = значение” в начальных тегах. Элементы могут иметь
526 Глава 15 любое количество атрибутов в их начальных тегах Первый элемент contact (строки 7—16) имеет атрибут type со значением атрибута "from", указывающий на то, что данный элемент contact размечает информацию об от- правителе письма. Второй элемент contact (строки 18—27) имеет атрибут type со значением "to", указываю- щий на то, что данный элемент contact размечает информацию об адресате письма. Подобно именам элемен- тов, имена атрибутов зависят от регистров, могут быть любой длины и содержать буквы, цифры, символы под- черкивания, дефисы и точки и должны начинаться с буквы или с символа подчеркивания. Элемент contact сохраняет имя контактного лица, адрес и номер телефона. Элемент salutation (строка 29) размечает приветст- вие письма. В строках 31—40 тело письма размечается элементами paragraph. Элементы closing (строка 42) и signature (строка 44) размечают завершающее предложение и подпись автора письма соответственно. Распространенная ошибка программирования________________________________________________ Если не заключить значения атрибутов в двойные (" ") или одинарные (' ’) кавычки, то это будет синтаксиче- ской ошибкой. Распространенная ошибка программирования________________________________________________ Попытка предоставления элементу двух атрибутов с одинаковыми именами является синтаксической ошибкой. В строке 15 представлен пустой элемент flag, указывающий пол контактного лица. Пустые элементы не со- держат символьных данных (т. е. они не содержат текста между начальным и конечным тегом). Такие элементы закрываются либо косой чертой в конце (как показано в строке 15), либо явным написанием закрывающего тега, как в выражении <flag gender = "F"></flag> 15.3. Пространства имен XML Языки объектно-ориентированного программирования, такие как C# и Visual Basic .NET, предоставляют объ- емные библиотеки классов, в которых функции сгруппированы в пространства имен. Эти пространства имен предотвращают конфликты имен между идентификаторами, определенными программистами, и идентификато- рами в библиотеках классов. Например, мы можем использовать класс Book для представления информации об одном из изданий, а филателист может использовать класс с таким же именем для обозначения альбома с мар- ками. Конфликт имен будет иметь место, если указывать оба класса в одном компоновочном блоке без про- странств имен для их дифференцирования. Подобно С#, XML также предоставляет пространства имен, обеспечивающие средство уникальной идентифи- кации элементов XML. Кроме того, языки на базе XML, называемые словарями,— XML Schema (см. разд. 15.5), XSL (см. разд. 15.6) и BizTalk (см. разд. 15.7) — часто используют пространства имен для иденти- фикации их элементов. Элементы различаются посредством префиксов пространств имен, идентифицирующих пространство имен, к которому принадлежит элемент. Например, <deitel:book>C# For Experienced Programmers</deitel:book> квалифицирует элемент book с префиксом пространства имен deitel. Это указывает на то, что элемент book является частью пространства имен deitel. Авторы документов могут использовать любое имя в качестве пре- фикса пространства имен, за исключением зарезервированного префикса xml. Распространенная ошибка программирования______________________________________________ Попытка создания префикса пространства имен с именем xml в любом случае будет синтаксической ошибкой. Разметка в листинге 15.3 демонстрирует использование пространств имен. Данный документ XML содержит два элемента file, различающиеся с помощью пространств имен. Результат представлен на рис. 15.2. Листинг 15.3. Демонстрация пространств имен XML 1 <?xml version = "1.0”?> 2 3 <!— Listing 15.3: namespace.xml —> 4 <!— Demonstrating namespaces —> 5 6 <text:directory xmlnsitext = "urn:deitel:textInfo" 7 xmlns:image = "urn:deitel:imagelnfo"> 8
Язык XML 527 9 <text:file filename = "book.xml"> 10 <text descriptions book 1ist</text:description> 11 </text:file> 12 13 <image:file filename = "funny.jpg"> 14 cimage:descriptions funny picturec/image:description> 15 cimage:size width = "200" height = "100" /> 16 </image:file> 17 18 c/text:directory> Рис. 15.2. Демонстрация пространств имен XML Замечание по технологии программирования___________________________________________________ Программист имеет возможность квалификации атрибута префиксом пространства имен. Однако этого не тре- буется, поскольку атрибуты часто ассоциируются с элементами. В строках 6 и 7 используется атрибут xmlns для создания префиксов пространств имен: text и image. Каждый префикс пространства имен ограничен серией символов, называемой универсальным идентификатором ресур- са (Uniform Resource Identifier, URI), уникально идентифицирующим пространство имен. Авторы документов создают собственные префиксы пространств имен и URI. Для обеспечения уникальности пространств имен авторы документов могут создавать уникальные URI. Здесь в качестве URI используется текст: urn: deitel text Info и urn: deitel: imageinfo. В настоящее время обычной практикой является использование унифицированных указателей ресурсов (Universal Resource Locator, URL) вместо URI, потому что имена доменов (таких как www.deitel.com), используемые в URL, будут гарантирован- но уникальными. Например, строки 6 и 7 можно было записать следующим образом: <text:directory xmlns:text = "http:I/www.deitel.com/xmlns-text" xmlns:image = http://www.deitel.com/xmlns-image> В данном примере используются URL, относящиеся к Deitel & Associates, Inc. — имени домена для идентифи- кации пространств имен. Анализатор никогда не заходит в эти URL: они просто представляют серию символов, используемую для различения имен. Нет необходимости делать ссылки URL на конкретные Web-страницы или подробно формировать их. В строках 9—11 используется префикс пространства имен text для квалификации элементов file и description как принадлежащих к пространству имен "urn:deitel:textinfo". Обратите внимание, что префикс пространства имен text также применяется к конечным тегам. В строках 13—16 префикс пространства имен image применяется к элементам file, description и size. Для устранения необходимости ввода префикса к пространству имен перед каждым элементом авторы докумен- тов могут задать пространство имен по умолчанию. В листинге 15.4 продемонстрировано создание и использо- вание пространств имен по умолчанию. Результат представлен на рис. 15.3.
528 Глава 15 |!Г-и~пи5Г-—-- Листинг1S 4- Демонстрация пространств имен по умолчанию 1 <?xml version = "1.0"?> 2 3 <!— Listing 15.4: defaultnamespace.xml —> 4 <!— Using default namespaces —> 5 6 <directory xmlns = "urn:deitel:textInfo" 7 xmlns:image = "urn:deitel:imagelnfo"> 8 9 <file filename = "book.xml"> 10. <description>A book list</description> 11 </file> 12 13 <image:file filename = "funny.jpg"> 14 <image: descriptions funny picture</image:description> 15 <image:size width = "200" height = "100" /> 16 </image:file> 17 18 </directory> Рис. 15.3. Демонстрация пространств имен по умолчанию В строке 6 с помощью атрибута xmlns создается пространство имен по умолчанию с URI в качестве значения. После определения пространства имен по умолчанию элементы-потомки, принадлежащие этому пространству имен, не нужно квалифицировать с помощью префикса пространства имен. Элемент file (строки 9—11) нахо- дится в пространстве имен urn: deitel: text info. Сравните его с листингом 15.3, где перед file и description ставится префикс text (строки 9—11). Пространство имен по умолчанию применяется к элементу directory и ко всем элементам, которые не квали- фицируются префиксом пространства имен. Однако префикс пространства имен можно использовать для указа- ния другого пространства имен для конкретных элементов. Например, перед элементом file в строке 13 ста- вится префикс image для указания, что оно находится в пространстве имен urn:deitel:imageinfo, а не в про- странстве имен по умолчанию. 15.4. Объектная модель документа (DOM) Несмотря на то, что документы XML являются текстовыми файлами, извлечение из них данных с помощью ме- тодов последовательной выборки ни практично, ни эффективно, особенно в случаях, когда данные должны до- бавляться или удаляться динамично. При успешном синтаксическом анализе некоторые XML-анализаторы сохраняют данные документа в памяти в виде структур деревьев. На рис. 15.4 показана структура дерева для документа article.xml, рассмотренного в листинге 15.1. Такая иерархическая структура дерева называется деревом объектной модели документа (Docu- ment Object Model, DOM), a XML-анализатор, создающий структуру такого типа, называется DOM-ана-
Язык XML 529 лизатором. Каждый компонент документа XML (например, article, date, firstName и т. д.) представляется в виде узла на дереве DOM. Узлы (например, author), содержащие другие узлы (называемыеузлами-потомками), называются узлами-родителями. Узлы, имеющие одного "родителя" (например, firstName и lastName), назы- ваются узлами-братьями. Узлы-потомки одного узла включают в себя потомков этого узла, потомков потомков и т. д. Точно так же узлы-предки узла включают в себя предка этого узла, предка предка и т п. Каждое дерево DOM имеет один корневой узел, содержащий все прочие узлы в документе: комментарии, элементы и т. д. Рис. 15.4. Структура дерева для листинга 15.1 Классы для создания, считывания документов XML и манипуляций ими расположены в пространстве имен C# System.xml, которое также включает дополнительные пространства имен, содержащие другие операции, имею- щие отношение к XML. В данном разделе приведено несколько примеров, в которых используются деревья DOM. В первом примере — в программе листинга 15.5 — загружается документ XML, представленный в листинге 15.1, и его данные ото- бражаются в текстовом поле. В данном примере используется класс XmlNodeReader, производный от класса XmlReader, итерирующийся через каждый узел в документе XML. Класс xmlReader принадлежит к типу abstract и определяет интерфейс для считывания документов XML. ’ ' ' ‘ 'л . .7.•7 ’d’ ’’ Г. ' Листинг 15.5. Класс XndNooeReader, используемый для прохода через документ XML .4....................1*. ......................... ....и.и.и. ... МЯЯЛ^.- U. .U-. Г-.*., ..i?... WU ....«ьж.ылнм. л 1 // Листинг 15.5: XmlReaderTest.cs 2 // Считывание документа XML 3 4 using System; 5 using System.Windows.Forms; 6 using System.Xml; 7 8 public class XmlReadertest : System.Windows.Forms.Form 9 { 10 private Systern.Windows.Forms.TextBox outputTextBox; 11 private System.ComponentModel.Container components = null; 12 13 public XmlReaderTest() 14 { 15 InitializeComponent(); 16 17 // ссылка на документ XML 18 XmlDocument document = new XmlDocument(); 19 document.Load(" \\.. Warticle.xml") ; 20 21 // создание класса XmlNodeReader для документа 22 XmlNodeReader reader = new XmlNodeReader(document); 23 24 // показ формы до заполнения объекта outputTextBox 25 this.Show(); 26 27 // глубина дерева -1, без отступа 28 int depth = -1; 29 34 Зак. 3333
530 Глава 15 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 • 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 II отображение содержимого каждого узла while (Reader.read()) { switch (reader.NodeType) { // если Element, отобразить его имя case XmlNodeType. Element: // повышение глубины табуляции depth++; TabOutput(depth); outputtextBox.text += "<" + reader.Name + ">" + "\r\n"; // если элемент пустой, уменьшить глубину if (reader.IsEmptyElement) depth—; break; // если Comment, отобразить case XmlNodeType.Comment: TabOutput(depth); outputTextBox.Text += + reader .Value + ’,-->\r\n"; break; // если Text, отобразить case XmlNodeType.Text: TabOutput(depth); outputTextBox.Text += "\t" + reader.Value + "XrXn"; break; // если объявление XML, отобразить case XmlNodeType.XmlDeclaration: TabOutput(depth); outputTextBox.text += "<?" + reader.Name + " " + reader.Value + " ?>\r\n"; break; // если EndElement, отобразить и уменьшить глубину case XmlNodeType.EndElement: TabOutput(depth); outputtextBox.text += "</" + reader.Name + ">\r\n"; depth—; break; } // конец оператора switch ) // конец цикла while ) // конец конструктора XmlReaderTest // вставка табуляции private void TabOutput(int number) { for (int i = 0; i < number; i++) outputtextBox.text += "\t"; } // конец TabOutput // код, сгенерированный Windows Form Designer [STAThread] static void Main ()
Язык XML 531 93 { 94 Application.Run(new XmlReaderText()); 95 } // конец Main 96 } // конец XmlReaderTest В строке 6 включено пространство имен System.Xml, содержащее классы XML, использованные в примере. В строке 18 создается ссылка на объект XmlDocument, концептуально представляющий пустой документ XML. Осуществляется синтаксический разбор документа XML article.xml, и он загружается в объект XmlDocument, ко- гда в строке активизируется метод Load. После загрузки документа XML в объект XmlDocument его данные мож- но считывать программным способом и манипулировать ими. В приведенном примере считывается каждый узел в объекте XmlDocument, являющемся деревом DOM. В последующих примерах будут описаны манипуляции значениями узлов. В строке 22 создается объект XmlNodeReader и присваивается ссылке reader, позволяющей считывать по одному узлу из объекта XmlDocument. Метод Read класса XmlReader считывает один узел из дерева DOM. Размещение этого оператора в цикле while (строки 31—78) вынуждает ссылку read считывать все узлы документа. Оператор switch (строки 33—77) обрабатывает каждый узел. Форматируется свойство Name (строка 41), содержащее имя узла, или свойство value (строка 53), содержащее данные узла, после чего связывается в строку, присвоенную свойству Text текстового поля. Свойство NodeType содержит тип узла (указывающий, является ли узел элемен- том, комментарием, текстом и т. д.). Обратите внимание, что каждый случай case указывает тип узла с по- мощью констант перечисления XmlNodeType. Также обратите внимание, что отображенные выходные данные подчеркивают структуру документа XML. Пе- ременная depth (строка 28) задает количество символов табуляции, применяемых для задания отступа каждому элементу. Глубина увеличивается всякий раз при нахождении типа Element и уменьшается при нахождении типа EndElement или пустого элемента. Такая же методика применяется в следующем примере для выделения струк- туры дерева отображенного документа XML. Заметьте, что в разрывах строк используется последовательность символов "\г\п", обозначающая "возврат ка- ретки", за которой следует "перевод строки". Это — стандартный разрыв строки, применяемый в приложениях и элементах управления на базе Windows. Результат работы программы изображен на рис. 15.5. Рис. 15.5. Результат структурирования XML-документа с помощью класса XmlNodeReader Программа С#, представленная в листинге 15.6, демонстрирует программные манипуляции деревом DOM. В этой программе файл letter.xml (см. листинг 15.2) загружается в дерево DOM, после чего создается второе дерево DOM, дублирующее дерево с содержимым letter.xml. GUI данного приложения содержит текстовые по- ля, элемент управления Treeview и три кнопки: Build, Print и Reset (рис. 15.6). При нажатии кнопки Build файл letter.xml копируется и в элементе управления Treeview отображается структура дерева документа; при нажатии кнопки Print в текстовом поле выводятся значения элементов XML и имена, а при нажатии кнопки Reset эле- мент управления Treeview и содержимое текстового поля сбрасываются.
532 Глава 15 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 // Листинг 15.6: XmlDom.cs // Демонстрация манипуляций деревом DOM using System; using System.Windows.Forms; using System.Xml; using System.IO; using System.CodeDom.Compiler; // содержит TempFileCollection // класс Xml Dorn демонстрирует DOM public class XmlDom : System.Windows.Forms.Form { private System.Windows.Forms.Button buildButton; private System.Windows.Forms.Button printButton; private System.Windows.Forms.Treeview xmlTreeView; private System.Windows.Forms.TextBox consoletextBox; private System.Windows.Forms.Button resetButton; private System.ComponentModel.Container components = null; private XmlDocument source; // ссылка на "документ XML” // ссылка copy "документа XML" источника private XmlDocument copy; private TreeNode tree; // ссылка TreeNode public XmlDom() { InitializeComponent(); // создание документа XML и загрузка letter.xml source = new XmlDocument(); source. Load (".. . Wetter. xml") ; // инициализация ссылок на null copy = null; tree = null; } // конец XmlDom [STAThread] static void Main() { Application.Run (new XmlDomf));. } // обработчик события buildButton click private void buildButton_Click(object sender, System.EventArgs e) { // определение наличия созданной копии if (copy != null) return; // документ уже существует // создание XmlDocument и TreeNode copy = new XmlDocument(); tree = new TreeNode(); // добавление имени корневого узла в TreeNode и // добавление TreeNode в элемент управления TreeView tree.Text = source.Name; xmlTreeView.Nodes.Add(tree);
Язык XML 533 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 // построение иерархии узлов и дерева BuiIdTree(source, copy, tree); printButton.Enabled = true; resetButton.Enabled = true; } // конец buildButton_Click \ // обработчик события printButton click private void printButton_Click(object sender, System.EventArgs e) { // выход, если копия не ссылается на XmlDocument if (copy = null) return; // создание временного файла XML TempFileCollection file = new TempFileCollection(); // создание файла, удаляемого при остановке программы file.AddExtension("xml", false); string[] filename = new string[1]; file.СоруТо(filename, 0); // запись данных XML на диск XmlTextWriter writer = new XmlTextWriter(filename[0], System.Text.Encoding.UTF8); copy.WriteTo(writer); writer.CloseO; // синтаксический анализ и загрузка временного документа XML XmlTextReader reader = new XmltextReader(filename[0]); // считывание, форматирование и отображение данных while(reader.Read()) { if (reader.NodeType == XmlNodeType.EndElement) consoleTextBox.Text += "/"; if (reader.Name 1= String.Empty) \ consoleTextBox.text += reader.Name + "\r\n"; if (reader.Value != String.Empty) consoleTextBox.Text += "\t" + reader.Value + ”\r\n"; } // конец while reader.Close(); } // конец printButton_Click // обработка события buildButton click private void resetButton_Click(object sender, System. EventArgs e) { JI удаление узлов TreeView if (tree != null) xmlTreeView. Nodes. Remove (tree); xmlTreeView.Refresh(); // вынужденное обновление TreeView // удаление XmlDocument и tree copy = null; tree = null; consoleTextBox.Text = ""; // сброс текстового поля
534 Глава 15 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 pnntButton. Enabled = false; resetButton.Enabled = false; } // конец resetButton_Click // построение дерева DOM private void BuildTree(XmlNode xmlSourceNode, XmlNode document, TreeNode treeNode) { // создание XmlReader для доступа к документу XML XmlNodeReader nodeReader =. new XmlNodereader( xmlSourceNode); // представление текущего узла"в дереве DOM XmlNode curгentNode - null; // treeNode для добавления в существующее дерево TreeNode newNode => new TreeNode(); // ссылки, модифицировавшие тип узла для CreateNode XmlNodeType modifiedNodeType; while (nodereader.Read()) { // получение текущего типа узла modifiedNodeType = nodereader.NodeType; // проверка на предмет EndElement, сохранение как Element if (modifiedNodetype == XmlNodeType.EndElement) modifiedNodeType = XmlNodeType.Element; // создание копии узла currentNode' = copy.CreateNode(modifiedNodeType, nodeReader.Name, nodereader.NamespaceURI); // построение дерева на основе типа узла switch (nodereader.NodeType) { // при узле Text, добавить его значение в дерево case XmlNodeType.text: newNode.text = nodereader.Value; treeNode.Nodes.Add(newNode); // добавление значения узла Text к данным текущего узла ((XmlText) currentNode).AppendData( nodereader.Value); document.AppendChild(currentNode); break; // если EndElement, переместить дерево case XmlNodeType.EndElement: document = document.ParentNode; treeNode = treeNode.Parent; break; // при новом элементе добавить 1аля и просмотреть дерево case XmlNodeType.Element: // определение содержимого в элементе if (!nodeReader.IsEmptyElement) { // присвоение текста узла, добавление потомка newNode newNode.Text = nodeReader.Name; treeNode.Nodes.Add(newNode);
Язык XML 535 193 // установка treeNode в последнего потомка 194 treeNode = newNode; 195 196 document.AppendChiId(currentNode); 197 document = documewnt.Lastchild; 198 } 199 else // не просматривать пустые элементы 200 { 201 // присвоение newNode строки NodeType 202 newNode.Text = 203 nodeReader.NodeType.Tostring(); 204 205 treeNode.Nodes.Add(newNode); 206 document.AppendChild(currentNode); 207 } 208 209 break; 210 211 // все прочие типы, отображение типа узла 212 default: 213 newNode.text = nodereader.NodeType.ToString(); 214 treeNode.Nodes.Add(newNode); 215 document.AppendChiId(currentNode); 216 break; 217 } // конец switch 218 219 newNode = new TreeNode(); 220 } // конец while 221 222 // обновление элемента управления TreeView 223 xmlTreeView.ExpandAll(); 224 xmlTreeView.Refresh(); 225 226 } // конец BuildTree 227 } // конец XmlDom В строках 20 и 23 создаются ссылки source и сору объекта XmlDocument. В строке 32 новый объект XmlDocument присваивается ссылке source. Затем в строке 33 активизируется метод Load для синтаксического разбора и за- грузки файла load.xml. Ссылка сору описывается ниже. К сожалению, XmlDocument не предоставляет функций для графического отображения содержимого. В данном примере содержимое документа отображается через элемент управления Treeview. Для представления каждого узла дерева используются объекты класса TreeeNode. Класс TreeView и TreeNode являются частью пространства имен Sys tern, windows. Forms. Классы TreeNode добавляются в TreeView для подчеркивания структуры документа XML. При нажатии кнопки Build запускается обработчик события buildButton_click (строки 47—68), динамично копирующий файл letter.xml. В строках 55 и 56 создаются новые объекты XmlDocument и TreeNode (т. е. узлы, используемые для графического представления Treeview). В строке 60 извлекается имя узла, на который дела- ется ссылка source (т. е. #root, представляющая корневой документ) и присваивается свойству Text типа tree. Затем объект TreeNode вставляется в список узлов элемента управления Treeview. Вызывается метод Add для добавления каждого нового объекта TreeNode в коллекцию узлов элемента управления Treeview. В строке 64 вызывается метод BuildTree для копирования класса XMLDocument, на который сделана ссылка source, и для обновления элемента управления Treeview. Метод BuildTree (строки 134—226) принимает объект XmlNode, представляющий исходный узел, пустой объект XmlNode и параметр treeNode для размещения в дереве DOM. Параметр treeNode делает ссылку на текущее ме- стоположение в дереве (т. е. на самый последний объект TreeNode, добавленный в элемент управления Treeview). В строках 138 и 139 создается новый класс XmlNodeReader для итерации через дерево DOM. В стро- ках 142—145 объявляются ссылки XmlNode и TreeNode, указывающие новые узлы, добавленные в document (т. е. дерево DOM, на которое сделана ссылка сору) и treeNode. Строки 150—220 итерируются через каждый узел дерева.
536 Глава 15 в Рис. 15.6. Создание DOM-дерева: а — загруженное дерево и нажатие кнопки Build; б — копия файла letter.xml; в — отображение значений XML-элементов после нажатия кнопки Print В строках 153—161 создается узел, содержащий копию текущего узла nodeReader Метод createNode класса XmlDocument принимает $ качестве аргументов NodeType, Name и NamespaceURI. NodeType не может принадлежать к типу EndElement. Если NodeType принадлежит к типу EndElement, тогда в строках 156 и 157 объекту modif iedNodeType присваивается тип Element. Оператор switch в строках 164—217 определяет тип узла, создает, добавляет узлы в класс Treeview и обновляет дерево DOM. При появлении текстового узла свойству Text нового объекта newNode класса TreeNode присваива- ется значение текущего узла. Этот класс TreeNode добавляется к элементу управления Treeview. В строках 172—174 объект currentNode приводится в XmlText и добавляется значение узла. Затем объект currentNode добавляется в document. В строках 178—181 тип узла EndElement сопоставляется. Этот случай (case) перемещает дерево вверх, потому что был достигнут конец элемента. Свойства ParentNode и Parent из- влекают родителей объектов document и treeNode соответственно. В строке 184 сопоставляются типы узлов Element. Каждый не пустой тип Element NodeType (строка 187) увели- чивает глубину дерева; следовательно, текущий объект nodeReaderName присваивается свойству Text объекта newNode, и последний добавляется в список узлов treeNode. В строках 104—197 порядок узлов в списке меняет- ся для того, чтобы объект newNode был последним TreeNode в списке. Объект XmlNode currentNode добавляется в document в качестве последнего потомка, и документ устанавливается на свойство Lastchild, являющееся толь- ко что добавленным узлом-потомком. Если это пустой элемент (строка 199), тогда свойству Text объекта newNode присваивается строковое представление класса NodeType. Затем newNode добавляется в список узлов treeNode. В строке 206 в document добавляется объект currentNode. Случай default присваивает строковое представление типа узла свойству Text класса NewNode, добавляет объект newNode в список узлов TreeNode и до- бавляет в document объект currentNode. После построения деревьев DOM список узлов TreeNode отображается в элементе управления Treeview. При щелчке кнопкой мыши на узлах (на рамки со знаками + или -) в элементе управления Treeview они разворачи- ваются или сворачиваются.
Язык XML 537 Нажатие кнопки Print активизирует обработчик события printButton Click (строка 71). В строках 79—84 соз- дается временный файл для сохранения XML. В строке 87 создается класс XmlTextWriter для передачи потока данных XML на диск. Вызывается метод WriteTo для записи представления XML в поток XmlTextWriter (стро- ка 89). В строке 93 создается класс xmlTextReader для считывания данных из файла..Цикл while (строки 96— 107) считывает каждый узел в дереве DOM и записывает теговые имена и символьные данные в текстовое поле. Если это конечный элемент, то вводится символ косой черты. Если узел имеет параметры Name или value, то это имя или значение добавляется к тексту поля. Обработчик события resetBitton_click, активизируемый кнопкой Reset, удаляет динамически созданные де- ревья и обновляет отображение элемента управления Treeview. Ссылке сору присваивается значение null (для того чтобы отправить дерево "в корзину" в строке 123), и ссылке tree списка узлов TreeNode также присваива- ется значение null. Несмотря на то, что объект XmlReader включает в себя методы для считывания и модификации значений узлов, это — не самый эффективный способ размещения данных в дереве DOM, .NET Framework предоставляет в про- странстве имен System.Xml.XPath класс XPathNavigator для итераций через списки узлов, соответствующие критериям поиска, которые записываются как выражение XPath. XPath (XML Path Language, язык указания пу- ти XML) предоставляет синтаксис для эффективного и производительного размещения определенных узлов в документах XML. XPath — это строковый язык выражений, используемый в XML и многих связанных с ним технологиях (например, XSLT, рассматриваемой в разд. 15.6). В листинге 15.7 запрограммирован процесс навигации в документе XML с помощью класса XPathNavigator. Подобно программе из листинга 15.6, в данном приложении также используется элемент управления Treeview и объекты TreeNode для отображения структуры документа XML. Однако, вместо отображения всего дерева DOM целиком, список узлов TreeNode обновляется всякий раз при позиционировании класса XPathNavigator на новом узле. В элемент управления TreeVigw добавляются узлы и удаляются для отображения местоположения класса XPathNavigator в дереве DOM. Документ XML sports.xml, используемый в этом примере, представлен в листин- ге 15.8. -т-........ - • • Листинг 15.7. Класс XPathNavigator, используемый для перемещения по выбранным узлам • - — — — -----1 —_ ...ъ.3k.— • ШайСйм 1 // Листинг 15.7: PathNavigator.es 2 // Демонстрация класса PathNavigator 3 4 using System; 5 using System.Windows.Forms; 6 using System.Xml.XPath; // содержит XPathNavigator 7 8 public class PathNavigator : System.Windows.Forms.Form 9 { 10 private System.Windows.Forms.Button firstChildButton; 11 private System.Windows.Forms.Button parentButton; 12 private System.Windows.Forms.Button nextButton; 13 private System.Windows.Forms.Button previousButton; 14 private System.Windows.Forms.Button selectButton; 15 private System.Windows.Forms.TreeView pathTreeViewer; 16 private System.Windows.Forms.ComboBox selectComboBox; 17 private System.ComponentModel.Container components = null; 18 private System.Windows.Forms.TextBox selectTreeViewer; 19 private System.Windows.Forms.GroupBox navigateBox; 20 private System.Windows.Forms.GroupBox locateBox; 21 22 // навигатор для прохождения документа 23 private XPathNavigator xpath; 24 25 // ссылка на документ для использования XPathNavigator 26 private XPathDocument document; 27 28 // ссылка на список- TreeNode, используемый элементом TreeView 29 private TreeNode tree; 30 31 public PathNavigator() 32 { 33 InitializeComponent(); 34
538 Глава 15 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 I/ загрузка документа XML document = new XPathDocument.Wsports .xml"); // создание навигатора xpath = document.CreateNavigator(); // создание корневого узла для TreeNodes tree = new TreeNode(); tree.Text = xpath.NodeType.ToString(); // корень pathThreeViewer.Nodes.Add(tree); // добавление дерева // обновление элемента управления TreeView pathTreeViewer.ExpandAll(); pathTreeViewer.Refresh(); pathTreeViewer..SelectNode = tree; // выделение корня } // конец конструктора [STAThread] static void Main() { Application.Run(new PathNavigator()); } // прохождение к первому потомку private void firstChildButton_Click(object sender, System. EventArgs e) { TreeNode newTreeNode; // переход к первому потомку if (xpath.MoveToFirstChild()) ( newTreeNode = new TreeNodeО; // создание нового узла // установка свойства Text узла //на имя навигатора или на значение DetermineType(newTreeNode, xpath); // добавление узла в список узлов TreeNode tree.Nodes.Add(newTreeNode); tree = newTreeNode; // присвоение дерева newTreeNode // обновление элемента управления TreeView pathTreeViewer.ExpandAll(); pathTreeViewer.Refresh(); pathTreeViewer.SelectedNode = tree; } else // узел не имеет потомков MessageBox.Show("Current Node has no children.", "", MessageBoxButtons.OK, MessageBoxIcftn.Information); } // прохождение к родителю узла при событии parentButton click private void parentButton_Click(object sender, System.EventArgs e) { // перемещение к родителю if (xpath.MoveToParent()) { tree = tree.Parent;
Язык XML 539 98 // получение числа узлов-потомков, не включая поддеревья 99 int count = tгее.GetNodeCount(false); 100 101 // удаление всех потомков , 102 tree.Nodes.Clear(); 103 104 // обновление элемента управления TreeView 105 pathTreeViewer.ExpandAll(); 106 pathTreeViewer.Refresh(); 107 pathTreeViewer.SelectedNode = tree; 108 } 109 else // если узел не имеет родителя (корневого узла) 110 MessageBox.Show("Current node has no parent.", "", 111 MessageBoxButtons.OK, 112 MessageBoxIcon.Information); 113 } 114 115 11 найти следующего "брата" при событии nextButton_click 116 private void nextButton_Click(object sender, 117 System.EventArgs e) 118 { 119 TreeNode newTreeNode = null, newNode = null; 120 121 // переход к следующему "брату" 122 if (xpath.MoveTonext()) 123 { 124 nextTreeNode = tree.Parent; // получение узла-родителя 125 126 newnode = new TreeNodeО; // создание нового узла 127 DetermineType(newNode, xpath); 128 newTreeNode.Nodes.Add(newnode); 129 130 // установка текущей позиции для отображения 131 tree = newNode; 132 133 // обновление элемента управления TreeView 134 pathTreeViewer.ExpandAll(); 135 pathTreeViewer.Refresh(); 136 pathTreeViewer.SelectedNode « tree; 137 } 138 else // узел не имеет дополнительных "братьев" 139 MessageBox.Show("Current node is last sibling.", 140 "", MessageBoxButtons.OK, 141 MessageBoxIcon.Information); 142 } // конец события nextButton_Click 143 144 // получение препниутпего "брата" при событии previousButton_click 145 private void previousButton_Click(object sender, 146 System.EventArgs e) 147 { 148 TreeNode parentTreeNode = null; 149 150 // переход к предыдущему "брату" 151 if (xpath.MoveToPrevious()) 152 { _ 153 parentTreeNode = tree.Parent; 11 получение узла-родителя 154 155 // удаление текущего узла 156 parentTreeNode.Nodes.Remove(tree); 157 158 // переход к следующему узлу 159 tree = parentTreeNode.LastNode; 160
540 Глава 15 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 П обновление элемента управления Treeview pathTreeViewer.ExpandAll(); pathTreeViewer.Refresh(); pathTreeViewer.SelactedNode = tree; } else // если текущий узел не имеет предыдущих "братьев" MessageBox.Show("Current node is first sibling.", "", MessageBoxButtons.OK, MessageBoxIcon.Information); } // конец события previousButton_Click // обработка события selectButton_click private void selectButton_Click(object sender, System.EventArgs e) { XPathNodelterator iterator; // обеспечивает итерацию узла // получение указанного узла из ComboBox try { ч iterator = xpath.Select(selectComboBox.Text); Displayiterator(iterator); // выбор печати } // захват недействительных выражений catch (System.ArgumentException argumentException) { MessageBox.Show(argumentException.Message, "Error", MessageBoxButtons.OK, MessageBoxIcon.Error); } } // конец события selectVutton_Click 11 печать значений для XPathNodelterator private void Displayiterator(XPathNodelterator iterator) { selectTreeViewer.Text = ""; // печать значений выбранного узла while (iterator.MoveNext()) selectTreeViewer.Text += iterator.Current.Value.Trim() + "\r\n"; ) // конец Displayiterator // определение необходимости отображения TreeNode имени // или значения текущего узла private void DetermineType(TreeNode node, XPathNavigator xPath) { // определение класса NodeType switch (xPath.NodeType) { // если Element, получить имя case XPathNodeType.Element: // получение имени текущего узла и удаление пробелов node.Text = xPath.Name.Trim(); break; // получение значений узла default:
Язык XML 541 224 // получение значения текущего узла и удаление пробелов 225 node.Text = xPath.Value.Trim(); 226 break; 227 228 } // конец switch 229 } // конец DetermineType 230 } // конец PathNavigator 1 Листинг 15.8. Документ XML, описывающий различные виды спорта 1 <?xml version = "1.0"?> 2 3 <1— Листинг 15.8: sports.xml —> 4 <!— База данных спорта —> 5 6 <sports> 7 8 <game id = "783"> 9 <name>Cricket</name> 10 11 <paragraph> 12 More popular among commonwealth nations. 13 </paragraph> 14 </game> 15 16 <game id = "239"> 17 <name>Baseball</name> 18 19 <paragraph> , 20 More popular in America. 21 </paragraph> 22 </game> 23 24 <game id = "418"> 25 <name>Soccer(Futbol)</name> 26 <paragraph>Most popular sport in the world</paragraph> 27 </game> 28 </sports> Программа загружает XML-документ sports.xml в объект xPathDocument путем передачи имени файла документа в конструктор XPathDocument (строка 36). С помощью метода CreateNavigator (строка 39) создается ссылка XPathNavigator и возвращается в структуру дерева XPathDocument. Методы навигации класса XPathNavigator, использованные в листинге 15.7, таковы: MoverToFirstChild (стро- ка 66), MoveToParent (строка 94), MoveToNext (строка 122) и MoveToPrevious (строка 151). Каждый из этих мето- дов выполняет операцию, обозначаемую их названиями. Метод MoveToFirstChild переходит к первому потомку узла, на который сделал ссылку класс XPathNavigator, MoveToParent переходит к родителю узла, на которую сделал ссылку класс XPathNavigator, MoveToNext переходит к следующему "брату" узла, на который сделана ссылка классом XPathNavigator, a MoveToPrevious переходит к предыдущему "брату", на которого сослался класс XPathNavigator. Каждый из этих методов возвращает значение bool, указывающее на успех перехода. В данном примере в окне Сообщений (MessageBox) всякий раз при сбое операции отображается предупреждение. Более того, каждый из этих методов вызывается в обработчике событий кнопок, соответствующих их названиям (например, кнопка First Child (рис. 15.7) запускает событие firstChildButton Click, вызывающее метод MoveToFirstChi Id). При передвижении с помощью XPathNavigator в случае с методами MoveToFirstChild и MoveToNext в список узлов TreeNode добавляются узлы. Метод DetermineType принадлежит к типу private (задан в строках 208— 229) и определяет, какое свойство класса Node присваивать классу TreeNode: Name или Value (строки 218 и 225). При вызове MoveToParent отображение всех потомков узла-родителя отменяется. Точно так же вызов метода MoveToPrevious удаляет текущий узел-брат. Обратите внимание, что узлы удаляются только из класса Treeview, а не из представления дерева в документе.
542 Глава 15 Рис. 15.7. Демонстрация навигации по выбранным узлам: а — нажатие кнопки First Child; б — выбор узла; в — нажатие кнопки First Child после выбора узла г — выбор узла /sports; д — выбор узла /sports/game [name= * Cricket' ]
Язык XML 543 Другой обработчик событий относится к кнопке Select (строки 173 и 174). Метод Select (строка 182) принима- ет критерии поиска либо в форме XPathExpression, либо строки string, представляющей выражение XPath, и возвращается как объект XPathNodeiterator, обозначающий любые узлы, соответствующие критериям поиска. Выражения XPath, представленные в раскрывающемся списке данной программы, в общем виде описаны в табл. 15.1. Метод Displayiterator (определен в строках 195—204) добавляет значения узлов из имеющегося XPathNodeiterator в текстовое окно selectTreeViewer. Обратите внимание на вызов метода Trim типа string для удаления ненужных символов пробела. Метод MoveNext (строка 200) вводится перед следующим узлом, дос- туп к которому может быть осуществлен с помощью свойства Current (строка 202). Таблица 15.1. Выражения XPath Выражение Описание /sports Соответствует узлу sports, являющемуся узлом-потомком корневого узла доку- мента. Данный узел содержит корневой элемент /sports/game/пате Соответствует всем узлам пате, являющимся узлами-потомками узла дате. Узел дате должен быть потомком узла sports, а узел sports должен быть узлом корневого элемента /sports/game/paragraph Соответствует всем узлам paragraph, являющимся узлами-потомками узла game: Узел game должен быть потомком узла sports, а узел sports должен быть узлом корневого элемента /sports/game [name=* Cricket' 1 Соответствует всем узлам game, содержащим элемент-потомок name со значе- нием Cricket. Узел game должен быть потомком узла sports, а узел sports должен быть узлом корневого элемента 15.5. Document Type Definition, схемы и проверка Документы XML могут ссылаться на вспомогательные документы, описывающие процесс структурирования документов XML. Такие вспомогательные документы называются определениями типов документов (Document Type Definition, DTD) и схемами. При наличии DTD или схемы некоторые анализаторы (называются проверяю- щими анализаторами) считывают DTD или схемы и проверяют относительно них структуру документа. Если документ XML соответствует DTD или схеме, тогда он считается допустимым. Анализаторы, которые не могут проверить соответствие документа относи пльно DTD или схемы, называются непроверяющими анализатора- ми. Если анализатор XML (проверяющий или непроверяющий) может обработать документ XML (не ссылаю- щийся на DTD или схему), тогда считается, что документ XML является правильно построенным (т. е. синтак- сически корректным). По определению, допустимый документ XML одновременно является правильно постро- енным. Если документ построен неправильно, синтаксический анализ прерывается, и анализатор выдает сообщение об ошибке. Замечание по технологии программирования__________________________________________ Документы DTD и схемы являются важными компонентами для документов XML, используемых в транзакциях между компаниями и в ответственных системах. Эти документы помогают подтвердить действительность доку- ментов XML. Замечание по технологии программирования__________________________________________ Из-за того что содержимое документа XML можно структурировать многими способами, программное приложе- ние не может определить фактов отсутствия каких-либо частей передаваемых в него данных, а также нужного порядка их сортировки. QTD и схемы решают эту проблему предоставлением расширенных средств описания содержимого того или иного документа. Приложение может воспользоваться документом DTD или схемой для выполнения проверки допустимости содержимого документа. 15.5.1. Определения типа документа Определения типов документов (DTD) предоставляют средство проверки типов документов XML, подтверждая их допустимость (все элементы содержат надлежащие атрибуты, элементы расположены в нужном порядке и т. д.). DTD используют грамматику EBNF (Extended Backus — Naur Form, расширенная форма Бэкуса — Наура) для описания содержимого документа XML. Анализаторам XML требуется дополнительная функцио-
544 Глава 15 наивность для чтения грамматики EBNF, потому что ее синтаксис отличается от синтаксиса XML. Несмотря на необязательность DTD, их рекомендуется использовать для подтверждения соответствия документа. DTD, пред- ставленное в листинге 15.9, определяет набор правил (т. е. грамматику) для структурирования документа дело- вого письма, содержащегося в листинге 15.10. Совет по повышению портативности____________________________________________________________ DTD обеспечивают непротиворечивость документов XML, сгенерированных разными программами. .................... v "- .............................................. . . -----..... ....................................................... —.... ; Листинг 15.9. Определение типа документа для делового письма Ж&ф U м...... ..’.(....«.....к.. ..... .. . . ..в..', .к...... ..В.............'';.)ЛН....ПЛ....^В....М...Н. . 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 <!— Листинг 15.9: letter.dtd <!— Документ DTD для letter.xml <!ELEMENT letter (contacts salutation, paragraphs closing, signature)> <!ELEMENT contact (name, addressl, address2, city, state, zip, phone, flag)> <!ATTLIST contact type CDATA #IMPLIED> <!ELEMENT name (#PCDATA)> <!ELEMENT addressl (#PCDATA)> <!ELEMENT address2 (#PCDATA)> <!ELEMENT city (#PCDATA) > <!ELEMENT state (#PCDATA)> <!ELEMENT zip (#PCDATA)> <! ELEMENT^ phone (#PCDATA)> <!ELEMENT flag EMPTY> <!ATTLIST flag gender (M | F) "M"> <!ELEMENT salutation (#PCDATA)> <! ELEMENT closing (#P£DATA)> <!ELEMENT paragraph (#PCDATA)> <!ELEMENT signature (#PCDATA)> В строке 4 (листинг 15.9) используется объявление типа элемента element для определения правил для эле- мента letter. В данном случае letter содержит один или несколько элементов contact, один элемент salutation, один или несколько элементов paragraph и один элемент signature в приведенной последователь- ности. Индикатор вхождения (+) указывает на то, что элемент должен появиться один или несколько раз. Дру- гие индикаторы включают в себя звездочку (*), указывающую на необязательный элемент, который может по- явиться любое количество раз, а также знак вопроса (?), указывающий на необязательный элемент, который может появиться максимум один раз. Если индикатор вхождения не используется, то ожидается только одно появление элемента. Определение элемента contact (строка 7) указывает на то, что он содержит элементы name, addressl, address2, city, state, zip, phone и flag — в указанном порядке. Ожидается однократное появление каждого из этих эле- ментов. В строке 9 используется объявление типа элемента attlist для определения атрибута для элемента contact. Ключевое слово #implied указывает, что, если анализатор обнаруживает элемент contact без атрибута type, тогда приложение может предоставить значение отсутствующему атрибуту или проигнорировать его. Отсутст- вие атрибута type не может сделать документ недопустимым. В число других типов значений по умолчанию входят #required и # fixed. Ключевое слово #required указывает, что в документе должен присутствовать атри- бут, а ключевое слово #fixed означает, что атрибуту (если он имеется) всегда должно быть присвоено особое значение. Например, выражение <!ATTLIST address zip #FIXED "01757"> указывает, что для атрибута zip должно использоваться значение 01757; в противном случае документ будет недопустимым. Если атрибут отсутствует, тогда анализатор по умолчанию использует фиксированное значение, указанное в объявлении attlist. Признак cdata означает, что атрибут type содержит строку, не обрабатывае- мую анализатором, а передаваемую в программу как есть.
Язык XML 545 Замечание по технологии программирования_______________________________________________ Синтаксис DTD не предоставляет механизма описания типа данных элемента (или атрибута). Признак #pcdata (строка 11) указывает, что элемент может сохранять синтаксически проанализированные сим- вольные данные (т. е. текст). Такие данные не могут содержать разметки. Символы "меньше" (<) и знак & долж- ны заменяться их логическими объектами (т. е. &lt; и &атр;). Впрочем, знак & можно вводить с логическими объектами. Список предварительно определенных логических объектов имеется в приложении 13. В строке 18 определяется пустой элемент с именем flag. Ключевое слово empty указывает, что данный элемент не может содержать символьных данных. Обычно пустые элементы используются для их атрибутов. Распространенная ошибка программирования_______________________________________________ Результатом неявного определения DTD любого элемента, атрибута или отношения является недопустимый до- кумент. Многие документы XML делают явные ссылки на DTD. В листинге 15.10 представлен документ XML, соответ- ствующий определению letter.dtd (см. листинг 15.9). 1 <?xml version = "1.0"?> 2 • 3 <!— Листинг 15.10: letter2.xml —> 4 <!— Деловое письмо, отформатированное XML —> 5 6 c’DOCTYPE letter SYSTEM "letter.dtd"> 7 8 <letter> 9 <contact type = "from”> 10 <name>Jane Doe</name> 11 <addressl>Box 12345</addressl> 12 <address2>15 Any Ave.</address2> 13 <city>Othertown</city> 14 <state>Otherstate</state> 15 <zip>67890</zip> 16 <phone>555-4321</phone> 17 <flag gender = ”F" /> 18 </contact> 19 20 <contact type = "to"> 21 <name>John Doe</name> 22 <addressl>123 Main St.</addressl> 23 ‘ <address2x/address2> 24 <city>Anytown</city> 25 <state>Anystate</state> 26 <zip>12345</zip> 27 <phone>555-1234</phone> 28 <flag gender = "M" /> 29 </contact> 30 31 <salutation>Dear Sir:</salutation> 32 33 <paragraph> It is our privilege to inform you about our new 34 database managed with XML. This new system 35 allows you to reduce the load on your inventory list 36 server by having the client machine perform the work of 37 sorting and filtering the data. 38 </paragraph> 39 40 <paragraph>Please visit our Web site for availability 41 and pricing. 42 </paragraph> 43 <closing>Sincerely</closing> 44 <signature>Ms. Doe</signature> / 45 </letter> 35 Зак. 3333
546 Глава 15 Данный документ XML похож на представленный в листинге 15.2. В строке 6 сделана ссылка на файл DTD. Эта разметка содержит три части: имя корневого элемента (letter в строке 8), к которому применяется DTD, клю- чевое слово SYSTEM (обозначающее в данном случае внешнее DTD — определенное в отдельном файле), а также название и местоположение DTD (т. е. letter.dtd в текущем каталоге). Несмотря на то, что применяться могут любые расширения файлов, документы DTD обычно имеют расширение dtd. Различные инструментальные средства (многие из которых поставляются бесплатно) помогают проверить соот- ветствие документа относительно DTD и схем (рассматриваются далее). Выходные данные, представленные на рис. 15.8, демонстрируют результаты проверки документа letter2.xml с помощью XML Validator от Microsoft. Список инструментов проверки представлен на сайте www.w3.org/XML/Schema.html. XML Validator можно бесплатно загрузить с сайта msdn.microsoft.com/downloads/samples/Internet/xml/xml_validator/sample.asp. XML Validator может подтверждать допустимость документов XML относительно DTD локально, либо при за- грузке документов на сайт с XML Validator. В рассматриваемом случае letter2.xml и letter.dtd помешаются в папку C:\XML\. Данный документ XML (letter2.xml) является правильно построенным и соответствует letter.dtd. Enter a url to load: |/xm|/letter2.xml or paste In some XML. PASTE | check the "Validation" box if you want to validate your document: 3; P Validation £ click the "Validate" button to see if your text is valid XML: VAubArt. 1 Your XML is well formed and is validated Pl: xml COMMENT: Fig. 18.13:1etter2.xml COMMENT: Business letter formatted with XML SCHEMA tetter ELEMENT: letter Рис. 15.8. XML Validator подтверждает допустимость документа XML относительно DTD Документы XML, не прошедшие проверку, по-прежнему являются правильно построенными документами. Ко- гда документ не соответствует DTD или схеме, XML Validator выдает сообщение об ошибке. Например, DTD, представленное в листинге 15.9, указывает, что элемент contacts должен содержать элемент-потомок name. Ес- ли данный элемент-потомок в документе отсутствует, то документ построен правильно, но недопустим. При таком сценарии XML Validator отображает сообщение об ошибке, показанное на рис. 15.9. Рис. 15.9. XML Validator, выдающий сообщение об ошибке
Язык XML 547 Для проверки XML-документов программы C# могут использовать msxml. Подробности использования описаны на сайте msdn.microsoft.com/library/defau!Lasp?url=/library/en-us/cpguidnf/html /cpconvalidationagainstdtdwithxmlvalidatingreader.asp. Для определения структур XML-документов в .NET предпочтительны схемы. Несмотря на существование не- скольких типов схем, самыми популярными являются Microsoft Schema и W3C Schema. 15.5.2. Microsoft XML Schema В данном разделе1 представлена альтернатива DTD — схема (Schema)— для определения структуры докумен- тов XML. Многие разработчики сообщества пользователей XML считают, что DTD не хватает гибкости для соответствия современным потребностям программирования. Например, DTD нельзя манипулировать (напри- мер, осуществлять в них поиск, изменять программными способами и т. д.) так же, как документами XML, по- тому что DTD не являются документами XML. Более того, DTD не имеют функций для описания типа данных элемента (или атрибута). В отличие от DTD, в схемах не используется расширенная форма грамматики Бэкуса— Наура (Extended Backus — Naur Form, EBNF). Схемы представляют собой документы XML, которыми можно манипулировать (например, добавлять или удалять элементы и т. д.) так же, как любым документом XML. Схемы — так же, как DTD — требуют наличия проверяющих анализаторов. В данном разделе подробно рассматривается словарь XML Schema. В листинге 15.11 представлен документ XML, соответствующий документу Microsoft Schema из листинга 15.12. Традиционно в документах Microsoft XML Schema используется расширение xdr (от англ. XML-Data Reduced — сокращенные данные XML). В стро- ке 6 (листинг 15.11) сделана ссылка на Schema-документ book.xdr. Е- - уммщимюшт •—.........**м. ---.....V-’.......................................... Листинг 15.11. Документ XML, соответствующий документу Microsoft Schema Л V*^*.*.YU>K^wj..e*№*.№.. .......... . . . .* . ......... . .«Л.2'. ........ .......М...ГО..... ....... ............... *'.. ............... I. .... .............. . ........... . ..*№...№...rt... Л 1 <?xml version = "1.0”?> 2 3 <!— Листинг 15.11: bookxdr.xml —> 4 <!— Файл XML, размечающий данные о книгах —> 5 6 <books xmlns = "x-schema:book.xdr"> 7 <book> 8 <title>C# How to Program</title> 9 </book> 10 11 <book> 12 <title>Java How to Program</title> 13 </book> 14 15 <book> 16 <title>Visual Basic .NET How to Program</title> 17 </book> 18 19 <book> 20 <title>Advanced Java 2 Platform How to Program</title> 21 </book> 22 23 <book> 24 <title>Python How to Program</title> 25 </book> 26 </books> 1 <?xml version = "1.0"?> 2 3 4 5 <!— Листинг 15.12: book.xdr —> <!— Документ Schema, которому соответствует book.xml —> 1 W3C Schema, рассматриваемая в разд. 15.5.3, постепенно становится промышленным стандартом описания структуры докумен- тов XML. Предполагается, что в ближайшие два года большинство разработчиков будет пользоваться W3C Schema.
548 Глава 15 6 <schema xmlns = "urn:schemas-microsoft-com:xml-data"> <ElementType name = "title" content = "textOnly" 8 model = "closed" /> 10 <ElementType name = "book" content = "eltOnly" model = "closed"> 11 <element type = "title" minOccurs = "1" maxOccurs = "1" /> 12 </ElementType> 13 14 <ElementType name = "books" content = "eltOnly" model = "closed"> 15 <element type = "title" minOccurs = "0" maxOccurs = /> 16 </ElementType> 17 </schema> Замечание по технологии программирования______________________________________________ Схемы являются документами XML, соответствующими DTD и определяющими структуру схемы. Такие DTD, объединенные с анализатором, используются для проверки авторских схем Замечание по технологии программирования______________________________________________ Многие организации и отдельные программисты создают DTD и схемы для широкого диапазона категорий доку- ментов (например, для обработки финансовых операций, медицинских рецептов и т. д ). Часто такие коллекции, называемые хранилищами, доступны для загрузки из Интернета1. В строке 6 корневой элемент schema начинает разметку Schema. В Microsoft Schema используется пространство имен URI "uri:schemas-microsoft-com: xml -data”. В строке 7 описан элемент ElementType для определения элемента title. Атрибут contents указывает, что данный элемент содержит синтаксически проанализирован- ные символьные данные (т. е. только текст). Элемент title не может содержать элементов-потомков. Установ- ка атрибута model на значение "closed" указывает, что соответствующий документ XML может содержать только элементы, определенные в этой схеме. В строке 10 определяется элемент book; содержимое (content) этого элемента — "только элементы" (т. е. eltOnly). Это означает, что данный элемент не может иметь смешан- ного содержимого (т. е. текста и прочих элементов). В рамках элемента ElementType с именем book элемент element указывает, что title является элементом-потомком (child) элемента book. Значения атрибутов minOccurs и maxOccurs установлены на "1"; это означает, что элемент book должен содержать только элемент title. Символ * в строке 15 указывает на то, что схема разрешает наличие любого количества элементов book в элементе books. В разд. 15.5.4 рассматривается процесс проверки bookxdr.xml относительно book.xdr. 15.5.3. W3C XML Schema В данном разделе1 2 рассматривается схема W3C XML Schema3, созданная W3C. XML Schema — это "Рекомен- дация" (т. е. стабильный релиз, подходящий для промышленного использования). В листинге 15.13 представлен допустимый в соответствии со схемой документ XML с именем bookxsd.xml, а в листинге 15.14 — документ W3C XML Schema (book.xsd), определяющий структуру для bookxsd.xml. Авторы схем могут использовать для них любые расширения файлов, но схемы XML W3C обычно используют расширение xsd. В следующем разде- ле рассматривается процесс проверки bookxsd.xml относительно book.xsd. 1 <?xml version = "1.0"?> 2 3 <!— Листинг 15.13: book.xsd —> 4 <!— Документ, соответствующий W3C XML Schema —> 5 6 <deitel:books xmlns:deitel = "http://www.deitel.com/booklist"> 7 <book> 8 <title>e-Business and e-Commerce How to Program</title> 9 </book> 1 См., например, opengis.net/schema.htm. 2 Подробно W3C Schema описывается в книге "XML for Experienced Programmers" (2003 г.). См. сайт издательства Deitel & Asso- ciates, Inc. по адресу www.deitel.com 3 Последняя информация о W3C XML Schema имеется на сайте www.w3.org/XML/Schema.
Язык XML i 549 10 <book> 11 <title>Python How to Program</title> 12 </book> 13 </deitel:books> 1 <?xml version = "1.0"?> 2 3 <!— Листинг 15.14: book.xsd —> 4 <!— Простой документ W3C XML Schema —> 5 6 <xsd:schema xmlns:xsd = "http://www.w3.org/2001/XMLSchema" 7 xmlns:deitel = "http://www.deitel.com/booklist" 8 targetNamespace = http://www/deitel.com/booklist"> 9 10 <xsd:element name = "books" type = "deitel: BooksType"/> 11 12 <xsd:complexType name = "BooksType"> 13 <xsd:sequence> 14 <xsd:element name = "book" type = "deitel:BookType" 15 minOccurs = "1" maxOccurs = "unbounded"/> 16 </xsd:sequence> 17 <xsd:complexType> 18 19 <xsd:complexType name = "BookType"> 20 <xsd:sequence> 21 <xsd:element name = "title" type = "xsd:string"/> 22 </xsd:sequence> 2 3 <xsd:complexType> 24 25 </xsd:schema> В схеме W3C XML используется пространство имен URI http://www/w3/org/2001/XMLSchema и часто префикс пространства имен xsd (строка 6 в листинге 15.14). Корневой элемент schema содержит элементы, определяю- щие структуру документа XML. В строке 7 URI http://www.deitel.com/booklist связывается с префиксом deitel пространства имен. В строке 8 задается пространство имен targetNamespace, являющееся пространством имен для определяемых схемой элементов и атрибутов. В W3C XML Schema элемент element (строка 10) определяет элемент. Атрибуты name и type указывают имя и тип данных элемента element соответственно. В данном случае имя элемента — books, а тип данных — deitel: BooksType. Любой элемент (например, books), содержащий атрибуты или элементы-потомки, должен задавать комплексный тип, определяющий, в свою очередь, каждый атрибут и элемент-потомок. Тип deitel: BooksType (строки 12—17) является примером комплексного типа. Перед BooksType ставится префикс deitel, потому что это созданный пользователем комплексный тип, а не существующий в W3C XML Schema. В строках 12—17 используется элемент complexType для определения типа элемента, имеющего элемент- потомок с именем book. Из-за того что book имеет элемент-потомок, его тип должен быть комплексным (напри- мер, ВоокТуре). Атрибут minOccurs указывает, что элемент books должен содержать минимум один элемент book. Атрибут maxOccurs со значением unbounded (строка 14) указывает, что элемент books может иметь любое количество элементов-потомков book. Элемент sequence указывает порядок элементов в комплексном типе. В строках 19—23 определяется комплексный тип complexType ВоокТуре. В строке 21 определяется элемент title с типом type:xsd: string. Когда элемент имеет простой тип, такой как xsd:string, то он не может иметь атрибутов и элементов-потомков. W3C XML Schema предоставляет большое количество типов данных, напри- мер, xsd:date для дат, xsd: int для целых чисел, xsd:double для чисел с плавающей точкой и xsd: time для вре- мени. О хорошем стиле программирования___________________________________________________________ Традиционно авторы W3C XML Schema используют префикс пространства имен xsd при обращении к URI http://www.w3.org/2001/XMLSchema,
550 I Глава 15 15.5.4. Проверка схемы в C# В данном разделе представлено программное приложение C# (листинг 15.14), в котором используются классы из библиотеки классов .NET Framework для проверки документов XML, представленных в двух последних раз- делах, относительно соответствующих им схем. Для выполнения проверки используется экземпляр класса XmlvalidatingReader. В строке 17 создается ссылка Xml Schemacollection с именем schemas В строке 28 вызы- вается метод Add для добавления объекта xmischema в коллекцию схемы. В метод Add передается имя, иденти- фицирующее схему (т. е. "book"), и имя файла схемы (т. е. book.xdr). В строке 29 вызывается метод Add для до- бавления W3C XML Schema. Первый аргумент указывает пространство имен URI (т. е. строка 18 в листин- ге 15.14), а второй аргумент идентифицирует файл схемы (т. е. book.xsd). Эта схема применяется для проверки документа bookxsd.xml. 1 // Листинг 15.15: ValidationTest.cs 2 // Проверка документов XML с помощью схем 3 4 using System; 5 using System.Windows.Forms; 6 using System.Xml; 7 using System .Xml. Schema; // содержит классы Schema 8 9 // определение допустимости схемы, документа XML 10 public class ValidationTest : System.Windows.Forms.Form 11 { s 12 private System.Windows.Forms.ComboBox filesComboBox; 13 private System.Windows.Forms.Button validateButton; 14 private Systern.Windows.Forms.Label consoleLabel; 15 private System.ComponentModel.Container components = null; 16 17 private XmkSchemaCollection schemas; // Schemas 18 private bool valid;// результат определения допустимости 19 20 public Validationtest() 21 < 22 InitializeComponent(); 23 24 valid = true; // документ, предположительно, допустимый 25 26 // получение схемы (схем) для проверки 27 schemas = new XmlSchemaCollection(); 28 schemas.Add("book”, "book.xdr"); 29 schemas.Add(http://www.deitel.com/booklist, "book.xsd"); 30 } // конец конструктора 31 32 // код, сгенерированный Visual Studio .NET 33 34 [STAThread] 35 static void Main() 36 { 37 Application.Run(new Validationtest()); 38 } // конец Main 39 40 // обработка события validateButton_Click 41 private void validationButton_Click(object sender, 42 System. EventArgs e) 43 { 44 // получение документа XML 45 XmlTextReader reader = 4 6 new XmlTextReader(filesComboBox.Text); 47
Язык XML 551 48 // получение проверяющего 49 XmlValidatingReader validator = 50 new XmlValidatingReader(reader); 51 52 // присвоение схемы (схем) 53 validator.Schemas.Add(schemas); 54 55 // задание типа проверки 56 validator.ValidationType = ValidationType.Auto; 575 85 // регистрация обработчика события для ошибок проверки 59 validator.ValidationEventHandler +«* 60 new ValidationEventHandler(ValidationError); 61 62 // проверка документа по узлам 63 while (validator.Read()) ; // пустое тело 64 65 // выяснение результата проверки 66 if (valid) 67 consoleLabel.text = "Document is valid"; 68 69 valid = trut; // сброс переменной 70 71 // закрытие потока reader 72 validator.Close(); 73 } // конец validateButton_Click 74 75 // обработчик события для ошибки проверки 76 private void ValidationError(object sender, 77 ValidationEventArgs argument) 78 { 79 consoleLabel.Text = arguments.Message; 80 valid = false; // сбой проверки 81 } // конец ValidationError 82 } // конец ValidationTest Рис. 15.10. Окно программы проверки документа с помощью схемы: а — проверка файла bookxsd.xml; б — проверка файла bookxdnxml В строках 45 и 46 создается класс xmlReader для файла, выбранного пользователем из раскрывающегося списка filesComboBox. Документ XML для проверки относительно схемы, содержащийся в XmlSchemaCollection, дол- жен быть передан в конструктор XmlValidatingReader (строки 49 и 50). В строке 53 добавляется (Add) коллекция схемы, на которую сделана ссылка объектом Schemas в свойстве Schemas. Данное свойство задает схему, используемую для проверки документа. Свойство ValidationType (строка 56) устанавливает значение константе перечисления ValidationType для автоматического (Auto) опре- деления типы схемы (XDR или XSD). В строках 59 и 60 регистрируется метод ValidationError с обработчиком события ValidationEventHandler. Метод ValidationError (строки 76—81) вызывается, если документ недопус- тим, либо при возникновении ошибок; например, документ невозможно найти. Сбой регистрации метода с об- работчиком ValidationEventHandler вызывает сброс исключения при отсутствии документа или его недопус- тимости. Проверка выполняется по узлам с помощью вызова метода Read (строка 63). При каждом обращении к методу Read осуществляется проверка очередного узла документа. Цикл прекращается либо после успешной проверки
552 Глава 15 всех узлов, либо при сбое проверки какого-либо узла. Проверка документов XML относительно соответствую- щих им схем (см. листинги 15.11 и 15.13) выполнена успешно. Вид окна программы представлен на рис. 15.10. В листингах 15.16 и 15.17 приведены два документа XML, не соответствующих book.xdr и book.xsd. В листин- ге 15.16 дополнительный элемент title в элементе book (строки 19—22) делает документ недопустимым. В листинге 15.17 документ также делается недопустимым из-за присутствия дополнительного элемента title в элементе book (строки 7—10). Несмотря на недопустимость, оба документы построены правильно. 1 <?хш1 version = "1.0"?> 3 <!— Листинг 15.16: bookxsdfail.xml —> 4 <!— Документ, не соответствующий схеме W3C —> 5 6 <deitel:books xmlns:deitel = "http://www.deite3.com/booklist"> 7 <book> 8 <title>e---Business and e-Commerce How to Program</title> 9 <title>C# How to Program</title> 10 </book> 11 <book> 12 <title>Python How to Program</title> 13 </book> 14 </deitel:books> Рис. 15.11. Проверка файла bookxsdfail.xml 1 <?xml version = "1.0"?> 2 3 <!— Листинг 15.17: bookxdrfail.xml —> 4 <!— Файл XML, не соответствующий схеме book.xdr —> 5 6 ebooks xmlns = "x-schema:book.xdr"> 7 <book> 8 <title>XML How to Program</title> 9 </book> 10 11 <book> 12 <title>Java How to Program, 4/e</title> 13 </book> 14 15 <book> 16 <title>Visual Basic .NET How to Program</title> 17 </book> 18 19 <book> 20 <title>C++ How to Program, 3/e</title> 21 <title>Python How to Program</title> 22 * </book> 23 24 <book> 25 <title>C# How to Program</title> 26 </book> 27 </books> Рис. 15.12. Проверка файла bookxdrfail.xml Результат проверки представлен на рис. 15.11 и 15.12
Язык XML 553 15.6. Язык XSL и класс XsITransform Язык XSL (Extensible Stylesheet Language, расширяемый язык стилей) — это словарь XML для форматирования данных XML. В этом разделе рассматривается часть XSL, называемая преобразованиями XSL (XSL Transforma- tions, XSLT), создающими отформатированные текстовые документы из документов XML. Данный процесс на- зывается преобразованием и включает в себя две древовидные структуры: исходное дерево, являющееся преоб- разуемым документом XML, и результирующее дерево, являющееся результатом (т. е. любым текстовым фор- матом, подобно XHTML1) преобразования. Во время преобразования исходное дерево не модифицируется. Для выполнения преобразований требуется процессор XSLT. В число популярных процессоров XSLT входят msxml и Xalan от Apache Software Foundation. Документ XML, представленный в листинге 15.18, преобразуется с помощью msxml в документ XHTML (листинг 15.19). Листинг 15.18. Документ XML, содержащий информацию о книге 1 <?xml version = "1.0"?> 2 3 <!— Листинг 15.18: sorting.xml —> 4 <!— Документ XML, содержащий информацию о книге —> 5 6 <?xml;stylesheet type = "text/xsl" href = "sorting.xsl"?> 7 8 <book isbn = "999-99999-9-X"> 9 <title>Deitel&apos;s XML Primer</title> 10 11 <author> .12 <firstName>Paul</firstName> 13 <lastName>Deitel</lastName> 14 </author> 15 16 <chapters> 17 <frontMatter> 18 <preface pages = "2" /> 19 <contents pages = "5" /> 20 «^illustrations pages = "4” /> 21 </frontMatter> 22 23 <chapter number « "3" pages = "44"> 24 Advanced XML</chapter> 25 26 <chapter number = ”2" pages = "35"> 27 Intermediate XML</chapter> 28 29 <appendix number = "B" pages = "26"> 30 Parsers and Tools</appendix> 31 32 <appendix number = "A" pages = "7"> 33 Entities</appendix> 34 35 <chapter number = "1" pages = "28"> 36 XML Fundamentals</chapter> 37 </chapters> 38 39 <media type = "CD" /> 40 </book> Строка 6 является командой обработки (processing instruction, PI), содержащей относящуюся к приложению информацию, интегрированную в документ XML. В данном конкретном случае команда обработки относится 1 XHTML (Extensible HyperText Markup Language, расширяемый язык гипертекстовой разметки) — это техническая рекомендация W3C, заменяющая HTML, для разметки содержимого Web-страниц. Более подробная информация о XHTML имеется в приложе- ниях 11 и 12, й также на сайте www.w3.org.
554 Глава 15 к Internet Explorer и указывает местоположение документа XSLT, в который будет преобразован документ XML. Символы <? и ?> разделяют команду обработки, состоящую из цели PI (например, xml: stylesheet) и значения PI (например, type = "text/xsl" href = "sorting.xsl"). Часть данного значения Pl, следующая за href, ука- зывает имя и местоположение применяемой таблицы стилей: в данном случае — файл sorting.xsl, размещенный в том же каталоге, что и документ XML. В листинге 15.19 представлен документ XSLT (sorting.xsl), преобразующий sorting xml (см. листинг 15.18) в XHTML. Результат представлен на рис. 15.13. Совет по повышению производительности___________________________________________________________ Использование клиентом программы Internet Explorer для обработки документов XSLT "консервирует" ресурсы сервера путем поглощения вычислительной мощности клиента (вместо того, чтобы сервер обрабатывал доку- менты XSLT для многих клиентов). 1 <?xml version = "1.0"?> 2 3 <!— Листинг 15.19: sorting.xml —> 4 <!— Преобразование информации о книге в документ XHTML —> 5 6 <xsl:stylesheet version = "1.0" 7 xmlns:xsl = http://www.w3.org/1999/XSL/Transform"> 8 9 <!— запись объявления XML и информации DOCTYPE DTD —> 10 <xsl:output method = "xml" omit-xml-declaration = "no" 11 doctype-system = 12 "http://www.w3.org/TR/shtmll/DTD/shtml1-strict.dtd" 13 doctype-public = "-//W3C//DTD XHTML 1.0 Strict//EN"/> 14 15 <!— сопоставление корня документа —> 16 <xsl:template match = "/"> 17 <html xmlns = "http://www.w3.org/1999/xhtml"> 18 <xsl:apply-templates/> 19 </html> 20 </xsl:template> 21 22 <!— сопоставление book —> 23 <xsl:template match = "book"> 24 <head> 25 <title>ISBN <xsl"value-of select = "@isbn" /> 26 <xsl:value-of select = "title" /></title> 27 </head> 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 <body> <hl style = "color: blue"> <xsl:value-of select = "title"/></hl> <h2 style = "color: blue">by <xsl:value-of select = "author/lastName" /> <xsl:value-of select = "author/firstName" /></h2> <table style =® "border-style: groove; background-color: wheat"> <xsl:for-each select = "chapter/frontMatter/*"> <tr> <td style = "text-align: right"> <xsl:value-of select = "nameO" /> </td> <td> (<xsl:value-of select = "@pages" /> pages) </td>
Язык XML 555 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 </tr> </xsl: for-each> <xsl:for-each select = "chapter/chapter"> <xsl:dort select = "©number" data-type = "number" order = "ascending" /> <tr> <td style = "text-align: right"> Chapter <xsl:value-of select = "@number" /> </td> <td> (<xsl:value-of select = "©pages” /> pages) </td> </tr> </xsl:for-each> <xsl:for-each select = "chapters/appendix"> <xsl:sort select = "©number" data-type = "text" order = "ascending" /> <tr> <td style — "text-align: right"> Appendix <xsl:value_of select = "©number" /> </td> <td> (<xsl:value-of select = "©pages" /> pages) </td> </tr> </xsl:for-each> </table> <br /><p style = "color: blue">Pages: <xsl:variable name = "pagecount" 4 select = "sum(chapters//*/@pages)" /> <xsl:value-of .select =s "$pagecount" /> <br />Media Type: <xsl:value-of select = "vedia/@type" /></p> </body> </xsl:template> </xsl:stylesheet> ISBN 999 99999-9-X Deitel’» XML Primer - Mi i, Fite £dt View F^vor'to Joob Help Д .1 Й Search jj avorite* Address [ C\book*.\2001\ctphtp1\c»phtp1 7^] f^Go , Link» Deitel's XML Primer by Deitel, Paul Pages: 151 Media Type: CD preface (2 pagci) ' contents ’(5 p<4j8t J illustrations (4 pages) ^ Chapter l(28prgts Chapter 2^( 35 pages) Chapters (44 paged) Appendix a(7 pages) Appendn J: ( 26,pages ) jar* l^jPone I: Рис. 15.13. Результат преобразования sorting.xml в XHTML-документ В строке 1 листинга 15.19 содержится объявление XML. Помните, что документ XSL— это документ XML. Строка 6 — это корневой элемент xsl: stylesheet. Атрибут version указывает версию XSLT, которой соответ- ствует документ. Задается префикс пространства имен xsl и ограничивается пространством имен URI XSLT, определенным W3C. При обработке в строках 11—13 объявление типа документа записывается в результирую- щее дерево. Атрибуту method присваивается "xml", указывая на то, что XML выводится в результирующее дере- во. Атрибуту omit-xmldeclaration присваивается "по", что выводит объявление XML в результирующее дере- во. Атрибуты doctype-system и doctype-public записывают информацию Doctype DTD в результирующее де- рево. Документы XSLT содержат один или несколько элементов xsl:template, указывающих на то, какую информа- цию выводить в результирующее дерево. Шаблон в строке 16 сопоставляет (match) корень документа исходного дерева. Данный шаблон применяется при появлении корня документа, и любой помеченный этим элементом текст, не входящий в пространство имен, на которое ссылается xsl, выводится в результирующее дерево. В строке 18 выполняется обращение ко всем шаблонам (template), соответствующим потомкам применяемого корня документа. В строке 23 указывается шаблон template, совпадающий с элементом book. В строках 25 и 26 создается заголовок документа XHTML. ISBN книги выбирается из атрибута isbn и содержи- мого элемента title для формирования строки заголовка ISBN 999-99999-9-Х - Deitel’s XML Primer. Элемент xsl: value-of выбирает атрибут isbn элемента book.
556 Глава 15 В строках 33—35 создается элемент заголовка, содержащий имя автора книги. Поскольку контекстный узел (т. е. текущий обрабатываемый узел) — book, то выражением XPath author/lastName выбирается фамилия авто- ра, а выражением author/f irstName — имя автора. В строке 40 выбирается каждый элемент (указан *), являющийся потомком элемента f rontMatter. В строке 43 вызывается функция установки узлов name для извлечения имени элемента текущего узла (например, preface). Текущий узел является контекстным узлом, указанным в xsl: f or-each (строка 40). В строках 53 и 54 элементы chapter сортируются по номерам в возрастающем порядке. Атрибутом select вы- бирается значение атрибута number контекстного узла chapter. Атрибут data-type со значением "number" ука- зывает сортировку по числам, а атрибут order задает восходящий ("ascending") порядок. Атрибуту data-type также можно присвоить значение "text" (строка 67), а атрибуту order можно присвоить значение "descending" (в убывающем порядке). В строках 82 и 83 используется переменная XSL для сохранения значения подсчитанных страниц книги и его вывода в результирующее дерево. Атрибут name задает имя переменной, а атрибут select — ее значение. Функ- ция sum складывает значения для всех значений атрибута раде. Две косые черты между chapters и * указывают, что все нисходящие узлы элементов chapters просматриваются на предмет наличия элементов, содержащих атрибут с именем pages. , Пространство имен SystemXmi.Xsl предоставляет классы для применения таблиц стилей XSLT к документам XML. В частности, объект класса xslTransform выполняет преобразование. В листинге 15.20 таблица стилей (sports.xsl) применяется к sports.xml (см. листинг 15.8). Результат преобразова- ния записывается в текстовое поле и в файл (рис. 15.14). Также показаны результаты преобразования, визуали- зированные в IE (рис. 15.15). 1 // Листинг 15.20: TransformTest.cs 2 // Применение таблицы стилей к документу XML 3 4 using System; 5 using System.Windows.Forms; 6 using System.Xml 7 using System.Xml.XPath; // содержит классы XPath 8 using System.Xml.Xsl; // содержит классы таблицы стилей 9 . using System.IQ; // содержит классы потока 10 11 // преобразование документа XML в XHTML 12 public class Transformtest : System.Windows.Forms.Form 13 { 14 private System.Windows.Forms.TextBox consoletextBox; 15 private System.Windows.Forms.Button transformButton; 16 private System.ComponentModel.Container components = null; 17 18 private XmlDocument document; // корень документа XML 19 private XPathNavigator navigator; // прохождение документа 20 private XslTransform transformer; // преобразование документа 21 private Stringwriter output; 22 23 public Transformtest() 24 { 25 InitializeComponent(); 26 27 // загрузка данных XML 28 document = new XmlDocument (); 29 document.Load("..\\.. Wsports.xml"); 30 31 // создание навигатора 32 navigator = document.CreateNavigator(); 33 34 II загрузка таблицы стилей 35 transformer = new XslTransform (); 36 transformer.Load(" . .\\. .Wsports.xsl") ; 37 } // конец конструктора
Язык XML 557 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 // код, сгенерированный Windows Form Designer [STAThread] _ static void Main() { Application.Run(new TransformTest()); } // конец Main // событие transformButton_Click private void trdnsformButton_Click(object sender, System.EventArgs e) { // преобразование данных XML output = new Stringwriter(); transformer.Transform(navigator, null, output); // отображение преобразования в текстовом поле consoleTextBox.Text = output.ToString(); // запись результата преобразования на диск Filestream stream = new FileStream(". .\\. .Wsports.html", FileMode.Create); Streamwriter writer = new Streamwriter(stream); writer.Write(output.ToString()); // закрытие потоков writer.Close(); output.Close(); } // конец transform_Click } // конец TransformTest Рис. 15.14. Окно программы: a — до применения таблицы стилей; б — после применения таблицы стилей Рис. 15.15. Загруженный в Internet Explorer отформатированный документ 6
558 Глава 15 В строке 20 объявляется ссылка transformer класса xslTransorm. Объект этого типа необходим для преобразо- вания данных XML в другой формат. В строке 29 выполняется синтаксический анализ документа XML, который загружается в память вызовом метода Load. В строке 32 вызывается метод createNavigator для создания объек- та XPathNavigator, используемого для прохождения документа XML во время преобразования. Вызовом метода Load класса XslTransform (строка 36) выполняется синтаксический анализ и загрузка таблицы стилей, исполь- зуемой данным приложением. Передаваемый аргумент содержит имя и местоположение таблицы стилей. ' Обработчик события transformButton_Click вызывает метод Transform класса XslTransform ДЛЯ применения таблицы стилей sports.xsl к sports.xml (строка 53). Данный метод принимает три аргумента: XPathNavigator (созданный из. класса XmlDocument объекта sports.xml), экземпляр класса XsltArgumentList, являющийся спи- ском строковых параметров, который можно применить к таблице стилей; в данном случае — null, и экземпляр класса, производного от Textwriter (в данном примере — экземпляр класса stringwriter). Результаты преоб- разования сохраняются в объекте stringwriter, на который делается ссылка output. В строках 59—62 резуль- таты преобразования записываются на диск. 15.7. Microsoft BizTalk Организации все чаще используют Интернет для обмена критичными данными с деловыми партнерами и между собственными подразделениями. Однако обмен данными между организациями может стать затруднительным, потому что разные компании часто пользуются различными системными платформами, программными прило- жениями и спецификациями данных, что усложняет их передачу. Например, представим себе предприятие, по- ставляющее сырье в разные промышленные отрасли. Если поставщик не может получить все заказы в элек- тронном виде по причине использования заказчиками иных системных платформ, нежели у поставщика, то служащим придется оформлять заказы вручную. При обороте сотен заказов в день весьма вероятно появление ошибок при наборе вручную, результатом чего могут возникать ошибки, например, в определении объемов то- варов на складах или при исполнении заказов, а это угрожает компании потерей клиентов. В данном случае поставщик имеет несколько вариантов решения проблемы: продолжение ввода данных вруч- ную, приобретение программного обеспечения, используемого заказчиками, либо предложение заказчикам ис- пользовать программные пакеты поставщика. При растущих потребностях экономии компании потребуется приобретать и обслуживать разнородные программные средства, нанимать больше сотрудников для обработки данных (с соответствующими финансовыми затратами), либо требовать от деловых партнеров стандартизации их программных средств. Для упрощения процесса обмена информацией между компаниями корпорация Micro- soft разработала на базе XML технологию под названием BizTalk (от англ, business talk — деловые переговоры), помогающую в управлении деловыми операциями и заметно их упрощающую. BizTalk создает среду, в которой данные, размеченные как XML, используются для обмена деловой информаци- ей, независимо от платформы или приложений программирования. В данном разделе i общем виде описывается технология BizTalk с представлением примерного кода для иллюстрации деловой информации, включенной в разметку. BizTalk состоит из трех частей: BizTalk Server (сервер), BizTalk Framework (рабочая среда) и BizTalk Schema Library (библиотека схем). □ BizTalk Server (BTS) синтаксически анализирует и переводит все входящие и исходящие сообщения (или документы) компании с помощью стандартов Интернета, например HTTP. □ BizTalk Framework — это схема (Schema) для структурирования таких сообщений. Рабочая среда предлагает особый набор базовых тегов. Компании могут загружать данную структуру и предлагать новые схемы орга- низациям, работающим с BizTalk, на сайте www.biztalk.org. Как только организация, работающая с BizTalk, подтверждает и проверяет предложения, данные схемы становятся схемами структуры BizTalk. □ BizTalk Schema Library— это коллекция схем рабочей среды. В табл. 15.2 представлена терминология BizTalk. Таблица 15.2. Терминология BizTalk BizTalk Описание Framework Спецификация, определяющая формат сообщений Schema Library Server Хранилище XML-схем структуры Программное приложение в помощь поставщикам при преобразовании сообщений в формат BizTalk. Более подробную информацию см. на сайте www.microsoft.com/biztalkserver JumpStart Kit Набор инструментальных средств для разработки приложений BizTalk
Язык XML 559 В листинге 15.21 приведен пример сообщения BizTalk о предложении компании — поставщика одежды. Схема сообщения для данного примера разработана в корпорации Microsoft для упрощения электронных заказов роз- ничных торговцев у оптовиков. Данная схема используется для несуществующей компании под названием ExComp. 1 <?xml version = ,,1.0"?> 2 <BizTalk xmlns = 3 "urn:schemas-biztalk-org:BizTalk/biztalk-0.81.xml"> 4 5 <!— Листинг 15.21: biztalkmarkup.xml —> 6 <!— Пример стандартной разметки BizTalk —> 7 8 <Route> 9 <From locationlD = "8888888" locationType = "DUNS" 10 handle = "23" /> 11 12 <To locationlD = "454545445" locationType = "DUNS" 13 handle = "45" /> 14 </Route> 15 16 <Body> 17 <Offers xmlns = 18 "x-schema:http//schemas/biztalk/org/eshop_msn_com/t7ntoqnq.xml"> 19 <Offer> 20 <Model>12-a-3411d</Model> 21 <Manufacturer>ExComp, Inc. </Manufacturer> 22 <ManufacturerModel>DCS-48403</ManufacturerModel> 23 24 <MerchantCategory> 25 Clothes | Sports wear 26 </MerchantCategory> 27 28 <MSNClassId><MSNClassId> 29 30 <StartDate>2001-06-05 T13:12:00</StartDate> 31 <EndDate>2001-12-05 T13:12:00</EndDate> 32 33 <RegularPrice>89.99</Regularprice> 34 <CurrentPrice>25.99</Currentprice> 35 <DisplayPrice value = "3" /> 36 cinStock value = "15" /> 37 38 <ReferenceImageURL> 39 http://ww.Example.com/clothes/index.jpg 40 </ReferenceImageURL> 41 42 <OfferName>Clearance sale</OfferName> 43 44 <OfferDescriptibn> 45 This is a clearance sale 46 </OfferDescription> 47 48 <PromotionalText>Free Shipping</PromotionalText> ' 49 50 <Comments> 51 Clothes that you would love to wear 52 </Comments> 53 54 <IconType value = "BuyNow" /> 55
560 Глава 15 56 <ActionURL> 57 http://Www.example.com/action.htm 58 </ActionURL> 59 60 <AgeGroupl value = "Infant" /> 61 <AgeGroup2 value = "Mult" /> 62 63 <Occasionl value = "Birthday" /> 64 <Occasion2 value = "Anniversary" /> 65 <0ccasion3 value = "Christinas" /> 66 67 </Offer> 68 </Offers> 69 </Body> 70 </BizTalk> Все документы BizTalk имеют корневой элемент BizTalk (строка 2). В строке 3 определяется пространство имен по умолчанию для элементов структуры BizTalk. Элемент Route (строки 8—14) содержит информацию о маршруте, которая является обязательной для всех документов BizTalk. Элемент Route также содержит элемен- ты То и From (строки 9—12), указывающие место назначения документа и место отправления соответственно. Это упрощает задачу взаимодействия принимающего программного приложения с отправляющим. Атрибут locationType указывает тип компании, которая отправляет или получает информацию, а атрибут locationiD — идентификацию компании (уникальную для каждого вида деятельности). Эти атрибуты упрощают исходную организацию отправления и получения. Атрибут handle обеспечивает информацией приложения маршрутиза- ции, обрабатывающие документ. Элемент Body (строки 16—69) содержит фактическое сообщение, схема которого определена родом деятельно- сти самих взаимодействующих компаний. В строках 17 и 18 указывается пространство имен по умолчанию для элемента offers (строки 17—68), входящего в элемент Body (обратите внимание, что строка 18 может перено- ситься на следующую строку; если ее разделить, то Internet Explorer не определит местоположение пространства имен). Каждое предложение разделено с помощью элемента offer (строки 19—67), содержащего элементы, описывающие предложение. Обратите внимание, что все теги являются понятными элементами, относящимися к компании. Дополнительная информация о BizTalk имеется на сайте www.biztalk.com. В данной главе был рассмотрен язык XML и несколько связанных с ним технологий. В следующей главе мы обсудим базам данных — принципиальный аспект при разработке многозвенных программных приложений на базе Web. 15.8. Резюме XML — это широко распространенная и поддерживаемая открытая (т. е. незапатентованная) технология обмена данными. XML быстро превращается в стандарт, в соответствии с которым программные приложения поддер- живают данные. XML обладает высокой степенью портативности. Любой текстовый редактор, поддерживаю- щий символы стандартов ASCII или Unicode, может отображать документы XML. Благодаря тому, что элемен- ты XML описывают содержащиеся в них данные, их могут читать как люди, так и машины. XML дает возможность авторам документов создавать специальную разметку практически для любого типа ин- формации. Подобная расширяемость позволяет программистам создавать абсолютно новые языки разметки, описывающие особые типы данных: математические формулы, химические молекулярные структуры и даже медицинские рецепты. Обработка документов XML, обычно хранящихся в файлах с расширением xml, требует наличия программы, называемой анализатором XML. Анализатор документов XML отвечает за идентификацию компонентов таких документов и сохранение этих компонентов в структуре данных для последующих манипуляций ими Документ XML может ссылаться на какой-либо второстепенный документ, определяющий структуру документа XML. Двумя типами необязательных документов, определяющих структуру, являются определения типов доку- ментов (DTD) и схемы. Данные маркируются с помощью тегов, имена которых заключены в угловые скобки (< >). Теги используются парами для разделения разметки. Тег, начинающий разметку, называется начальным, а тег, завершающий раз- метку, — конечным. Конечные теги отличаются от начальных наличием символа левой косой черты (/). Отдельные блоки разметки называются элементами; они представляют собой самые базовые блоки XML Доку- менты XML содержат один элемент, называемый корневым, включающий все остальные элементы документа.
Язык XML 561 Элементы встроены (вложены) друг в друга с образованием иерархий; корневой элемент находится в самом верху иерархии. Кроме размещения между тегами, данные могут находиться в атрибутах, представляющих пары "имя = значение" в начальных тегах. Элементы могут иметь любое количество атрибутов. Из-за того что XML позволяет авторам документов создавать собственные теги, могут иметь место конфликты имен (т. е. появление двух элементов с одинаковыми именами). Как и в С#, пространства имен XML предостав- ляют авторам документов средство исключения возможности конфликтов имен. Элементы квалифицируются префиксами пространств имен, указывающими пространство имен, к которому они принадлежат. Каждый префикс пространства имен связан с универсальным идентификатором ресурса (Uniform Resource Iden- tifier, URI), который уникально определяет пространство имен. URI представляет собой серию символов, кото- рыми различаются имена. Авторы документов создают собственные префиксы пространств имен. В качестве префикса пространства имен можно использовать практически любое имя, за исключением зарезервированного префикса xml. Для устранения необходимости размещения префикса пространства имен в каждом элементе ав- торы документов могут указать пространство имен по умолчанию для элемента и его потомков. Когда анализатор XML успешно проверит документ в синтаксическом плане, он сохраняет в памяти древовид- ную структуру, содержащую данные документа. Такая иерархическая древовидная структура называется дере- вом объектной модели документа (Document Object Model, DOM). Дерево DOM представляет каждый компо- нент документа XML как узел дерева. Узлы, содержащие другие узлы (называемые узлами-потомками), назы- ваются узлами-родителями. Узлы, имеющие одного "родителя", называются узлами-"братьями". В узлы, нисходящие от одного узла, порождают потомки этого узла, потомки его потомков и т. д. Узлы предшественни- ки одного узла включают в себя его родителей, родителей их родителей и т. п. Дерево DOM имеет один корне- вой узел, содержащий все прочие узлы документа. Пространство имен System.Xml содержит классы для создания, прочтения XML документов и для манипуляций ими. Производный от класса XmlReader класс XmlNodeReader итерируется через каждый узел в документе XML. Концептуально объект XmlDocument представляет пустой документ XML. Документы XML анализируются син- таксически и загружаются в объект xmldocument при активизации метода Load. Как только документ XML за- гружен в объект XmlDocument, его данные можно считывать и ими можно манипулировать программно. Класс XmlNodeReader позволяет разработчикам считывать по одному узлу из объекта XmlDocument. Объект XmlTextWriter сбрасывает данные XML потоком на диск. Объект xmlTextReader считывает данные XML с диска. XPath (XML Path Language) предоставляет синтаксис для эффективного и производительного нахождения кон- кретных узлов в документе XML. XPath — это язык выражений на базе строк, используемый XML и многими связанными с ним технологиями. Класс XPathNavigator в пространстве имен System.Xml.XPath может итериро- ваться через списки узлов, соответствующих критериям поиска и записанных в виде выражений XPath. Документы XML содержат только данные, однако технология XSLT может преобразовывать документы XML в любой формат, основанный на тексте. Файлы документов XSLT, как правило, имеют расширение xsl. В преоб- разовании документа XML через XSLT принимают участие две древовидные структуры: исходное дерево, являющееся преобразуемым документом XML, и результирующее дерево, представляющее собой результат преобразования (например, XHTML). Документы XML можно преобразовывать программными способами че- рез С#. Пространство имен System.Xml.Xsl упрощает применение таблиц стилей XSLT к документам XML. 15.9. Ресурсы Интернета и WWW □ www.w3.org/xml W3C (World Wide Web Consortium) упрощает разработку общих протоколов с целью обеспечения сетевого взаимодействия. На странице XML размещена информация о будущих мероприятиях, публикациях, про- граммных средствах и дискуссионных семинарах. Посетите сайт для ознакомления с последними разработ- ками в области XML. □ www.xml.org xml.org — материалы о XML, DTD , схемах и пространствах имен. □ www.w3.org/style/XSL Данная страница W3C предоставляет информацию о XSL, включая такие темы, как разработка XSL, обуче- ние XSL, инструментальные средства на базе XSL, спецификации XSL, вопросы и ответы, а также история XSL. 36 Зак. 3333
562 Глава 15 □ www.w3.org/TR Технические отчеты и страница публикаций W3C. Содержит ссылки на рабочие проекты, предлагаемые ре- комендации и другие ресурсы. □ www.xmlbooks.com На сайте представлен список книг по XML, рекомендуемых Чарльзом Голдфарбом (Charles Goldfarb), одним из самобытных разработчиков GML (General Markup Language, общий язык разметки), производным от ко- торого стал язык SGML (Standard Generalized Markup Language, стандартный обобщенный язык разметки). □ www.xml-zone.com Development Exchange XML Zone — исчерпывающий ресурс информации об XML. Сайт включает в себя раздел FAQ ("Вопросы и ответы"), новости, статьи и ссылки на другие сайты и новостные группы, посвя- щенные XML. П wdvl.internet.com/Authoring/Languages/XML Сайт виртуальной библиотеки Web-разработчика XML объединяет учебные материалы, рубрику FAQ ("Во- просы и ответы"), последние новости, многочисленные ссылки на сайты, посвященные XML, а также воз- можность загрузки программных средств. □ www.xml.com XML.com предоставляет самые последние новости и информацию об XML, стенограммы конференций, ссылки на Web-ресурсы XML, структурированные по темам, инструментальным средствам и другим ресур- сам. □ msdn.microsoft.com/xml/default.asp Оперативный центр разработки XML MSDN (Microsoft Developer Network) включает статьи по XML, чат- сессии "Ask the Experts" ("Спроси у специалистов"), примеры и демо-версии, новостные группы и другую полезную информацию. □ msdn.microsoft.com/downloads/samples/Internet/xml/xml_validator/sample.asp Microsoft XML Validator с возможностью загрузки с сайта; проверка как документов в on-line, так и off-line. □ www.oasis-open.org/cover/xml/html Web-страница SGML/XML — обширный ресурс, включающий ссылки на несколько рубрик вопросов и отве- тов, оперативные ресурсы, промышленные инициативы, демонстрационные материалы, конференции и учебные материалы. □ www.gca.org/whats_xml/default.htm Сайт GCA (Graduate Course Advisor, справочник по курсам обучения) предлагает глоссарий XML, список книг, посвященных теме XML, краткие описания проектов стандартов для XML, а также ссылки на другие проекты. □ www-106.ibm.com/developerworks/xml Сайт IBM XML Zone— прекрасный ресурс для разработчиков. Здесь представлены новости, инструмен- тальные средства, библиотека, исследования контрольных примеров, а также информация о мероприятиях и стандартах. □ developer.netscape.com/tech/xml/index.html Сайт XML и Metadata Developer включает в себя демонстрационные материалы, технические примечания, новости и статьи по теме XML. □ www.projectcool.com/developer/xmlz Сайт Project Cool Developer Zone предлагает несколько учебных материалов, охватывающих тему XML от основ до самых последних разработок. □ www.ucc.ie/xml Сайт содержит подробный набор вопросов и ответов по XML. Разработчики могут получить ответы на са- мые популярные вопросы, а также разместить свои вопросы.
ГЛАВА 16 (Ш Базы данных, SQL и ADO.NET Построение теорий при отсутствии фактов является грубой ошибкой Артур Конан-Дойл Иди, начертай им на скрижали и запиши в книге, которая останется в веках. Священное писание: Ветхий завет Посмотрим записи. Альфред Эммануэль Смит Сначала получите факты. Потом вы сможете исказить их так, как вам угодно. Марк Твен Мне нравятся два типа людей: домоседы и иностранцы. Мэй Уэст Темы данного приложения: □ понятие модели реляционной базы данных; □ понятие основных запросов баз данных с помощью SQL; □ использование классов и интерфейсов пространства имен System. Data для манипуляций базами данных; П понятие и использование несоединенной модели ADO.NET; П использование классов и интерфейсов пространства имен System. Data .oleDb. w 16.1. Введение Базой данных Называется интегрированный набор информации (данных). Существует много различных страте- гий для организации данных в базы с целью упрощения доступа к ним и обработки. Система управления базами данных (Database Management System, DBMS) предоставляет механизмы хранения и организации данных спосо- бом, не противоречащим формату базы данных. Системы управления базами данных (СУБД) обеспечивают программистам доступ к данным и возможность их сохранения, и разработчики могут не беспокоиться о внут- реннем представлении баз данных. Наиболее популярными на сегодняшний день СУБД являются реляционные базы данных (Relational Database Management System, RDBMS). Практически во всех реляционных базах данных используется язык структуриро- ванных запросов (Structured Query Language, SQL) для выполнения запросов (т. е. требования информации, удовлетворяющей заданным критериям) и манипуляций данными. В число широко распространенных систем реляционных баз данных промышленного уровня входят Microsoft SQL Server, Oracle, Sybase, DB2, Informix и MySQL. Примеры в главе приведены с использованием Microsoft Access — системы управления реляционной бгзой данных, входящей в пакет Microsoft Office. Язык программирования подключается к реляционной базе данных и взаимодействует с ней через интер- фейс — программное средство, упрощающее связь между СУБД и программным приложением. Программисты C# связываются с базами данных и манипулируют их данными через Microsoft ActiveX Data Objects (ADO) — ADO.NET. 16.2. Модель реляционной базы данных Модель реляционной базы данных можно изобразить в виде логического представления данных, позволяющего рассматривать взаимосвязь данных без учета их физической структуры. Реляционная база данных состоит из
564 Глава 16 таблиц. На рис. 16.1 показана таблица, которую можно использовать в системе учета кадров. Название табли- цы — Employee, и ее основной целью является иллюстрация особых атрибутов разных служащих. Отдельная строка таблицы называется записью (или строкой). Данная таблица состоит из шести записей. Поле (или стол- бец) number каждой записи таблицы является первичным ключом для ссылки на данные в таблице. Первичный ключ — это поле (или поля) в таблице, содержащие уникальные данные, т. е. данные, которые не повторяются в других записях таблицы. Этим гарантируется, что каждая запись может быть идентифицирована минимум од- ним значением. Примерами полей первичного ключа являются столбцы, содержащие коды социального обес- печения, идентификационные номера служащих и номера партий в системе инвентаризации. Записи на рис. 16.1 упорядочены по первичному ключу. В данном случае записи перечислены в возрастающем порядке (их также можно перечислить в убывающем порядке). Number Name Depart dent Salary Location 23603 Jones 413 1100 New Jersey 24568 Kerwin 413 2000 New Jersey 34589 Larson 642 1800 Los Angeles 35761 Myers 611 1400 Orlando 47132 Neumann 413 9000 New Jersey 78321 Stephens 611 8500 Orlando Первичный ключ Поле/столбец Ч Department Location 413 New Jersey 611 Orlando 642 Los Angeles Рис. 16.1. Структура таблицы Employee в реляционной базе данных Рис. 16.2. Результирующее множество, образованное выбором столбцов Department и Location из таблицы Employee Каждый столбец таблицы представляет очередное поле. Обычно записи в таблице уникальны (по первичному ключу), но значения конкретного поля могут дублироваться во многих полях. Например, три разные записи в поле Department таблицы Employee содержат число 413. Разные пользователи баз данных часто заинтересованы в разных данных и разных отношениях между этими данными. Некоторым нужны только подмножества столбцов таблицы Для их извлечения используются опера- торы SQL для указания определенный данных, которые следует выбрать в таблице. SQL предоставляет полный набор команд (включая select), позволяющих программистам определять комплексные запросы на выборку данных из таблицы. Результаты запроса обычно называются результирующим множеством (или множеством записей). Например, можно выбрать данные из таблицы, представленной на рис. 16.1, для создания нового ре- зультирующего множества, состоящего только из местоположения каждого подразделения. Данное результи- рующее множество показано на рис. 16.2. Подробно запросы SQL рассматриваются в разд. 16.4. 16.3. Обзор реляционной базы данных: база данных Books В следующем разделе представлен обзор SQL в контексте примера базы данных Books, созданной специально для примеров данной главы. Однако до рассмотрения SQL следует пояснить назначение различных таблиц базы данных Books. Она используется для представления разных концепций баз данных, включая использование SQL для извлечения полезной информации и манипуляций ею. База данных Books состоит из четырех Таблиц: Authors, Publishers, AuthorlSBN и Titles. Таблица Authors (табл. 16.1) состоит из трех полей (или столбцов), содержащих уникальный идентификационный номер каждого автора, его имя и фамилию. В табл. 16.2 содержатся данные из таблицы Authors базы данных Books. Таблица 16.1. Таблица Authors базы данных Books Поле Описание authorlD Идентификационный номер автора в базе данных. В базе данных Books это int-поле определя- ется как поле автоматического увеличения. Для каждой новой записи, вводимой в таблицу, база данных увеличивает значение authorlD для того, чтобы данный идентификационный номер был уникальным. Поле authorlD является первичным ключом таблицы firstName Имя автора (string) lastName Фамилия автора (string)
Базы данных, SQL и ADO.NET 565 Таблица 16.2. Данные из таблицы Authors базы данных Books authorlD firstName lastName 1 Harvey Deitel 2 Paul Deitel 3 Tem Nieto 4 Kate Steinbuhler 5 Sean Gantry 6 Ted Lin 7 Praveen Sadhu 8 David' McPhie 9 Cheryl Yaeger 10 Marina Zlatkina 11 Ben Wiedermann 12 Jonathan Liperi 13 Jeffrey Listfield Таблица Publishers (табл. 16.3) состоит из двух полей, представляющих уникальный идентификационный но- мер каждого издателя и его имя. Табл. 16.4 содержит данные из таблицы Publishers базы данных Books. Таблица 16.3. Таблица Publishers базы данных Books Поле Описание publisheriD Идентификационный номер издателя в базе данных. Это автоматически увеличиваемое поле int является первичным ключом таблицы publisherName Имя издателя (string) Таблица 16.4. Данные из таблицы Publishers базы данных Books publisheriD publisherName 1 Prentice Hall 2 Prentice Hall PTG Таблица AuthoriSBN (табл. 16.5) состоит из двух полей с идентификационными номерами авторов и соответст- вующими номерами ISBN их книг. Данная таблица помогает связывать имена авторов с названиями их книг. В табл. 16.6 содержатся данные из таблицы AuthoriSBN базы данных Books. Аббревиатура ISBN расшифровыва- ется как "Стандартный международный номер книги" (International Standard Book Number). Это — схема нуме- рации, по которой издатели во всем мире присваивают каждой книге уникальный идентификационный номер.- Примечание______________________________________________ Йз экономии места авторы разделили таблицу на два столбца, в каждом из которых содержатся поля authorlD и isbn. Таблица 16.5. Таблица AuthoriSBN базы данных Books Поле Описание authorlD Идентификационный номер автора, позволяющий базе данных ассоциировать каждую книгу с тем или иным автором. Целое идентификационное число в этом поле должно появиться в таблице Authors ISBN Номер ISBN книги (string)
566 Глава 16 Таблица 16.6. Данные из таблицы AuthoriSBN базы данных Books authorlD ISBN authorlD ISBN 1 0130895725 2 0139163050 1 0132261197 2 013028419Х 1 0130895717 2 0130161438 1 0135289106 2 0130856118 1 0139163050 2 0130125075 1 013028419Х 2 0138993947 1 0130161438 2 0130852473 1 0130856118 2 0130829277 1 0130125075 2 0134569555 1 0138993947 2 0130829293 1 0130852473 2 0130284173 1 0130829277 2 0130284181 1 0134569555 2 0130895601 1 0130829293 3 013028419Х. 1 0130284173 3 0130161438 1 0130284181 3 0130856118 1 0130895601 3 0134569555 2 0130895725 3 0130829293 2 0132261197 3 0130284173 2 0130895717 3 0130284181 2 Q135289106 4 0130895601 Таблица Titles (табл. 16.7) состоит из семи полей, содержащих общую информацию о книгах в базе данных. Эта информация включает в себя номер ISBN каждой книги, название, номер издания, год регистрации автор- ских прав и идентификационный номер издателя, а также имя файла, в котором содержится макет обложки и, наконец, цену каждой книги. В табл. 16.8 содержатся данные из таблицы Titles. Таблица 16.7. Таблица Titles базы данных Books Поле Описание ISBN Номер ISBN книги (string) title Название книги (string) editionNumber Номер издания книги (string) copyright Год регистрации авторского права на книгу (int) publisherID Идентификационный номер издателя (int). Данное значение должно соответствовать иденти- фикационному номеру в таблице Publishers imageFile Имя файла, содержащего макет обложки книги (string) price Предполагаемая цена книги (вещественное число) Примечание. Цены, указанные в этой базе данных служат только примером. Таблица 16.8. Данные из таблицы Titles базы данных Books ISBN title edition-Number publisher-ID copyright imageFile Price 0130923613 Python How to Program 1 1 2002 python.jpg $69.95 0130622214 C# How to Program 1 1 2002 cshtp.jpg $69.95
Базы данных, SQL и ADO NET 567 Таблица 16.8 (продолжение) ISBN title edition-Number publisher-ID copyright imageFile Price 0130341517 Java How to Program 4 1 2002 jhtp4.jpg $69.95 0130649341 The Complete Java Training Course 4 2 2002 javactc4.jpg $109.95 0130895601 Advanced Java2 Platform How to Program 1 1 2002 advjhtp1.jpg $69.95 0130308978 Internet and World Wide Web How to program 2 1 2002 iw3htp2.jpg $69.95 0130293636 Visual Basic.NET How to Program 2 1 2002 vbnet.jpg $69.95 0130895636 The Complete C++ Training Course 3 2 2001 cppctc.jpg $109.95 0130895512 The Complete e-Business & e-Commerce Programming Training Course 1 - 2 2001 ebecctc.jpg $109.95 013089561X The Complete Internet & World Wide Web Programming Training Course 2 2 2001 iw3ctc2.jpg $109.95 0130895547 The Complete Perl Training Course 1 2 2001 perl.jpg $109.95 0130895563 The Complete XML Programming Training Course 1 2 2001 xmlctc.jpg $109.95 0130895725 C How to Program 3 1 2001 chtp3.jpg $69.95 0130895717 C++ How to Program 3 1 2001 cpphtp3.jpg $69.95 013028419X e-Business and e-Commerce How to Program 1 1 2001 ebechtp1.jpg $69.95 0130622265 Wireless Internet and Mobile Business How to program 1 1 2001 wireless.jpg $69.95 0130284181 Perl How to Program 1 1 2001 perlhtp1.jpg $69.95 0130284173 XML How to Program 1 1 2001 xmlhtpl .jpg $69.95 0130856118 The Complete Internet and World Wide Web Programming Training Course 1 2 2000 iw3ctc1.jpg $109.95 0130125075 Java How to program (Java 2) 3 1 2000 jhtp3.jpg $69.95 0130852481 The Complete Kava 2 Training Course 3 2 2000 javactc3.jpg $109.95 0130323640 e-Business and e-Commerce for Managers 1 1 2000 ebecm.jpg $69.95 0130161438 Internet and World Wide Web How to Program 1 1 2000 iw3htp1.jpg $69.95 0130132497 Getting Started with Visual C++ 6 with an Introduction to MFC 1 I 1 1999 gsvc.jpg $49.95 0130829293 The Complete Visual Basic 6 Training Course 1 2 1999 vbctd.jpg $109.95 0134569555 Visual Basic 6 How to Ptogram 1 1 1999 vbhtp1.jpg $69.95 0132719746 Java Multimedia Cyber Classroom 1 2 1998 javactc.jpg $109.95 0136325890 Java How to Program 1 1 1998 jhtp1.jpg $69.95 0139163050 The Complete C++ Training Course 2 2 1998 cppctc2.jpg $109.95 0135289106 C++ How to Program 2 1 1998 cpphtp2.jpg $49.95 0137905696 The Complete Java Training Course 2 2 1998 javactc2.jpg $109.95
568 Глава 16 Таблица 16.8 (окончание) ISBN title edition-Number publisher-ID copyright imageFile Price 0130829277 The Complete Java Training Course (Java 1.1) 2 2 1998 javactc2.jpg $99.95 0138993947 Java Howto Program (Java 1.1) 2 1 1998 jhtp2.jpg $49.95 0131173340 C++ How to program 1 - 1 1994 cpphtp1.jpg $69.95 0132261197 C How to program 2 1 1994 chtp2.jpg $49.95 0131180436 C How to program 1 1 1992 cjtp.jpg $69.95 На рис. 16.3 показана взаимосвязь таблиц в базе данных Books. Первая строка каждой таблицы — это ее назва- ние. Поле, название которого выделено курсивом, содержит первичный ключ данной таблицы. Первичный ключ таблицы уникально идентифицирует каждую запись в таблице. Каждая запись должна иметь значение в поле первичного ключа, и это значение должно быть уникальным. Это называется правилом целостности данных. Обратите внимание, что таблица AuthoriSBN содержит два поля с названиями, выделенными курсивом. Это ука- зывает на то, что оба поля образуют составной первичный ключ', каждая запись в поле должна иметь уникаль- ную комбинацию author id-isbn. Например, несколько записей должны иметь authorlD 2, несколько записей могут иметь ISBN 0130895601, но только одна запись может иметь authorlD— 2 И ISBN — 0130895601. Рис. 16.3. Взаимосвязь таблиц в базе данных Books Распространенная ошибка программирования____________________________________________ Отсутствие значения в поле первичного ключа в каждой записи нарушает правило целостности сущностей объ- ектов; в этом случае DBMS выдает сообщение об ошибке. Распространенная ошибка программирования____________________________________________ При вводе дублирующихся значений в поле первичного ключа нескольких записей DBMS выдает сообщение об ошибке. Линии, соединяющие таблицы на рис. 16.3, обозначают отношения между таблицами. Обратите внимание на линию между таблицами Publishers и Titles. На конце Publishers этой линии имеется значение 1, а на конце таблицы Titles стоит знак бесконечности (<»). Эта линия указывает отношение "один ко многим", когда каждый издатель в таблице Publishers может иметь произвольно большое количество книг в таблице Titles. Обратите внимание, что эта линия отношения связывает поле publisherlD в таблице Publishers с полем publisherlD в таблице Titles. В таблице Titles поле publisherlD является внешним ключом', полем, для которого каждая запись имеет уникальное значение в другой таблице, где поле в другой таблице является первичным ключом для этой таблицы (например, publisherlD в таблице Publishers). Разработчики задают внешние ключи при созда- нии таблицы. Внешний ключ помогает придерживаться правила целостности данных', значение каждого поля внешнего ключа должно появиться в поле первичного ключа другой таблицы. Внешние ключи позволяют объ- единять информацию из многих таблиц в целях анализа. Между первичным ключом и соответствующим ему внешним ключом существует отношение "один ко многим". Это означает, что значение поля внешнего ключа может много раз появиться в его таблице, но только один раз в качестве первичного ключа — в других. Распространенная ошибка программирования____________________________________________ Предоставление значения внешнего ключа, не проявляющегося в качестве значения первичного ключа в другой таблице, является нарушением правила целостности данных; в этом случае DBMS выдает сообщение об ошибке.
Базы данных, SQL и ADO.NET 569 Линия между таблицами AuthoriSBN и Authors указывает, что для каждого автора из таблицы Authors в таблице AuthoriSBN может содержаться произвольное количество номеров ISBN для книг, написанных этим автором. Поле authorlD в таблице AuthoriSBN является внешним ключом поля authorlD (первичный ключ) таблицы Authors. Опять же, обратите внимание, что линия между таблицами связывает внешний ключ в таблице AuthoriSBN с соответствующим первичным ключом в таблице Authors. Таблица AuthoriSBN связывает инфор- мацию В таблицах Titles и Authors. Линия между таблицами Titles и AuthoriSBN иллюстрирует другое отношение "один ко многим"; название кни- ги может быть написано многими авторами. На самом деле, единственной целью таблицы AuthoriSBN является представление отношения "многие ко многим" между таблицами Authors и Titles; автор может написать любое количество книг, и книга может иметь любое количество авторов. 16.4. Structured Query Language (SQL) В данном разделе представлен общий обзор языка структурированных запросов (Structured Query Language, SQL) в контексте примера базы данных Books. Рассматриваемые запросы SQL формируют основу языка SQL, используемого в примерах главы. В табл. 16.9 представлены ключевые слова SQL с описанием каждого. В следующих нескольких разделах клю- чевые слова SQL рассматриваются в контексте полных запросов SQL. Существуют и другие ключевые слова SQL, но они не соответствуют целям данной книги. Примечание___________________________________________________________________________ Дополнительная информация по SQL представлена в библиографии в конце книги. Таблица 16.9. Ключевые слова запросов SQL Ключевое слово SQL Описание SELECT Выбор (извлечение) полей из одной или нескольких таблиц FROM Указание таблиц, из которых следует получить поля или в которых следует удалить записи. Требуется в каждой команде select и delete WHERE Задание критериев, определяющих строки для извлечения INNER JOIN Объединение записей из множественных таблиц для создания единого набора записей GROUP BY Задание критериев группирования записей ORDER BY Создание критериев упорядочения записей INSERT Вставка данных в указанную таблицу UPDATE Обновление данных в указанной таблице DELETE Удаление данных из указанной таблицы 16.4.1. Базовый запрос SELECT Рассмотрим несколько запросов SQL, с помощью которых информация извлекается из базы данных Books. При типичном запросе SQL информация "выбирается" из одной или нескольких таблиц базы данных. Такая выборка осуществляется с помощью запросов типа select. Базовый формат для запроса select таков: SELECT * FROM таблица В данном запросе символ * указывает на то, что из таблицы таблица базы данных должны быть выбраны все столбцы. Например, для выбора всего содержимого таблицы Authors (т. е. всех данных, показанных в табл. 16.2) следует воспользоваться запросом: SELECT * FROM Authors Для выбора конкретных полей таблицы замените символ * списком названий полей, разделенных запятыми. Например, для выбора только полей authorlD и lastName для всех строк в таблице Authors воспользуйтесь за- просом: SELECT authorlD, lastName FROM Authors Данный запрос возвращает только данные, представленные в табл. 16.10.
570 Глава 16 Примечание____________________________________________________________________________ Если название поля содержит пробелы, то все название поля в запросе должно быть заключено в квадратные скобки ([]). Например, если название поля— firstName, то в запросе оно должно появиться в квадратных скобках: [firstName], Распространенная ошибка программирования______________________________________________ Если программой предполагается, что оператор SQL, использующий символ * для выбора полей, всегда воз- вращает эти поля в том же порядке, то программа может некорректно обрабатывать результирующее множест- во. При изменении порядка полей в таблице (таблицах) базы данных будет меняться и порядок полей в резуль- тирующем множестве. Совет по повышению производительности_________________________________________________ Если программе неизвестен порядок полей в результирующем множестве, то она должна обрабатывать поля по их названиям. Это может потребовать линейного поиска названий полей в результирующем множестве. Если пользователи указывают названия полей, которые они хотят выбрать из таблицы (или нескольких таблиц), тогда приложение, принимающее результирующее множество, будет заранее знать порядок полей. В этом случае про- грамма может обрабатывать данные с большей эффективностью, потому что доступ к полям может осуществ- ляться напрямую по номерам столбцов. Таблица 16.10. Поля authorlD и lastName из таблицы Authors authorlD lastName authorlD lastName 1 Deitel 8 McPhie 2 Deitel 9 Yaeger 3 Nieto 10 Zlatkina 4 Steinbuhler 11 Wiedermann 5 Santry 12 Liper: 6 Lin 13 Listfield 7 Sadhu 16.4.2. Выражение WHERE В большинстве случаев пользователи ищут в базе данных записи, удовлетворяющие определенным критериям отбора. В запросе select язык SQL использует необязательное выражение where для задания критериев отбора. Простейшим форматом для запроса select, имеющим критерии отбора, является следующий. SELECT поле1, поле2, ... FROM таблица WHERE критерии Например, для выбора полей title, editionNumber и copyright из строк с такими названиями в таблице Titles, где дата регистрации авторского права (copyright) — позже 1999 года, воспользуйтесь запросом: SELECT title, editionNumber, copyright FROM Titles WHERE copyright > 1999 В табл. 16.11 показаны результаты предыдущего запроса. Примечание______________________________________________________________________________ При создании запроса для использования в C# просто создается строка, содержащая запрос целиком. Однако при отображении запросов для повышения удобочитаемости в тексте часто присутствуют множественные строки и отступы Совет по повышению производительности___________________________________________________ Использование критериев отбора повышает производительность, потому что при запросах, содержащих такие критерии, обычно выбирается часть базы данных, меньшая, чем вся база данных. Работать с небольшой частью базы удобнее и эффективнее, нежели со всем набором хранящихся в ней данных.
Базыданных, SQL и ADO.NET 571 Таблица 16.11. Названия книг с авторскими правами, зарегистрированными после 1999 года, из таблицы Titles Title editionNumber copyright Internet and World Wide Web How to Program 2 2002 Java How to Program 4 2002 The Complete Java Training Course 4 2002 The Complete e-Business & e-Commerce Programming Training Course 1 2001 The Complete Internet & World Wide Web Programming Training Course 2 2001 The Complete Perl Training Course 1 2001 The Complete XML Programming Training Course 1 2001 C How to Program 3 2001 C++ How to Program 3 2001 The Complete C++ Training Course 3 2001 e-Business and e-Commerce How to Program 1 2001 Internet and World Wide Web How to Program 1 2000 4 The Complete Internet and World Wide Web Programming Training Course 1 2000 How to Program (Java 2) 3 2000 The Complete Java 2 Training Course 3 2000 XML How to Program 1 2001 Perl How to Program 1 2001 Advanced Java 2 Platform How to Program 1 2002 e Business and e-Commerce for managers 1 2000 Wireless Internet and Mobile Business How to Program 1 2001 C# How to Program 1 2002 Python How to Program 1 2002 Visual Basic .NET Howto Program 2 2002 Условие выражения where может содержать операции <, >, <=, >=, =, о и like. Оператор like используется для сопоставления с эталоном групповых символов "звездочки" (*) и вопросительного знака (?). Сопоставление с эталоном позволяет языку SQL находить строки, "совпадающие с эталоном". Эталон, содержащий *, помогает осуществить поиск строк, в которых ноль или более символов занимают место "звездочки" в эталоне. Например, следующий запрос выбирает записи всех авторов, фамилии которых начина- ются с буквы D: SELECT authorlD, firstName, lastName FROM Authors WHERE lastName LIKE ' D*' Представленным выше запросом выбираются две записи, показанные в табл. 16.12, потому два автора в базе данных имеют фамилии, начинающиеся с буквы D (за которой следует ноль или более символов). "Звездочка" в эталоне like выражения where указывает, что после буквы D в поле lastName может появиться любое количест- во символов. Обратите внимание, что строка эталона заключена в одинарные кавычки. Совет по повышению портативности___________________________________________________________ Не все СУБД поддерживают оператор like, поэтому перед его применением внимательно ознакомьтесь с доку- ментацией по использованию системы. Совет по повышению переносимости________________________'____________________________ В большинстве баз данных в выражениях like вместо символа * используется символ %. В некоторых базах данных строковые данные, названия таблиц и полей зависят от регистра.
572 Глава 16 О хорошем стиле программирования___________________________________ Традиционно, ключевые слова языка SQL должны записываться прописными буквами в системах, где регистр набора значения не имеет. Этим ключевые слова SQL выделяются в операторе SQL. Таблица 16.12. Авторы из таблицы Authors с фамилиями, начинающимися с буквы D authorlD firstName lastName 1 Harvey Deitel 2 Paul Deitel Эталонная строка, включающая в себя вопросительный знак (?), осуществляет поиск строк, где только один символ занимает в эталоне место вопросительного знака. Например, следующий запрос осуществляет выборку записей всех авторов, фамилия которых начинается с любого символа (обозначенного символом ?, за которым следует буква i и любое количество дополнительных символов (обозначенных символом *): SELECT authorlD, firstName, lastName FROM Authors WHERE lastName LIKE '?i*' Приведенный запрос создает записи, представленные в табл. 16.13; пять авторов в базе данных имеют фамилии, в которых вторая буква — i. Совет по повышению переносимости____________________________________________________________ В большинстве баз данных в выражениях like вместо символа _ используется символ ?. Таблица 16.13. Список авторов из'таблицы Authors, в чьих фамилиях вторая буква — / authorlD firstName lastName 3 Tem Nieto 6 Ted Lin 11 Ben Wiedermann 12 Jonathan Liperi 13 Jeffrey Listfield 16.4.3. Выражение ORDER BY Результаты запроса можно расположить в восходящем или нисходящем порядке с помощью необязательного выражения order by. Самые простые формы выражения order by таковы: SELECT поле1, поле2, ... FROM таблица ORDER BY поле ASC SELECT поле1, поле2, FROM таблица ORDER BY поле DESC где asc обозначает восходящий порядок (от англ, ascending— от нижнего к верхнему), desc— нисходящий порядок (от англ, descending — от верхнего к нижнему), а полеы — имя поля, значения которого определяют порядок сортировки. Например, для получения списка авторов в восходящем порядке по последнему имени (табл. 16.14) воспользуй- тесь запросом: SELECT authorlD, firstName, lastName FROM Authors ORDER BY lastName ASC Обратите внимание, что порядок сортировки по умолчанию — восходящий, поэтому выражение asc — необяза- тельно. Для получения того же списка авторов в нисходящем порядке по фамилии (табл. 16.15) воспользуйтесь за- просом: SELECT authorlD, firstName, lastName FROM Authors ORDER BY lastName DESC
Базы данных, SQL и ADO.NET 573 Таблица 16.14. Список авторов из таблицы Authors в восходящем порядке сортировки по фамилии (lastName) authorlD firstName lastName 2 Paul Deitel 1 Harvey Deitel 6 Ted Lin 12 Jonathan Liperi 13 Jeffrey Listfield 8 David McPhie 3 Tern Nieto 7 Praveen Sadhu 5 Sean Santry 4 Kate Steinbuhler 11 Ben Wiedermann 9 Cheryl Yaeger 10 Marina Zlatkina Таблица 16.15. Список авторов из таблицы Authors в нисходящем порядке сортировки по фамилии (lastName) authorlD firstName lastName 10 Marina Zlatkina 9 Cheryl Yaeger 11 Ben Wiedermann 4 Kate Steinbuhler 5 Sean Santry 7 Praveen Sadhu 3 Tem Nieto 8 David McPhie 13 Jeffrey Listfield 12 Jonathan Liperi 6 Ted Lin 2 Paul Deitel 1 Harvey Deitel Выражение order by также можно использовать для упорядочения записей по нескольким полям. Такие запро- сы записываются в форме: ORDER BY поле! порядок_сортировки, поле2 порядок_сортировки, — где порядок_сорт!фовки— asc или desc. Обратите внимание, что значение порядок_сортировки не обязательно должен быть одинаковым для каждого поля. Например, запрос SELECT authorlD, firstName, lastName FROM Authors ORDER BY lastName, firstName сортирует всех авторов в восходящем порядке сначала по фамилии, а потом по имени. Это означает, что если какие-либо авторы имеют одинаковую фамилию, то их записи возвращаются отсортированными по имени (табл. 16.16).
574 Глава 16 Таблица 16.16. Список авторов из таблицы Authors в восходящем порядке сортировки по фамилии (lastName) и имени (firstName) authorlD firstName lastName 1 Harvey Deitel 2 Paul Deitel 6 Ted Lin 12 Jonathan Liperi 13 Jeffrey Listfield 8 David McPhie 3 Tem Nieto 7 Praveen Sadhu 5 Sean Santry 4 Kate Steinbuhler 11 Ben Wiedermann 9 Cheryl Yaeger 10 Marina Zlatkina Выражения where и order by можно объединить в одном запросе. Например, запрос SELECT ISBN, title, editionNumber, copyright, price FROM Titles WHERE title LIKE '*How to Program' DRIER BY title ASC возвращает номер ISBN, заголовок, номер издания и цену каждой книги в таблице Titles, имеющие окончание title, следующее после заголовка "How to Program"; эти записи приведены в восходящем порядке по заголов- кам (title). Результаты запроса представлены в табл. 16.17. Таблица 16.17. Список книг из таблицы Titles, заголовки которых заканчиваются фразой "How to Program", в восходящем порядке по заголовкам (title) ISBN title edition-Number copyright price 0130895601 Advanced Java 2 Platform How to Program 1 2002 $69.95 0131180436 C How to Program 1 1992 $69.95 0130895725 C How to Program 3 2001 $69.95 0132261197 C How to Program 2 1994 $49.95 0130622214 C# Hpw to Program 1 2002 $69.95 0135289106 C++ How to Program 2 1998 $49.95 0131173340 C++ How to Program 1 1994 $69.95 0130895717 C++ How to Program 3 2001 $69.95 013028419X e-Business and e-Commerce How to Program 1 2001 $69.95 0130308978 Internet and World Wide Web How to Program 2 2002 $69.95 0130161438 Internet and World Wide Web How to Program 1 2000 $69.95 0130341517 Java How to Program 4 2002 $69.95 0136325890 Java How to Program 1 1998 $49.95 0130284181 Perl How to Program 1 2001 $69.95 0130923613 Python How to Program 1 2002 $69.95
Базы данных, SQL и ADO.NET 575 Таблица 16.17 (окончание) ISBN title edition-Number copyright price 0130293636 Visual Basic.NET How to Program 2 2002 $69.95 0134569555 Visual Basic 6 How to Program 1 1999 $69.95 0130622265 Wireless Internet and Mobile Business How to Program 1 2001 $69.95 0130284173 XML How to Program 1 2001- $109.95 16.4.4. Слияние данных из нескольких таблиц: INNER JOIN Разработчики баз данных часто разделяют связанные данные по отдельным таблицам для того, чтобы в базе не сохранялись лишние данные. Например, в базе данных Books содержатся таблицы Authors и Titles. Таблица AuthoriSBN используется для создания "связок" между авторами и соответствующими им заголовками книг. Если бы данная информация не разделялась по разным таблицам, тогда информация об авторе должна быть включена в каждую запись таблицы Titles. В результате этого информация об авторах, написавших несколько книг, дублировалась бы в базе данных. Часто для целей анализа данные из разных таблиц необходимо объединять в единый набор данных. Этот про- цесс называется слиянием и осуществляется с помощью операции inner join в запросе select. Операция inner join (внутреннее соединение) осуществляет слияние записей из одной или нескольких таблиц по совпадающим значениям в поле, являющемся общим для этих таблиц. Самый простой формат для выражения inner join таков: SELECT поле!, поле2, ... FROM таблица! INNER JOIN таблица2 ON таблица!.поле = таблица2.поле Часть on выражения inner join указывает поля каждой таблицы, сравниваемые для определения объединяемых записей. Например, следующий запрос создает список авторов, за которым следуют номера ISBN книг, напи- санных каждым автором: SELECT firstName, lastName, ISBN FROM Authors INNER JOIN AuthoriSBN ON Authors.authorlD = AuthoriSBN.authorlD ORDER BY lastName, firstName Данный запрос объединяет ПОЛЯ firstName и lastName из таблицы Authors С полем ISBN из таблицы AuthoriSBN с сортировкой результатов в восходящем порядке по lastName и firstName. Обратите внимание на использова- ние синтаксиса таблица.поле в части on выражения inner join. Данный синтаксис (называется полностью оп- ределенным именем) указывает поля каждой таблицы, которые необходимо сравнить для объединения таблиц. Синтаксис, включающий имя таблицы, необходим, если поля в обеих таблицах имеют одинаковые имена. Тот же синтаксис можно применять в любом запросе для различения полей разных таблиц, имеющих одинаковые имена. Полностью определенные имена, начинающиеся с имени базы данных, можно использовать для пере- крестных ссылок в базе данных. Замечание по технологии программирования_________________________________________________ Если оператор SQL включает в себя поля с одинаковыми именами из нескольких таблиц, тогда перед операто- ром необходимо вводить имена этих полей с именами таблиц, в которых они содержатся, и оператором . — точ- ка (например, Authors.authorlD). Распространенная ошибка программирования_________________________________________________ Если в запросе не указаны полностью определенные имена для полей с одинаковыми именами в двух или более таблицах, то это является ошибкой. Как всегда, запрос может содержать выражение order by. В табл. 16.18 представлены результаты предшест- вующего запроса, отсортированного ПО полям lastName и firstName. Примечание_______________________________________________________________________________ Для экономии места результаты запроса разделены на две части, в каждой из которых содержатся поля firstName, lastName и ISBN.
576 Глава 16 Таблица 16.18. Список авторов из таблицы Authors и номера ISBN книг, отсортированные в восходящем порядке по полям lastName и firstName fisrtName lastName ISBN firstName LastName ISBN Harvey Deitel 0130895601 Harvey Deitel 0130829293 Harvey Deitel 0130284181 Harvey Deitel 0134569555 Harvey Deitel 0130284173 Harvey Deitel 0130829277 Harvey Deitel 0130852473 Paul Deitel 0130125075 Harvey Deitel 0138993947 Paul Deitel 0130856118 Harvey Deitel 0130856118 Paul Deitel 0130161438 Harvey Deitel 0130161438 Paul Deitel 013028419X Harvey Deitel 013028419x Paul Deitel 0139163050 Harvey Deitel 0139163050 Paul Deitel 0130895601 Harvey Deitel 0135289106 Paul Deitel 0135289106 Harvey Deitel 0130895717 Paul Deitel 0130895717 Harvey Deitel 0132261197 Paul Deitel 0132261197 Harvey Deitel 0130895725 Paul Deitel 0130895725 Harvey Deitel 0130125075 Paul Deitel 0130284181 Paul Deitel 0130284181 Tern Nieto 0130284173 Paul Deitel 0130284173 Tern Nieto 0130829293 Paul Deitel 0130829293 Tern Nieto 0134569555 Paul Deitel 0134569555 Tem Nieto 0130856118 Paul Deitel 0130829277 Tern Nieto 0130161438 Paul Deitel 0130852473 Tem Nieto 013028419x Paul Deitel 0138993947 16.4.5. Объединение данных из таблиц Authors, AuthoriSBN, Titles и Publishers База данных Books содержит один предварительно созданный запрос (TitleAuthor), выбирающий из базы дан- ных в качестве результатов заголовок, номер ISBN, имя автора, его фамилию, год регистрации авторского права и имя издателя каждой книги. Для книг, имеющих несколько авторов, запрос создает отдельную составную запись по каждому автору. Запрос TitleAuthor представлен в листинге 16.1’. В табл. 16.19 содержится часть результатов запроса. 1 SELECT Titles.title, Titles,ISBN, Authors.firstName, 2 Authors.lastName, Titles.copyright, 3 Publishers.publisherName 4 FROM 5 (Publishers INNER JOIN Titles 6 ON Publishers.publisherlD = Titles.publisherlD) 7 INNER JOIN 8 (Authors INNER JOIN AuthoriSBN 9 ON Authors.authorlD = AuthoriSBN.authorlD) 10 ON Titles.ISBN = AuthoriSBN.ISBN 11 ORDER BY Titles.title 1 Номера строк не входят в реальный запрос. — Ред.
Базы данных, SQL и ADO. NET 577 Таблица 16.19. Часть результатов, созданных запросом из листинга 16.1 Title ISBN firstName lastName copy-right publisherName Advanced Java 2 Platform How to Program 0130895601 Paul Deitel 2002 Prentice Hall Advanced Java 2 Platform How to Program 0130895601 Harvey Deitel 2002 Prentice Hall Advanced Java 2 Platform How to Program 0130895601 Sean Santry 2002 Prentice Hall C How to Program 0131180436 Harvey Deitel 1992 Prentice Hall C How to Program 0131180436 Paul Deitel 1992 Prentice Hall C How to Program ' 0132261197 Harvey Deitel 1994 Prentice Hall C How to Program 0132261197 Paul Deitel 1994 Prentice Hall C How to Program 0130895725 Harvey Deitel 2001 Prentice Hall C How to Program 0130895725 Paul Deitel 2001 Prentice НаП C# How to Program 0130622214 Tem Nieto 2002 Prentice Hall C# How to Program 0130622214 Paul Deitel 2002 Prentice Hall C# How to Program 0130622214 Jeffrey Listfield 2002 Prentice Hall C# How to Program 0130622214 Cheryl Yaeger 2002 Prentice Hall C# How to Program 0130622214 Marina Zlatkina 2002 Prentice Hall C# How to Program 0130622214 Harvey Deitel 2002 Prentice Hall C++ How to Program 0130895717 Paul Deitel 2001 Prentice Hall C++ How to Program 0130895717 Harvey Deitel 2001 Prentice Hall C++ How to Program 0131173340 Paul Deitel 1994 Prentice Hall C++ How to Program 0131173340 Harvey Deitel 1994 Prentice Hall C++ How to Program 0135289106 Harvey Deitel 1998 Prentice Hall C++ How to Program 0135289106 Paul Deitel 1998 Prentice Hall e-Business and e-Commerce for Managers 0130323640 Harvey Deitel 2000 Prentice Hall e-Business and e-Commerce for Managers 0130323640 Kate Steinbuhler 2000 Prentice Hall e-Business and e-Commerce for Managers 0130323640 Paul Deitel 2000 Prentice Hall e-Business and e-Commerce How to Program 013028419X Harvey Deitel 2001 Prentice Hall e-Business and e-Commerce How to Program 013028419X Paul Deitel 2001 Prentice Hall e-Business and e-Commerce How to Program 013028419X Tem Nieto 2001 Prentice Hall В запрос, представленный в листинге 16.1, добавлены отступы для удобочитаемости запроса. Теперь разделим запрос на несколько частей. В строках 1—3 содержится разделенный запятыми список полей, возвращаемых запросом; порядок полей слева направо указывает порядок полей в возвращенной таблице. В данном запросе из таблицы Titles выбираются поля title и ISBN, из таблицы Authors — поля firstName и lastName, из таблицы Titles — поле copyright, и поле publisherName — из таблицы Publishers. Для наглядности каждое имя поля было полностью определено с именем его таблицы (например, Titles. isbn). В строках 5—10 задаются операции inner join, используемые для комбинирования информации из различных таблиц. Существуют три типа операции inner join. Важно отметить, что, несмотря на выполнение inner join в двух таблицах, каждая из этих таблиц может быть результатом другого запроса или другой операции inner join. Для вложения операций inner join используются круглые скобки; сначала SQL оценивает самый внут- ренний набор скобок, постепенно перемещаясь наружу. Начнем со следующей операции inner join (Publishers INNER JOIN Titles ON Publishers.publisheriD = Titles.publisheriD) объединяющей таблицу Publishers с таблицей Titles при (on) условии, что номера publisheriD в каждой таб- лице совпадают. В результирующей временной таблице содержится информация о каждой книге и ее издателе. 37 Зак. 3333
578 Глава 16 Другой вложенный набор скобок содержит выражение inner join: (Authors INNER JOIN AuthoriSBN ON Authors.AuthorlD = AuthoriSBN.AuthorlD) Данное выражение объединяет таблицы Authors и AuthoriSBN при (on) условии, что поля AuthorlD в каждой таблице совпадают. Помните, что таблица AuthoriSBN хранит несколько записей для номеров ISBN книг, имеющих более одного автора. Третья операция inner join (Publishers INNER JOIN Titles ON Publishers.publisherlD = Titles.publisherlD) INNER JOIN (Authors INNER JOIN AuthoriSBN ON Authors.AuthorID = AuthoriSBN.authorlD) ON Titles.ISBN = AuthoriSBN.ISBN объединяет две временные таблицы, созданные двумя предыдущими объединениями при (on) условии, что поле Titles.isbn для каждой записи в первой временной таблице совпадает с соответствующим полем AuthoriSBN для каждой записи во второй временной таблице. Результатом всех этих операций является временная таблица, в которой выбираются нужные поля для создания результатов запроса. Наконец, в строке 11 запроса ORDER BY Titles.title указывается, что все записи должны быть отсортированы в восходящем порядке по заголовку (по умолчанию). 16.4.6 . Оператор INSERT С помощью оператора insert осуществляется вставка новой записи в таблицу. Простейшая форма оператора: INSERT INTO таблица (гюле1, поле2, —, гюляЫ) VALUES (значение!, значение2, ..., значение!]) где таблица — таблица, в которую вставляется запись. За именем таблицы следует разделенный запятыми спи- сок имен полей в скобках. За списком имен полей указывается ключевое слово SQL — values и разделенный запятыми список значений в скобках. Специальные значения в этом списке должны совпадать с именами полей, перечисленными после имени таблицы в обоих порядках сортировки и одного типа (например, если поле! ука- зано как поле firstName, тогда значение! должно представлять собой строку в одинарных кавычках, обозна- чающую имя автора). Оператор insert INSERT INTO Authors (firstName, lastName) VALUES ('Sue*, ’Smith') вставляет запись в таблицу Authors. Первый разделенный запятыми список указывает, что оператор предостав- ляет данные для полей firstName и lastName. Данными для вставки, содержащимися во втором разделенном запятыми списке, являются имя 'Sue' и фамилия 'Smith*. В этом примере не указывается идентификационный номер автора— authorlD, потому что это поле автоматически приращивается в базе данных. Каждой новой вставляемой в таблицу записи присваивается уникальное значение authorlD — следующее в последовательно- сти автоматического приращения (т. е. 1,2,3 ит. д.). В данном случае имени и фамилии sue smith будет при- своен идентификационный номер автора (authorlD)— 14. В табл. 16.20 показана таблица Authors после выпол- нения операции insert. Таблица 16.20. Таблица Authors после применения оператора insert для добавления записи authorlD firstName lastName 1 Harvey Deitel 2 Paul Deitel 3 Tem Nieto 4 Kate Steinbuhler 5 Sean Santry 6 Ted Lin 7 Praveen Sadhu
Базы данных, SQL и ADO.NET 579 Таблица 16.20 (окончание) authorlD firstName lastName 8 David McPhie 9 Cheryl Yaeger 10 Marina Zlatkina 11 Ben Wiedermann f 12 Jonathan Liperi 13 Jeffrey Listfield 14 Sue Smith Распространенная ошибка программирования__________________________________________________ В операторах SQL используется символ одинарных кавычек ('), служащий разделителем строк. Для задания в операторе SQL строки, содержащей одинарную кавычку (например, в фамилии O'Malley), строка должна иметь две одинарные кавычки в позиции, где должен появиться символ одинарной кавычки (например, О' ’Malley). Первая одинарная кавычка служит знаком перехода на вторую. Отсутствие одинарных кавычек перехода в стро- ке, выступающей частью оператора SQL, является синтаксической ошибкой SQL. 16.4.7 . Оператор UPDATE С помощью оператора update пользователь может изменять данные в таблице. Самая простая форма оператора update: UPDATE таблица SET поле! = значение!, поле2 = значение2, полеЫ = значениеи WHERE критерии где таблица — таблица, в которой будет изменена запись (записи). За именем таблицы следует ключевое слово set и разделенный запятыми список пар "имя = значение", записанный в формате поле = значение. Выражение where указывает критерии, используемые для определения записи (записей) с целью изменения. Например, опе- ратор UPDATE UPDATE Authors SET lastName = 'Jones' WHERE lastName' = 'Smith' AND firstName = 'Sue' обновляет запись в таблице Authors. Данный оператор указывает, что полю lastName будет присвоено новое значение Jones для записи, в которой значение поля lastName в данный момент — Smith, a firstName — Sue. Если authorlD известно до выполнения операции update (возможно, в результате предыдущего поиска authorlD), тогда выражение where можно упростить до WHERE AuthorlD =14 В табл. 16.21 представлена таблица Authors после выполнения операции update. У Таблица 16.21. Таблица Authors после применения оператора UPDATE для изменения записи authorlD firstName lastName 1 Harvey Deitel 2 Paul Deitel 3 Tem Nieto 4 Kate Steinbuhler 5 Sean Santry 6 Ted Lin 7 Praveen Sadhu 8 David McPhie 9 Cheryl Yaeger
580 Глава 16 Таблица 16.21 (окончание) authorlD firstName LastName 10 Marina Zlatkina 11 Ben Wiedermann 12 Jonathan Liperi 13 Jeffrey Listfield 14 Sue Jones Распространенная ошибка программирования______________________________________________ Отсутствие выражения where с оператором update может привести к появлению логических ошибок 16.4.8 . Оператор DELETE С помощью оператора delete языка SQL можно удалять записи из таблицы. Простейшая форма оператора delete: DELETE FROM таблица WHERE критерии где таблица — таблица, из которой будет удалена запись (записи). Выражение where указывает критерии, ис- пользуемые для определения записи (записей) с целью удаления. Например, оператор delete DELETE FROM Authors WHERE lastName = 'Jones' AND firstName = 'Sue' удаляет запись Sue Jones из таблицы Authors. Распространенная ошибка программирования______________________________________________ Выражения where могут соответствовать многим записям. При удалении записи из базы данных определите вы- ражение where, соответствующее только удаляемым записям. В табл. 16.22 представлена таблица Authors после выполнения оператора delete. Таблица 16.22. Таблица Authors после применения оператора delete для удаления записи authorlD firstName lastName 1 Harvey Deitel 2 Paul Deitel 3 Tern Nieto 4 Kate Steinbuhler 5 Sean Santry 6 Ted Lin 7 Praveen Sadhu 8 David McPhie 9 Cheryl Yaeger 10 Marina Zlatkina 11 Ben Wiedermann 12 Jonathan Liperi 13 Jeffrey Listfield 16.5. Объектная модель ADO.NET Объектная модель ADO.NET предоставляет API (Application Programming Interface, программный интерфейс приложения) для программного доступа к системам управления базами данных. ADO.NET была создана для .NET Framework и представляет собой следующее поколение технологии ADO (ActiveX Data Objects, объекты данных ActiveX).
Базы данных, SQL и ADO.NET 581 Пространство имен System. Data является корневым для ADO.NET API. Первичные пространства имен ADO.NET: System.Data.OleDb и System.Data.SqlClient содержат классы, позволяющие программам подклю- чаться к ресурсам данных и модифицировать их. В пространстве имен System.Data.OleDb содержатся классы, предназначенные для работы со многими ресурсами данных, тогда как пространство имен System.Data.SqlClient содержат классы, оптимизированные для работы с базами данных Microsoft SQL Server 2000. Экземпляры класса System.Data.DataSet, состоящие из набора объектов DataTables и взаимоотношений между ними, представляют кэши данных— данные, которые программа временно сохраняет в локальной памяти. Структура DataSet имитирует структуру реляционной базы данных. Преимущество использования класса DataSet заключается в том, что он — разъединенный’, для работы с данными в классе DataSet программе не нужно непрерывного подключения к источнику данных. Программа подключается к источнику данных только во время первоначального заполнения класса DataSet, после чего сохраняет все внесенные изменения. Именно поэтому программе не нужно активного, постоянного подключения к источнику данных. Экземпляры класса OleDbConnection (пространство имен System.Data.OleDb) представляет подключения к ис- точнику данных. Экземпляр класса OleDbDataAdapter подключается к источнику данных через экземпляр класса OleDbConnection и может заполнять класс DataSet данными из этого источника. Далее в главе создание и за- полнение класса DataSet будут рассмотрены более подробно. Экземпляр класса oleDbCommand (пространство имен System.Data.OleDb) представляет произвольную команду SQL, исполняемую в источнике данных. Про- грамма может использовать экземпляр класса OleDbCommand для манипуляций источником данных через класс OleDbConnection. Программист должен явно закрыть активное подключение к источнику данных после того, как больше изменений вноситься не будет. В отличие от классов DataSet объекты OleDbCommand не кэшируют данные в локальную память. Экземпляры классов SqlDataReader (пространство имен System.Data.SqlClient) и OleDbDataReader (простран- ство имен System.Data.OleDb) представляют подключения к источнику данных только для их чтения. Устройст- ва считывания данных обеспечивают оптимизированный в плане производительности способ извлечения из ба- зы больших объемов данных. В главе 17 представлено программное приложение с использованием класса OleDbReader. 16.6. Программирование с помощью ADO.NET: извлечение информации из базы данных В данном разделе приведены два примера, демонстрирующие подключение к базе данных, выполнение запроса и отображение его результатов. Использованная в примерах база данных Books, создана в Microsoft Access. В каждой программе должно быть указано местоположение этой базы данных на жестком диске компьютера. При исполнении примеров для каждой программы читатели должны обновлять местоположение. Например, перед запуском на своих машинах программы листинга 16.2 читатели должны изменить строки 234—247, чтобы код указывал корректное местоположение файла базы данных. 16.6.1. Подключение к источнику доступа к данным и запросы В первом примере (листинг 16.2) выполняется простой запрос к базе данных Books, извлекающий всю таблицу Authors и отображающий ее данные в классе DataGrid (компонентном классе пространства имен System.windows.Forms, отображающем источник данных в GUI). Программа иллюстрирует процесс подключе- ния к базе данных, запроса в ней и отображение результатов в классе DataGrid. Примечание__________________________________________________________________________ Весь код, автоматически сгенерированный Visual Studio .NET, представлен в листинге 16.2 для информирования пользователей. В примере используется база данных Access. Для регистрации базы данных Books в качестве источника данных выберите View | Server Explorer. Щелкните правой кнопкой мыши на узле Data Connections в Server Explorer, после чего дважды щелкните кнопкой мыши на строке <Add Connections На вкладке Provider появившегося окна выберите строку Microsoft Jet 4.0 OLE DB Provider, указывающую драйвер для баз данных Access. На вкладке Connection щелкните левой кнопкой мыши на эллипсовидную кнопку (...) справа от текстового поля имени базы данных; при этом откроется окно Select Access Database. Перейдите к нужной папке, выберите базу данных Books и нажмите кнопку ОК. Теперь нужная база данных зарегистрирована как подключение к Server Explorer. Перетяните мышью узел базы данных на форму Windows. При этом для источника будет создан класс OleDbConnection, который программа-проектировщик отобразит как oleDbConnectionl.
582 Глава 16 Затем перетяните класс OieDbDataAdapter из группы Data панели инструментов в проектировщик Windows Form. При этом появится окно мастера Data Adapter Configuration Wizard для конфигурирования экземпляра OieDbDataAdapter с пользовательским запросом с целью заполнения класса DataSet. Нажмите кнопку Next для выбора подключения. Выберите в раскрывающемся списке подключение, созданное в предыдущем шаге, и на- жмите кнопку Next. В следующем окне можно выбрать механизм доступа класса OieDbDataAdapter к базе дан- ных. Отметьте опцию по умолчанию Use SQL Statement и нажмите кнопку Next. Нажмите кнопку Query But- ton, выберите в меню Add таблицу Authors и закройте меню. Отметьте флажок *AU Columns в окне Authors. Обратите внимание на список столбцов таблицы Authors в этом окне. Затем необходимо создать класс DataSet для сохранения результатов запроса. Для этого перетяните мышью класс DataSet из группы Data в панели инструментов. При этом откроется окно Add DataSet. Выберите строку Untyped DataSet (no schema), потому что запрос, которым будет'заполняться DataSet, определяет схему, или структуру, класса DataSet. ..... -«мим • ♦ ж ... чшма « ~ г~чеиг-’г«ик----- [ Листинг 16.2. Доступ к данным базы и их отображение 1 // Листинг 16.2: TableDisplay.cs 2 // Отображение данных из таблицы базы данных 3 4 using System; 5 using System.Drawing; 6 using System.Collections; 7 using System.ComponentModel; 8 using System.Windows.Forms; 9 using System.Data; 10 11 // Общее описание TableDisplay.es 12 public class TableDisplay : System.Windows.Forms.Form 13 { 14 private System.Data.DataSet dataSet1; 15 private System.Data.OleDb.OieDbDataAdapter oleDbDataAdapterl; 16 private System.Windows.Forms.DataGrid dataGridl; 17 private System.Data.OleDb.OleDbCommand oleDbSelectCommandl; 18 private System.Data.OleDb.OleDbCommand oleDblnsertCommandl; 19 private System.Data.OleDb.OleDbCommand oleDbUpdateCommandl; 20 private System.Data.OleDb.OleDbCommand oleDbDeleteCommandl; 21 private System.Data.OleDb.OleDbConnection oleDbConnectionl; 22 23 private System.ComponentModel.Container components = null; 24 25 public TableDisplay() 26 { 27 InitializeComponent(); 28 29 // Заполнение класса dataSet1 данными 30 oleDbDataAdapterl.Fill(dataSet1, "Authors"); 31 32 // связывание данных в таблице Authors в dataSet1 с dataGridl 33 dataGridl.SetDataBinding(dataSet1, "Authors"); 34 } 35 36 private void InitializeComponent() 37 { 38 this.dataSetl = new System.Data.DataSet(); 39 this.oleDbDataAdapterl = 4 0 new System.Data.OleDb.OieDbDataAdapter(); 41 this.dataGridl = new System.Windows.Forms.DataGridO; 42 this.oleDbSelectCommandl = 43 new System.Data.OleDb.OleDbCommand(); 44 this.oleDblnsertCommandl = 45 new System.Data.OleDb.OleDbCommandO; 46 this.oleDbUpdateCommandl = 47 new System.Data.OleDb.OleDbCommand();
Базы данных, SQL и ADO.NET 583 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 this.oleDbDeleteCommandl = new System.Data.OleDb.OleDbCommand(); this.oleDbConnectionl = new System.Data.OleDb.OleDbConnection (); ((System.ComponentModel.ISupportlmtialize) (this.dataSetl)).Beginlnit(); ((System.ComponentModel.ISupportInitialize) (this.dataGridl)).Beginlnit(); this.SuspendLayout(); // // dataSetl // this.dataSetl.DataSetName = "NewDataSet"; this.dataSetl.Locale new System.Globalization.Culturelnfor("en-US"); // // OleDbDataAdapterl // this.oleDbDataAdapterl.DeleteCommand = this.oleDbDeleteCommandl; this.oleDbDataAdapterl.Insertcommand = this.oleDblnsertCommandl; this.oleDbDataAdapterl.SelectCommand = this.oleDbSelectCommandl; this.oleDbDataAdapterl.TableMappings.AddRange( new System.Data.Common.DataTableMappingt] { new System.Data.Common.DataTableMapping( ’’Table”, "Authors", new System.Data.Common.DataColumnMapping[] { new System.Data.Common.DataColumnMapping( "authorlD", "authorlD"), new System.Data.Common.DataCoZ umnMapping( "firstName", "firstName"), new System,. Data. Common. DataCoiumnMapping ( "lastName", "lastName")})}); this.oleDbDataAdapterl.UpdateCommand = this.oleDbUpdateCommandl; // // dataGridl // this.dataGridl.Datamember = this.dataGridl.HeaderForeColor = System.Drawing.SystemColors.ControlText; this.dataGridl.Location = new System.Drawing.Point(16, 16); this.dataGridl.Name = "dataGridl"; this.dataGridl.Size = new System.Drawing.Size(264, 248); this.dataGridl.Tabindex = 0; // 11 oleDbSelectCommandl // this.oleDbSelectCommandl.CommandText - "SELECT authorlD, firstName, lastName FROM Authors"; this.oleDbSelectCommandl.Connection = this.oleDbConnectionl; // // oleDblnsertCommandl // this.oleDblnsertCommandl.CommandText = "INSERT INTO Authors(firstName, lastName) VALUES " + ” (? , ?) " ; this.oleDblnsertCommandl.Connection = this.oleDbConnectionl;
584 Глава 16 Ill 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 this.oleDblnsertCommandl.Parameters.Add( new System.Data.OleDb.OleDbParameter("firstName", System.Data.OleDb.OleDbType.VarWChar, 50, "firstName")); this.oleDblnsertCoramandl.Parameters .Add( new System.Data.OleDb.OleDbParameter("lastName", System.Data.OleDb.OleDbType.VarWChar, 50, "lastName")); // // oleDbUpdateCommandl // this.oleDbUpdateCommandl.CommandText = "UPDATE Authors SET firstName = ?, lastName = ? WHERE" + " (authorlD = ?) AND (firstName" + "e = ? OR ? IS NULL AND firstName IS NULL) AND " + "(lastName = ? OR ? IS NULL And las" + "tName IS NULL)"; this.OleDbUpdateCommandl.Connect ion = this.oleDbConnectionl; this.oleDbUpdateCommandl.Parameters.Add( new System.Data.OleDb.OleDbParameter( "firstName", System.Data.OleDb.OleDbType.VarWChar, 50, "firstName")); this.oleDbUpdateCommandl.Parameters .Add( new System.Data.OleDb.OleDbParameter( "lastName", System.Data.OleDb.OleDbType.VarWChar, 50, "lastName")); this.oleDbUpdateCommandl.Parameters .Add( new System.Data.OleDb.OleDbParameter( "Original_authorID", System.Data.OleDb.OleDbType.Integer, 0, System.Data.ParameterDirection.Input, false, ((System.Byte) (10)), ((System.Byte) (0)), "authorlD", System.Data.DataRowVersion.Original, null)); this.oleDbUpdateCommandl.Parameters.Add( new System.Data.OleDb.OleDbParameter( "Original_firstName", System.Data.OleDb.OleDbType.VarWChar, 50, System.Data.ParameterDirection.Input, false, ((System.Byte) (0)), ((System.Byte) (0)), "firstName", System.Data.DataRowVersion.Original, null)); this.oleDbUpdateCommandl.Parameters.Add( new System.Data.OleDb.OleDbParameter( "Original_firstNamel", System.Data.OleDb.OleDbType.VarWChar, 50, System.Data.ParameterDirection.Input, false, ((System.Byte) (0)), ((System.Byte) (0)), "firstName", System.Data.DataRowVersion.Original, null)); this.oleDbUpdateCommandl.Parameters.Add( new System.Data.OleDb.OleDbParameter( "Original_lastName", System.Data.OleDb.OleDbType.VarWChar, 50, System.Data.ParameterDirection.Input, false, ((System.Byte) (0)), ((System.Byte) (0)), "lastName", System.Data.DataRowVersion.Original, null)); this.oleDbUpdateCommandl.Parameters.Add( new System.Data.OleDb.OleDbParameter(
Базы данных, SQL nADO.NET 585 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 "Original_lastNamel", System.Data.OleDb.OleDbType.VarWChar, 50, System.Data.ParameterDirection.Input, false, ((System.Byte) (0)), ((System.Byte) (0)), "lastName", System.Data.DataRowVersion.Original, null)); // // oleDbDeleteCommandl // this.oleDbDeleteCommandl.CommandText = "DELETE FROM Authors WHERE (authorlD = ?) AND " + "(firstName = ?) OR ,? IS NULL AND firs” + "tName IS NULL) AND (lastName = ? OR ? IS NULL) AND " + "lastName IS NULL)"; + this.oleDbDeleteCommandl.Connection = this.oleDbConnectionl; this.oleDbDeleteCommandl.Parameters. Add( new System.Data.OleDb.OleDbParameter( "Original_authorID", System.Data.OleDb.OleDbType.Integer, 0, System.Data.ParameterDirection.Input, false, ((System.Byte) (10)), ((System.Byte) (0)), "authorlD", System.Data.DataRowVersion.Original, null)); this.oleDbDeleteCommandl.Parameters.Add( new System.Data.OleDb.OleDbParameter( "Original_firstName", . System.Data.OleDb.OleDbType.VarWChar, 50, System.Data.ParameterDirection.Input, false, ((System.Byte) (0)), ((System.Byte) (0)), "firstName", System.Data.DataRowVersion.Original, null)); this.oleDbDeleteCommandl.Parameters .Add( new System.Data.OleDb.OleDbParameter( "Original_firstNamel", System.Data.OleDb.OleDbType.VarWChar, 50, System.Data.ParameterDirection.Input, false, ((System.Byte) (0)), ((System.Byte) (0)), "firstName", System.Data.DataRowVersion.Original, null)); this.oleDbDeleteCommandl.Parameters .Add( new System.Data.OleDb.OleDbParameter( "Original_lastName", System.Data.OleDb.OleDbType.VarWChar, 50, System..Data.ParameterDirection. Input, false, ((System.Byte) (0)), ((System.Byte) (0)), "lastName", System.Data.DataRowVersion.Original, null)); this.oleDbDeleteCommandl.Parameters.Add( new System.Data.OleDb.OleDbParameter( "Original_lastNamel", System.Data.OleDb.OleDbType.VarWChar, 50, System.Data.ParameterDirection.Input, false, ((System.Byte) (0)), ((System.feyte) (0)), "lastName", System.Data.DataRowVersion.Original, null) )•; // oleDbConnectionl // // this.oleDbConnectionl.Connectionstring = @"Provider=Microsoft.Jet.OLEDB.4.0;Password="""";" + @"User ID=Admin;Data Source=C:\Books\2001\csphtpl\" + @"csphtpl_examples\cpl9\Books.mdb;Mode=Share " +
586 Глава 16 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 @"Deny None;Extended Properties="""";Jet OLEDB:" + @"System database3*""""; Jet OLEDB:Registry " + @"Path="""";Jet OLEDB:Database Password*3"""";" + @”Jet OLEDB:EngineType=5;JetOLEDB:Database " + Locking Model=l;Jet OLEDB:Global Partial Bulk " + @"Ops=2; Jet OLEDB:Global Bulk Transactions*3!;Jet " + @"OLEDB:New Database Password3**"""";Jet OLEDB:" + @"Create System Database=False;Jet OLEDB:Encrypt " + @"Database=False; Jet OLEDB:Doin't Copy Locale on " + @"Compact=False;Jet OLEDB:Compact Without Replica " + @"Repair=False; Jet OLEDB:SFP=*False"; II // TableDisplay // this.AutoScaleBaseSize ₽ new System.Drawing.Size(5, 13); this.Clientsize = new System.Drawing.Size(292, 273); this.Controls.AddRange( new System. Windows.Forms.Control[] { this.dataGridl}); this.Name = "TableDisplay"; this.Text = "TableDisplay"; ((System.ComponentModel.ISupportInitialize) (this.dataSetl)).Endlnit(); ((System.ComponentModel.ISupportInitialize) (this.dataGridl)).Endlnit(); this.ResumeLayout(false); ) // конец InitializeComponent [STAThread] static void Main!) { Application.Run(new TableDisplay()); Рис. 16.4. Выборка данных из БД 271 } В листинге 16.2 представлен код, сгенерированный Visual Studio. Обычно этот код опускается, потому что, как правило, он содержит строки, относящиеся к GUI. Впрочем, в данном случае в этом коде задается функцио- нальность базы данных, которую необходимо обсудить. Более того, в примере оставлены правила именования Visual Studio по умолчанию для демонстрации точного формата кода, автоматически генерируемого Visual Studio. Как правило, эти имена изменяются для соответствия правилам и стилю программирования. Код, сгенерированный Visual Studio, также был отформатирован в целях наглядности. Распространенная ошибка программирования____________________________________________________ В коде пользуйтесь понятными описательными именами переменных. Это повышает удобочитаемость программы. В строках 233—247 инициализируется класс OleDbConnection для программы. Свойством Connectionstring задается путь для файла базы данных на жестком диске компьютера. В примере экземпляр класса oleDbDataAdapter заполняет класс DataSet данными из базы Books. Свойства экземпляра DeleteCommand (строки 66 и 67), InsertCommand (строки 68—69), SelectCommand (строки 70 и 71) и UpdateCommand (строки 83 и 84) являются объектами класса OleDbCommand, определяющего то, как класс OleDbDataAdapter удаляет, вставляет, выбирает и обновляет данные в базе соответственно. Каждый объект класса OleDbCommand должен обладать свойством OleDbConnection, посредством которого класс OleDbCommand может взаимодействовать с базой данных. Свойство Connection задается OleDbConnection базы данных Books. Для OleDbUpdateCommandl в строках 128 и 129 задается свойство Connection, а в строках 122— 127 — свойство CommandText. Несмотря на то, что большая часть кода программы генерируется Visual Studio .NET, код вводится в конструк- тор TableDisplay (строки 25—34) для заполнения dataSetl с помощью OleDbDataAdapter. В строке 30 вызыва- ется метод Fill класса OleDbDataAdapter для извлечения из базы данных информации, относящейся к объекту OleDbConnection, с ее размещением в классе DataSet, представленном в качестве аргумента. Вторым аргумен-
Базы данных, SQL и ADO.NET 587 том этого метода является строка, где указывается имя таблицы из базы данных, из которой будет заполняться (Fill) класс DataSet. В строке 33 активизируется метод SetDataBinding класса DataGrid для "привязывания" DataGrid к источнику данных. Первым аргументом является класс DataSet — в нашем случае dataSetl, данные которого должны ото- бражаться классом DataGrid. Второй аргумент— строка, представляющая имя таблицы в источнике данных, которую необходимо связать с DataGrid. После исполнения этой строки DataGrid заполняется информацией в классе DataSet: количество строк и столбцов указывается из информации в dataSetl. Результат работы программы представлен на рис. 16.4. 16.6.2. Запросы базы данных Books Пример в листинге 16.3 демонстрирует исполнение операторов SQL select в базе данных Books.mdb и отобра- жает результаты. Несмотря на то, что в листинге 16.3 для запроса данных используются только операторы select, эту же программу можно использовать для выполнения многих разных операторов SQL (при внесении небольших изменений). Листинг 16.3. Исполнение оператооов >QL в базе данных 1 // Листинг 16.3: DisplayQueryResults.es 2 // Отображение содержимого таблицы Authors 3 4 using System; 5 using System.Drawing; 6 using System.Collections; 7 using System.ComponentModel; 8 using System.Windows.Forms; 9 using System.Data; 10 11 public class DisplayQueryResults : System.Windows.Forms.Form 12 ( 13 private System.Data.OleDb.OleDbConnection oleDbConnectionl; 14 private System.Data.DataSet dataSetl; 15 private System.Data.OleDb.OieDbDataAdapter pleDbDataAdapterl; 16 private System.Data.OleDb.OleDbCommand oleDbSelectCommandl; 17 private System.Data.OleDb.OleDbCommand oleDblnsertCommandl; 18 private System.Data.OleDb.OleDbCommand oleDbUpdateCommandl; 19 private System.Data.OleDb.OleDbCommand oleDbDeleteCommandl; 20 private System.Windows.Forms.TextBox queryTextBox; 21 , private System.Windows.Forms.Button submitButton; 22 private System.Windows.Forms.DataGrid dataGridl; 23 private System.ComponentModel.Container components = null; 24 25 public DisplayQueryResults() 26 { 27 28 InitializeComponent(); 29 } 30 ' 31 // код, сгенерированный Visual Studio .NET 32 33 [STAThread] 34 .static void Main() 35 { 36 Application.Run(new DisplayQueryResults()); 37 } 38 39 // выполнить запрос SQL на данные 40 private void submitButton_Click(object sender, 41 System. EventsArgs e) 42 { 43 try
588 Глава 16 44 { 45 // установка запроса SQL в данные, 46 // введенные пользователем в queryTextBox 47 oleDbDataAdapterl. SelectCommand.Commandtext = 4 8 queryTextBox.text; 49 50 // сброс DataSet из предыдущей операции 51 dataSetl.Clear(); 52 53 // заполнение DataSet результирующей информацией 54 // из запроса SQL 55 oleDbDataAdapter.Fill(dataSetl, "Authors"); 56 57 // связывание DataGrid с содержимым DataSet 58 dataGridl.SetDataBinding(dataSet1, "Authors"); 59 } 60 61 catch (System.Data.OleDb.OleDbException oleException) 62 { 63 MessageBox.Show("Invalid query"); 64 } 65 66 } // конец submitButton Click 67 ) Метод submitButton_ciick является ключевой частью программы. При активизации программой этого обра- ботчика событий в строках 47 и 48 запрос string типа select присваивается свойству SelectCommand класса OleDbDataAdapter. Данная строка анализируется как запрос SQL и исполняется в базе данных через метод Fill класса OleDbDataAdapter (строка 55). Как упоминалось в предыдущем разделе, метод Fill помещает данные базы в класс dataSetl. Распространенная ошибка программирования ________________________________________________ Если класс DataSet был заполнен (Fill) хотя бы один раз, то отсутствие вызова метода Clear класса DataSet до повторного вызова метода Fill может привести к логическим ошибкам. Для отображения или повторного отображения содержимого в классе DataGrid воспользуйтесь методом setDataBinding. Первый аргумент— это источник данных для отображения в таблице: в данном случае, DataSet. Второй аргумент — строковое имя элемента источника данных для отображения (строка 58). Читатели могут попробовать вводить в текстовое поле свои запросы и исполнять их при нажатии кнопки Submit Query (рис. 16.5). Рис. 16.5. Выполнение SQL-запроса для выборки данных из таблицы Authors: а — сортировка по полю authorlD; б — сортировка по полю lastName
Базы данных, SQL и ADO.NET 589 16.7. Программирование с помощью ADO.NET: модификация базы данных В следующем примере реализуется простое программное приложение адресной книги, позволяющее пользова- телю вставлять, размещать и обновлять записи в базе данных Addressbook Microsoft Access. В приложении Addressbook (листинг 16.4) предоставляет GUI, позволяющий выполнять SQL-запросы в базе данных. Ранее в главе приводились примеры, демонстрирующие использование операторов select для запроса к базе данных. Здесь представлена та же функциональная возможность. иии*--- мл.»;»»»' Г Листинг 16.4. Изменения базы данных .......... U&MK 3.4.. 1 // Листинг 16.4: AddressBook.cs 2 // Использование операторов SQL для манипуляций базой данных 3 4 using System; 5 using System.Drawing; 6 using System.Collections; 7 using System.ComponentModel; 8 using System.Windows.Forms; 9 using System.Data; 10 11 public class AddressBook : System.Windows.Forms.Form 12 { 13 private System.Windows.Forms.TextBox faxTextBox; 14 private System.Windows.Forms.TextBox homeTextBox; 15 private System.Windows.Forms.TextBox firstTextBox; 16 private System.Windows.Forms.TextBox stateTextBox; 17 private System.Windows.Forms.TextBox idTextBox; 18 private System.Windows.Forms.TextBox lastTextBox; 19 private System.Windows.Forms.TextBox postalTextBox; 20 private System.Windows.Forms.TextBox addressTextBox; 21 private System.Windows.Forms.TextBox cityTextBox; 22 private System.Windows.Forms.TextBox countryTextBox; 23 private System.Windows.Forms.TextBox emailTextBox; 24 private System.Data.DataSet dataSetl; 25 private System.Data.OleDb.OieDbDataAdapter oleDbDataAdapterl; 26 private System.Data.OleDb.OleDbCommand oleDbSelectCommandl; 27 private System.Data.OleDb.OleDbCommand oleDblnsertCommandl; 28 private System.Data.OleDb.OleDbCommand oleDbUpdateCommandl; 29 private System.Data.OleDb.OleDbCommand oleDbDeleteCommandl; 30 private System.Data.OleDb.OleDbConnection oleDbConnectionl; 31 private System-Windows.Forms.TextBox statusTextBox; 32 private System.Windows.Forms.Label addressLabel; 33 private System.Windows.Forms.Label cityLabel; 34 private System.Windows.Forms.Label stateLabel; 35 private System.Windows.Forms.Label idLabel; 36 private System.Windows.Forms.Label firstLabel; 37 private System.Windows.Forms.Label lastLabel; 38 private System.Windows.Forms.Label postalLabel; 39 private System.Windows.Forms.Label countryLabel; 40 private System.Windows.Forms.Label emailLabel; 41 private System.Windows.Forms.Button clearButton; 42 private System.Windows.Forms.Button helpButton; 43 private System.Windows.Forms.Button findButton; 44 private System.Windows.Forms.Button addButton; 45 private System.Windows.Forms.Button updateButton; 46 private System.Windows.Forms.Label faxLabel; 47 private System.Windows.Forms.Label homeLabel; 48 private System.ComponentModel.Container components = null; 49
590 Глава 16 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 б9 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 public AddressBook() { InitializeComponent(); oleDbConnectionl.Open(); } // код, сгенерированный Visual Studio .NET [STAThread] static void Main () { Application.Run(new AddressBook()); x ) private void findButton_Click(object sender, System.EventArgs e) { try { if (lastTextBox.text != "") { // сброс DataSet из последней операции dataSet1.Clear(); // создание запроса SQL для обнаружения контакта //с указанной фамилией (lastName) oleDbDataAdapter1.SelectCommand.CommandText = "SELECT * FROM addresses WHERE lastName = ’" + lastTextBox.text + "'"; // заполнение dataSet1 результирующими // строками запроса oleDbDataAdapterl.Fill(dataSetl) ; ' I // отображение информации Display(dataSetl); statusTextBox.Text += "\r\nQuery successful\r\n"; ) else lastTextBox.Text = "Enter last name here then press Find"; } catch (System.Data.OleDb.OleDbException oleException) { Console.WriteLine(oleException.StackTrace); statusTextBox.text += oleException.ToString(); ) catch (InvalidOperationException invalidException) { MessageBox.Show(invalidException.Message); ) } // конец findButton_Click private void addButton_Click(object sender. System.EventArgs e) { try I if (lastTextBox.Text != "" && firstTextBox.Text != "") {
Базы данных, SQL и ADO.NET 591 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 // создание запроса SQL для вставки строки oleDbDataAdapterl.InsertCommand.Commandtext = "INSERT INTO addresses (" + "firstName, lastName, address, city, " + "stateorprovince, postalcode, country, " + "emailaddress, homephone, faxnumber" + ") VALUES ('"+ firstTextBox.Text + "', '" + lastTextBox.Text + ”', "' + addressTextBox.Text + "', '"+ cityTextBox.Text + "', '" + stateTextBox.Text + "', '" + postalTextBox.Text + "', '"+ countryTextBox.Text + "', '" + emailTextBox.Text + "', '" + homeTextBox.Text + "', "’ + faxTextBox.Text + " // уведомление пользователя об отправке запроса statusTextBox.text += "\r\nSending query: " + , oleDbDataAdapterl.Insertcommand.CommandText + "\r\n" ; // отправка запроса oleDbDataAdapterl.Insertcommand.ExecuteNonQuery(); statusTextBox.Text += "\r\nQuery successful\r\n"; } else statusTextBox.Text += "\r\nEnter at least first " + "and last name then press Add\r\n"; } catch (System.Data.OleDb.OleDbException oleException) { Console.WriteLine(oleException.StackTrace); statusTextBox.Text += oleException.TbStringO; } } // конец addButton_Click private void updateButton_Click(object sender, System.EventArgs e) { try { // подтверждение обнаружения пользователем записи // для обновления if (idTextBox.Text != "") { // установка запроса SQL для обновления всех // полей таблицы, номер id совпадает //с номером id в idTextBox oleDbDataAdapterl.UpdateCommand.Commandtext = "UPDATE addresses SET " + "firstName ="’ + firstTextBox.Text + " ', lastname="’ + lastTextBox.Text + " ', address='" + addressTextBox.Text + " ', city='" + cityTextBox.Text + ” ', stateorprovince="' + stateTextBox.Text + " ’, postalcode='" + postalTextBox.Text + " ', country='" + countryTextBox.Text + " ', emailaddress='" + emailTextBox.Text +
592 Глава 16 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 " ', homephone='" + homeTextBox.Text + ” ', faxnumber®'" + faxTextBox.Text + " ' WHERE id=" + idTextBox.Text; // уведомление пользователя об установке запроса statusTextBox.Text += "\r\nSending query: " + oleDbDataAdapter1.UpdateCommand.CommandText + "\r\n"; // выполнение запроса oleDbDataAdapterl.UpdateCommand.ExecuteHonQuery(); statusTextBox.Text += "\r\nQuery successful\r\n"; } else statusTextBox.Text += "\r\nYou may only update " + "an existing record. Use Find to locate the" + "record, then modify the information and " + "press Update.\r\n"; } 4 catch (System.Data.OleDb.OleDbException oleException) { Console.WriteLine(oleException.StackTrace); statusTextBox.Text += oleException.Tostring(); } } // конец updateButton_Click / private void clearButton_Click(object sender, System. EventArgs e) { idTextBox.Clear(); ClearTextBoxes(); private void helpButton_Click(object sender, System.EventArgs e) { statusTextBox.AppendText( "\r\nClick Find to locate a record\r\n" + "Click Add to insert a new record.\r\n" + "Click Update to update the information in a record " + "\r\nClick Clear to empty the textboxes"); } । private void Display(DataSet dataset) { try ( // получение первой таблицы данных - она всегда одна DataTable dataTable = dataset.Tables[0]; if (dataTable.Rows.Count != 0) < int recordNumber = (int) dataTable.Rows[0][0]; idTextBox.Text = recordNumber.ToStringO; firstTextBox.Text = {string) dataTable.Rows[0][1]; lastTextBox.Text = (string) dataTable.Rows [0] [2]; addressTextBox.Text = (string) dataTable.Rows[0][3];
Базы данных, SQL и ADO.NET ' 593 239 cityTextBox.Text = 240 (string) dataTable.Rows[0][4]; 241 stateTextBox.Text = 242 (string) dataTable.Rows[0][5]; 243 postalTextBox.Text * 244 (string) dataTable.Rows[0][ 6 ]; 245 countryTextBox.Text = 246 (string) dataTable.Rows[0]{7]; 247 emailTextBox.Text = 248 (string) dataTable.Rows[0][8]; 249 homeTextBox.Text = 250 (string) dataTable.Rows[0][ 9 ]; 251 faxTextBox.Text = 252 (string) dataTable.Rows[0][10]; 253 ) 254 255 else 256 statusTextBox.Text += "\r\nNo record found\r\n”; 257 } 258 259 catch(System.Data.OleDb.OleDbException oleException) 260 { 261 Console.WriteLine(oleException.StackTrace); 262 statusTextBox.Text += oleException.ToStringO; 263 ) 264 265 ) //конец Display 266 । 267 private void ClearTextBoxes() 268 { 2.6 9 firstTextBox.Clear (); 270 lastTextBox.Clear(); 271 addressTextBox.Clear(); 272 cityTextBox.Clear{); 273 stateTextBox.Clear(); 274 postalTextBox.Clear(); 275 countryTextBox.Clear(); 276 emailTextBox.Clear(); 277 home-TextBox.Clear (); , 278 faxTextBox.Clear(); 279 ) 280 } Обработчик события f indButton_ciick выполняет запрос select в базе данных для поиска записи, относящейся к строке в объекте lastTextBox. Он обозначает фамилию человека, запись о котором пользователь хочет из- влечь. В строке 72 активизируется метод clear класса DataSet для освобождения класса DataSet от всех преды- дущих данных. После этого в строках 76—78 текст запроса SQL модифицируется для выполнения соответст- вующей операции select. Этот оператор выполняется методом Fill (строка 82) класса OieDbDataAdapter, пере- даваемым классу DataSet в качестве аргумента. Наконец, текстовые поля (TextBox) обновляются обращением к методу Display (строка 85). Методы addButton_ciick и updateButton_ciick выполняют операции insert и update соответственно. В каж- дом методе используются элементы класса OleDbCommand для выполнения этих операций в базе данных. Свойст- ва экземпляра Insertcommand и UpdateCommand класса OieDbDataAdapter являются экземплярами класса OleDbCommand. Свойство CommandText класса OleDbCommand является строкой, представляющей оператор SQL, который выпол- няет объект OleDbCommand. Метод addButton_click задает данное свойство insertcommand для выполнения в базе соответствующего оператора insert (строки 113-—128). Метод updateButton_ciick устанавливает свойст- во UpdateCommand для выполнения в базе соответствующего оператора update. Метод ExecuteNonQuery класса OleDbCommand выполняет действие, указанное свойством CommandText. Сле- довательно, оператор INSERT, определенный объектом oleDbDataAdapterl.Insertcommand.CommandText в обработчике события addButton Click, выполняется, когда в строке 136 активизируется метод oleDbDataAdapterl.Insertcommand.ExecuteNonQuery. Точно так же, оператор UPDATE, определенный объектом 38 Зак. 3333 t
594 Глава 16 oleDbDataAdapterl.DeleteCommand.CommandText в обработчике события updateButton_Click, выполняется ме- тодом oleDbDataAdapter1.UpdateCommand.ExecuteNonQuery. Методом Display (строки 221—265) обновляется пользовательский интерфейс с данными из вновь извлеченной записи адресной книги. В строке 226 принимается объект DataTable из коллекции Tables класса DataSet. Объ- ект DataTable содержит результаты запроса SQL. В строке 228 определяется, возвращает ли запрос какие-либо строки. Свойство Rows в классе DataTable обеспечивает доступ ко всем записям, извлеченным по запросу. ; £ ' tNSEHTINTOaddressdsWname.lastname, address Йй fteteorrtoyincei. postals<-de. country. emaladdtessj h-xiephor л jtaieiunbwlVAUlE^P^. W-. WSdreSveef SomeCrty', SomeStatr''ЗЭЭ'г' ''jBeCuunt'y lnew.user@some^rver con ЭЗЭ-ЭЗЭ-ЭЗЭ? T7?.777-77777 * Сиг State. nostal.Cdcfe Country. Query successful Ж 4~nePhooef ТэдЧитов! Г Рис. 16.6. Изменение базы данных Addressbook: а — добавление новой записи; б — ввод данных для поиска записи; в — найденная запись; г — обновление записи Addtes*! |656 Center St. Я’сцШии*! 154321 :|Any County" 'Home! Sendms tjuerv; UPDAfE jd ke-rses S£l fitstname«’John1, f‘ " .7 £ Л аНгй irJn'i riMliui‘Pifte *infa? «йтпёш! агЬЧгмГr Vtr~irn*_~irimffl» t n гччт* i_________________ LastNaim jDoe-hampernackle lsomeone@isp.com s.a.,..b„„..:,д... 999-999-9999 : AddressBook г
Базы данных, SQL и ADO.NET 595 Свойство Rows сходно с двумерным прямоугольным массивом. В строке 230 извлекается поле с индексом 0, 0 (т. е. первый столбец данных в первой таблице) и сохраняется значение в переменной recordNumber. После это- го в строках 232—252 из объекта DataTable извлекаются остальные поля данных для заполнения пользователь- ского интерфейса. При нажатии кнопки Help приложение выводит команды в текстовой области внизу окна приложения (строки 214—218). Обработчик события для этой кнопки — helpButton_ciick. Нажатием кнопки Clear данные из тек- стовых полей удаляются. Этот обработчик события определен в методе clearButton_ciick; он использует про- цедурный метод ClearTextBoxes (строка 208). Этапы работы программы отображены на рис. 16.6. 16.8. Считывание файлов XML и запись в них Мощной функцией ADO.NET является возможность преобразования данных, сохраненных в источнике данных, в XML. Класс DataSet пространства имен System. Data предоставляет методы WriteXml, ReadXml и GetXml, по- зволяющие разработчикам создавать документы XML из источников данных и преобразовывать данные из XML в источники данных. Приведенное в листинге 16.5 программное приложение заполняет класс DataSet статисти- кой об игроках в бейсбол, после чего записывает данные в файл в виде XML. Данное приложение также ото- бражает XML-текст в текстовом поле. 1 II Листинг 16.5: XMLWriter.cs 2 // Демонстрация создания XML из класса DataSet ADO.NET 3 4 using System; 5 using System.Drawing; 6 using System.Collections; 7 using System.ComponentModel; 8 using Systern.Windows.Forms; 9 using System.Data; 10 11 public class DatabaseXMLWriter : System.Windows.Forms.Form 12 { 13 private System.Data.OleDb.OleDbConmnection baseballconnection; 14 private System.Data.OleDb.OleDbDataAdapter playersDataAdapter; 15 private System.Data.OleDb.OleDbCommand oleDbSelectCommandl; 16 private System.Data.OleDb.OleDbCommand oleDblnsertCommandl; 17 private System/Data.OleDb.OleDbCommand oleDbUpdateCommandl; 18 private System.Data.OleDb.OleDbCommand oleDbDeleteCommandl; 19 private System.Data.DataSet playersDataSet 20 private System.Windows.Forms.DataGrid playersDataGrid; 21 private Systern.Windows.Forms.Button writeButton; 22 private System.Windows.Forms.TextBox outputTextBox; 23 private System.ComponentModel.Container components = null; 24 25 public DataBaseXMLWriter() 26 { 27 // 28 // требуется для поддержки Windows Form Designer 29 // 30 InitializeComponent(); 31 32 // открытие подключения к базе данных 33 baseballconnection.Qpen(); 34 35 // заполнение класса DataSet данными из oleDbDataAdapter 36 playersDataAdapter.Fill(playersDataSet, "Players"); 37 38 // привязка DataGrid к DataSet 39 playersDataGrid.SetDataBinding(playersDataSet, "Players"); 40 41 ) 42
596 Глава 16 43 // код, сгенерированный Visual Studio .NET 44 45 // основная точка входа для приложения 46 [STAThread] 47 static void Main() 48 { 4 9 Application.Run(new DatabaseXMLWriter()); 50 } 51 52 // запись представления XML класса DataSet при нажатии кнопки 53 private void writeButton_Click( 54 object sender, System.EventArgs e) 55 { 56 // запись представления XML класса DataSet в файл 57 playersDataSet.WriteXml("Players.xml"); 58 59 // отображение XML в TextBox 60 outputTextBox.Text += "Writing the following XML:\n\n" + 61 playersDataSet.GetXml() + "\n\n"; 62 63 } 64 } Конструктор DatabaseXMLWriter (строки 25—41) устанавливает в строке 33 подключение к базе данных Baseball. После этого в строке 36 вызывается метод Fill класса oieDbDataAdapter для заполнения объекта playersDataSet данными таблицы Players из базы данных Baseball. В строке 39 класс playerDataGrid связы- вается с playersDataSet для отображения информации пользователю. Метод writeButton Ciick определяет обработчик событий для кнопки Write to XLML (рис. 16.7). При нажатии пользователем этой кнопки в строке 57 активизируется метод writexml класса DataSet, генерирующий пред- ставление XML-данных, содержащихся в классе DataSet, и записывающий XML в специальный файл. В листин- ге 16.6 приведено это представление XML. Каждый элемент Players представляет запись в таблице Players. Элементы firstName, lastName, battingAverage и playerlD соответствуют полям С такими же именами в форме XML. В строках 60 и 61 строка XML добавляется в класс outputTextBox. Writng the folowing XML<NewDataSet> <Р1ауегэ> <firat Name) John..first Name> <JastName>Doe</te«tNaine> <battingAverage>0.375</baitingAverage> q>layerlD>1 <7playerlD> Рис. 16.7. Окно программы, создающей XML-документ из таблицы базы данных Baseball ---"—ГТ——7Г"-------- *—7“—Т-—Г7 ST-----Т Листинг 16.6. Документ XWIL, сгенерированный из класса Dataset в DatabaseXMLW zer 1*1?- ..........................л: .....к» ... 1 <?xml version="1.0” standalone="yes"?> 2 <NewDataSet> 3 <Players> 4 <firstName>John</firstName> 5 <lastName>Doe</lastName> 6 <battingAverage>0.375</battingAverage> 7 <playerID>l</playerID> 8 </Players>
Базы данных, SQL и ADO.NET 597 9 10 <Players> 11 <firstName>Jack</firstName> 12 <lastName>Smith</lastName> 13 <battingAverage>0.223</battingAverage> 14 <playerID>2</playerID> 15 </Players> 16 17 <Players> 18 <firstName>George</firstName> 19 <lastName>O'Malley</lastName> 20 <battingAverage>0.444</battingAverage> 21 <playerID>3</playerID> 22 </Players> 23 </NewDataSet> \ В данной главе рассматривались основы структурированного языка запросов (SQL) и возможности баз данных С#. Выяснилось, что программисты C# могут взаимодействовать с базами данных и манипулировать данными посредством Microsoft ActiveX Data Objects .NET, или ADO.NET. В следующей главе рассматриваются Web- формы ASP.NET. Web-формы дают программистам возможность разрабатывать динамические Web-материалы с помощью баз данных и функций ASP.NET. 16.9. Резюме Базой данных называется интегрированный набор данных. Система управления базой данных (СУБД) предос- тавляет механизмы для хранения и организации информации. Наиболее популярными в настоящее время систе- мами баз данных являются реляционные БД. Реляционная база данных состоит из таблиц. Строка таблицы называется записью. Первичным ключом называ- ется поле записи, содержащее уникальные данные, или данные, не дублирующиеся в других записях этой таб- лицы. Каждый столбец таблицы представляет отдельное поле. Первичный ключ может состоять из нескольких столбцов (или полей) в базе данных. Отношение "один ко многим" между таблицами указывает на то, что одна запись в одной таблице может иметь много соответствующих ей разных записей в другой таблице. Внешним ключом называется поле, для которого каждая запись в одной таблице имеет уникальное значение в другой таб- лице, где поле в последней таблице является ее первичным ключом. Структурированный язык запросов (SQL) используется практически во всех системах управления реляционны- ми базами данных для выполнения запросов и манипуляций данными. В SQL представлен полный набор команд, позволяющих программисту составлять сложные запросы для выбора данных из таблицы. Результаты запросов обычно называются результирующими множествами (или множествами записей). Запрос select используется для извлечения данных из их базы. Необязательное выражение where задает для запроса критерии отбора. Условие выражения where может содержать операции <, >, <=, >=, =, о и like. Опера- тор like предназначен для сопоставления образцов поиска с помощью символов "звездочки” (*) и вопроситель- ного знака (?). Результаты запроса могут формировать в возрастающем или убывающем порядке посредством необязательного выражения order by. В процессе объединения совмещаются записи из двух или более таблиц путем проверки наличия совпадающих значений в поле, общем для всех таблиц. С помощью оператора insert в таблицу вставляется новая запись. Посредством оператора update пользователь обновляет запись, а с примене- нием оператора delete запись из таблицы удаляется. Язык программирования позволяет подключаться к реляционным базам данных и взаимодействовать с ними посредством интерфейса. Программисты C# могут взаимодействовать с базами данных и манипулировать дан- ными посредством технологии ADO.NET. System.Data, System.Data.OleDb и System.Data.SqlClient— три основных пространства имен в ADO.NET. Класс DataSet принадлежит пространству имен System. Data. Экзем- пляры этого класса представляют кэши (хранилища) данных "в памяти". Преимущество использования класса DataSet заключается в том, что оно разрешает вносить изменения в содержимое источника данных без необхо- димости поддержания активного подключения. Класс OleDbCommand пространства имен System.Data.OleDb по- зволяет программистам выполнять операторы SQL непосредственно из источника данных. Для создания подключения к базе данных рекомендуется пользоваться опцией Add Connection. Для задания класса OleDbDataAdapter и генерирования запросов используйте мастер Data Adapter Configuration Wizard. Команды класса OleDbCommand выполняются классом OleDbDataAdapter в базе данных в форме запросов SQL.
598 Глава 16 Свойство Connection экземпляра OleDbCommand задается классу OleDbConnection, в котором будет выполняться команда, а свойство CommandText экземпляра задается запросу SQL, который станет выполняться в базе данных. С помощью метода Fill класс OleDbDataAdapter извлекает информацию из базы данных, относящейся к OleDbConnection, и помещает эту информацию в класс DataSet, предоставляемый в качестве аргумента. С по- мощью метода SetDataBinding класса DataGrid этот класс связывается с источником данных, Метод ExecuteNonQuery класса OleDbCommand выполняет действие, указанное в базе данных свойством CommandText. Мощной функцией ADO.NET является возможность преобразования данных, сохраненных в источнике данных, в XML и наоборот. Метод WriteXml класса DataSet записывает представление XML экземпляра DataSet в пер- вый переданный аргумент. Метод Readxml класса DataSet считывает представление XML первого переданного в него аргумента в его собственный класс DataSet.
ГЛАВА 17 ASP.NET, Web-формы и элементы управления Web Если человек изложит проблему и укажет свое имя внизу первой страницы, то я отвечу ему незамедлительно Если же он вынуждает меня перевернуть страницу, то ему придется дождаться, пока у меня появится свободное время. Лорд Сэндвич Правило 1: Клиент всегда прав. Правило 2: Если клиент не прав, см. Правило 1. Без подписи За четким вопросом должно следовать молчаливое действие. Данте Алигьери Вы придете сюда и найдете книги, которые раскроют ваши глаза, уши, любо- пытство, которые вывернут вас наизнанку. Ральф Уолдо Эмерсон Темы данной главы: П ознакомление с Web-формами в ASP.NET; □ создание Web-форм; □ создание приложения ASP.NET, состоящего из множественных Web-форм; □ управление пользовательским доступом к Web-приложениям посредством аутентификации форм; □ использование файлов и баз данных в приложениях ASP.NET; □ выполнение трассировки при работе с Web-формами. 17.1. Введение В предыдущих главах при разработке Windows-приложений использовались формы Windows и элементы управления Windows. В данной главе рассматривается разработка программных приложений на базе Web с помощью технологии ASP.NET корпорации Microsoft. Приложения на базе Web создают материалы для клиен- тов Web-браузеров В эти материалы (содержание) может входить гипертекстовый язык разметки (HyperText Markup Language, HTML), написание клиентских сценариев, графические изображения и двоичные данные. В этой главе приведено несколько примеров, демонстрирующих разработку приложений на базе Web, с по- мощью Web-форм (еще называемых страницами Web-форм), элементов управления Web (также называемых элементами управления сервера ASP.NET) и программирования на языке С#. Файлы Web-форм имеют расши- рение aspx и содержат пользовательский интерфейс Web-страницы. Программисты настраивают Web-формы путем добавления элементов управления Web, включающих в себя метки, текстовые поля, графические изобра- жения, кнопки и прочие компоненты графического пользовательского интерфейса. Файл Web-формы представ- ляет внешний вид Web-страницы, отправленной в клиентский браузер. Примечание_______________________________________________________________________ Здесь и далее в главе Web-файлы будут называться файлами ASPX 1 Читателям, не знакомым с HTML, перед прочтением данной главы следует изучить приложения 9 и 10.
600 Глава 17 Каждый файл ASPX, созданный в Visual Studio .NET, имеет соответствующий класс, написанный на языке, со- вместимом с .NET, например, на С#. В этот класс входят обработчики событий, код инициализации, методы использования (утилизации), а также прочие коды, поддерживающие пользовательский интерфейс в файле ASPX. Файл С#, содержащий этот класс, называется файлом фонового кода (code-behind file) и обеспечивает программное решение файла ASPX. 17.2. Простая транзакция HTTP Перед подробным рассмотрением процесса разработки программных приложений на базе Web необходимо по- нимание основ сетевого планирования и World Wide Web. В данном разделе рассматриваются внутренние про- цессы протокола HTTP (Hypertext Transfer Protocol, протокол передачи гипертекстовых файлов) и обсуждается "закулисная" деятельность во время отображения браузером Web-страницы. HTTP — это протокол, задающий набор методов и заголовков, обеспечивающих взаимодействие клиентов с серверами, обмен информацией меж- ду ними единообразным и предсказуемым способом. В самой простейшей форме Web-страницы представляют собой документы HTML: текстовые файлы, содержа- щие маркировку (разметку или теги), описывающую структуры документа. Например, разметка HTML <title>My Web Page</title> указывает, что текст, содержащийся между начальным тегом <title> и конечным тегом </title>, является за- головком Web-страницы. Документы HTML также могут содержать гиперссылки, которые дают пользователям возможность перехода в Web-браузерах на другие Web-страницы. При активизации пользователем гиперссылки (как правило, щелчком кнопки мыши) нужная Web-страница (или другой раздел той же Web-страницы) загру- жается в окно браузера. Любой имеющийся в сети документ HTML имеет унифицированный указатель информационного ресурса (Uniform Resource Locator, URL), указывающий местоположение ресурса. URL содержит информацию, направляющую Web-браузеры к документу. Компьютеры с программным обеспечением Web-cepeepa обеспечивают такие ресурсы. Internet Information Server (IIS, информационный сервер Интернета) корпорации Microsoft представляет собой Web-сервер, используемый программистами при разработке Web-приложений ASP.NET в Visual Studio. Рассмотрим некоторые компоненты URL: http://www.deitel.com/books/downloads.htm http:// указывает на то, что доступ к ресурсу будет осуществляться с помощью протокола HTTP. Средняя часть — www.deitel.com — полностью определенное имя хоста сервера. Именем хоста называется имя компь- ютера, на котором размещен ресурс. Такой компьютер обычно называется хостом (от англ, host — главная ма- шина), потому что на нем размещены обслуживаемые ресурсы. Имя хоста www.deitel.com преобразуется в ад- рес IP (Internet Protocol Address)— 63.110.43.82, — идентифицирующий сервер практически так же, как теле- фонный номер определяет ту или иную телефонную линию. Преобразование имени хоста в адрес IP обычно осуществляется сервером доменных имен (Domain Name Server, DNS) — компьютером, поддерживающим базу данных имен хостов и соответствующих им адресов IP. Такая операция преобразования называется поиском DNS. В оставшейся части URL представлено имя и местоположение запрошенного ресурса: /books/downloads.htm (документ HTML). Данная часть URL указывает имя ресурса (downloads.htm), а также путь (или местоположе- ние — /books) на Web-сервере. Путь может указать местоположение того или иного каталога в файловой систе- ме Web-сервера. Впрочем, из соображений безопасности пути часто указывают местоположения виртуального каталога. В таких системах сервер преобразует виртуальный каталог в реальное местоположение на сервере (или на другом компьютере серверной сети), скрывая, таким образом, истинное местоположение ресурса. Более того, некоторые ресурсы созданы динамически и не имеют заданного местоположения на сервере. Имя хоста в URL такого ресурса указывает точный сервер, путь, а информация о ресурсе идентифицирует местоположение ресурса, который будет взаимодействовать с запросом клиента. При наличии URL браузер выполняет простую транзакцию HTTP для извлечения и отображения Web-страницы. На рис. 17.1 транзакция показана подробно. Она состоит из взаимодействия между Web-браузером (со стороны клиента) и приложения Web-сервера (со стороны сервера). На рис. 17.1 показана отправка Web-браузером запроса HTTP на сервер. Данный запрос (в простейшей фор- ме) — выглядит следующим образом: GET /books/downloads.htm НТТР/1.1
ASP.NET, Web-формы и элементы управления Web 601 1. Клиент посылает GET-запрос на Web-сервер 2. После получения ответа Web-сервер выполняет поиск ресурса в своей системе Рис. 17.1. Взаимодействие Web-сервера и клиента. Шаг 1 запрос GET: GET/books/downloads .htm НТТР/1.1 Слово get представляет собой метод HTTP, указывающий на то, что клиент делает запрос на получение ресурса с сервера. В оставшейся части запроса указано имя пути ресурса, имя протокола и номер версии (нттр/1.1). Любой сервер, распознающий HTTP (версии 1.1), может преобразовать этот запрос и выдать соответствующий ответ. На рис. 17.2 показан ответ Web-сервера на успешный запрос. Сначала сервер отвечает отправкой стро- ки текста, где указана версия HTTP, за которой следует числовой код и фраза, описывающие состояние тран- закции. Например, нттр/1.1 200 ОК указывает на успешный запрос, тогда как ответ НТТР/1.1 200 404 Not found извещает клиента о том, что Web-сервер не может обнаружить запрошенный ресурс. Сервер посылает ответ с соответствующим сооощением о наличии ресурса Рис. 17.2. Взаимодействие клиента с Web-сервером. Шаг 2: ответ HTTP: НТТР/1.1 200 ОК После этого сервер отправляет один или несколько заголовков HTTP, предоставляющих информацию о переда- ваемых данных. В рассматриваемом случае сервер отправляет текстовый документ HTML, поэтому заголовок HTTP для нашего примера следующий: Content-type: text/html Данный заголовок задаст тип MIME (Multipurpose Internet Mail Extensions, многоцелевые расширения электрон- ной почты в Интернете) содержимого, которое сервер передает в браузер. MIME — это стандарт Интернета, применяемый для идентификации различных типов данных с тем, чтобы программы могли корректно интерпре- тировать эти данные. Например, тип MIME text/plain указывает, что информация представляет собой простой текст, который Web-браузер может отобразить напрямую, без какого-либо специального форматирования. По- добным же образом тип MIME image/gif указывает, что передаваемое содержимое представляет собой графи- ческое изображение в формате GIF, что дает браузеру возможность его корректного представления. За набором заголовков следует пустая строка, указывающая клиенту, что сервер закончил отправку заголовков HTTP. Затем сервер отправляет содержимое запрошенного документа HTML (downloads.htm). Сервер прерыва- ет подключение по завершении передачи ресурса. На данном этапе браузер на стороне клиента производит син- таксический анализ полученного HTML и визуализирует (отображает) результаты.
602 Глава 17 17.3. Системная архитектура Большинство программных приложений на базе Web являются многоуровневыми (иногда они еще называются л-уровневыми — multi-tier applications). В многоуровневых приложениях функциональность делится на отдель- ные уровни (т. е. логические группы функциональности). Несмотря на то, что уровни могут размещаться на од- ном компьютере, уровни приложений на базе Web, как правило, размещаются на разных компьютерах. На рис. 17.3 показана базовая структура трехуровневого приложения на базе Web. Промежуточный ASP NET Рис. 17.3. Трехуровневая архитектура Информационный уровень (также называемый уровнем данных или нижним уровнем) поддерживает данные, относящиеся к приложению. Данный уровень обычно сохраняет данные в системе управления реляционной ба- зой данных (Relational Database Management System, RDBMS). RDBMS рассматривались в главе 16. Например, в магазине розничной торговли может вестись база данных информации о наименованиях продукции: описания, цены и количества на складе. В этой же базе данных могут храниться сведения о заказчиках: имена пользовате- лей, адреса для выставления счетов, а также номера кредитных карт. Данный уровень может состоять из не- скольких баз данных; все вместе они содержат информацию, необходимую для приложения. В среднем уровне реализуется бизнес-логика, логика контроллера и логика представления для управления взаи- модействием между клиентами приложения и данными приложения. Средний уровень выступает в роли по- средника между данными информационного уровня и клиентами приложения. Логика контроллера среднего уровня обрабатывает запросы клиентов (например, запросы на просмотр каталога продукции) и извлекает дан- ные из базы. Затем логика представления среднего уровня обрабатывает данные информационного уровня и предоставляет содержимое клиенту. Обычно Web-приложения передают клиентам данные в форме документов HTML. Бизнес-логика среднего уровня вводит в действие деловой регламент и обеспечивает надежность данных до обновления серверным приложением базы данных или их передачи клиентам. Деловой регламент определяет организацию доступа клиентов к данным (или запрета доступа), а также механизмы обработки данных прило- жениями. Клиентский уровень (верхний уровень) реализуется пользовательским интерфейсом приложения, который, как правило, является Web-браузером. Пользователи напрямую взаимодействуют с приложением посредством пользовательского интерфейса. Клиентский уровень взаимодействует со средним уровнем для создания запро- сов и извлечения сведений из информационного уровня. После этого клиентский уровень отображает пользова- телю данные, извлеченные средним уровнем. 17.4. Создание и запуск простого примера Web-формы В данном разделе приведен первый пример приложения ASP.NET. При запуске данная программа выводит текст "A Simple Web Form Example” ("Пример простой Web-формы"), за которым отображается время Web- сервера. Как упоминалось ранее, программа состоит из двух взаимосвязанных файлов: файла ASPX (лис- тинг 17.1) и файла фонового кода C# (листинг 17.2). Результат работы приложения показан на рис. 17 4 Примечание______________________________________________________________ В целях наглядности разметка в листинге 17.1 и другие листинги файла ASPX в данной главе переформатиро- ваны. | Листинг 17.1, Страница ASPX, отображающая время Web-сервера i Й1 *> <а»Г>*-аа««к •• аааааааа aaaaOtaataaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa ааа aaaaaaaat a»aa«.'eaa»a«».liaaaaa¥*a . .£»№4^аМа4а<*а*а*Л^АаУЛ'<№мт^(^1{^1пМ«М«к«*а'Л1Йтаааа«а1<а«'««ааааааааава1>аа*а»аааааага1агааааКааа{4[а>»Й*«^агаааал>аа*а>аааа^ 1 <%— Листинг 17.1: WebTime.aspx —%> 2 <%— Страница, содержащая две метки —%> 3
ASP.NET, Web-формы и элементы управления Web 603 4 <%@ Page language="C#" Codebehind=’'WebTime.aspx.cs" 5 AutoEventWireup="false" Inherits="WebTime.WebTimeTest" 6 EnableSessionState="False" enabLeViewState="False"%> 7 8 <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN" > 9 10 <HTML> 11 <HEAD> 12 <title>WebTime</title> 13 <meta narae="GENERATOR" 14 Content="Microsoft Visual Studio 7.0"> 15 <meta name="CODE__LANGUAGE" Content="C#"> 16 <meta name="vs_defaultClientScript" 17 content+"JavaScript"> 18 <meta name="vs_targetSchema" 19 content=http://schemas.microsoft.com/intellisense/ie5> 20 </HEAD> 21 22 Cbody MS_POSITIONING="GridLayout"> 23 <form id="WebForml" method="post" runat="server"> 24 <asp:Label id="promptLabel" style="Z-INDEX: 101; 25 LEFT: 25px; POSITION: absolute; TOP: 23px" 26 runat="server" Font-Size="Medium”> 27 A Simple Web Form Example 28 </asp:Label> 29 30 <asp:Label id="timeLabel" style=”Z-INDEX: 102; 31 LEFT: 25px; POSITION: absolute-; TOP: 55px" 32 runat="server" Font-Size="XX-Large" 33 BackColor="Black" ForeColor="LimeGreen"> 34 </asp:Label> 35 </form> 36 </body> 37 </HTML> Visual Studio генерирует разметку, представленную в листинге 17.1, когда программист перетягивает две метки (Label) на Web-форму и задает их свойства. Обратите внимание, что, помимо HTML, файл ASPX содержит и другую информацию. Строки 1 и 2 в листинге 17.1 являются комментариями ASP.NET, в которых указаны порядковый номер листин- га, имя файла и его предназначение. Комментарии ASP.NET начинаются с символов <%— и заканчиваются сим- волами —%>. В строках 4—6 используется директива <%Раде.. .%> для указания информации, необходимой для обработки данного файла. Язык (language) файла фонового кода задан как с#, а имя этого файла — WebTime.aspx.cs. Атрибут AutoEventwireup определяет связь обработчиков событий с событиями элемента управления. При ус- тановке AutoEventwireup на true ASP.NET определяет ^етоды класса для вызова при ответе на событие, сгене- рированное взаимодействием пользователя с Web-страницей. ASP.NET вызовет нужные обработчики событий для элемента управления Web (исходя из особых правил именования для обработчиков событий) без использо- вания делегата; это освобождает программиста от необходимости добавления делегата для обработчика собы- тия. Такое освобождение особенно полезно, если разработчики не пользуется Visual Studio и, следовательно, вынуждены самостоятельно добавлять все коды. Когда Visual Studio .NET генерирует файл ASPX, атрибуту AutoEventwireup присваивается значение false, потому что Visual Studio генерирует необходимые делегаты события. Если установить AutoEventwireup в Visual Studio на true, где все делегаты добавляются автоматиче- ски, тогда обработчик события может быть вызван дважды: один раз через делегата, а второй — в результате AutoEventwireup. Атрибут inherits задает класс в файле фонового кода, от которого наследует документ ASP.NET, в данном случае — webTimeTest. Позднее атрибут inherits будет рассмотрен более подробно. Примечание__________________________________________________________________________________ Атрибуты EnableViewState и EnableSessionState явно устанавливаются на false. Далее в главе поясняется важность этих атрибутов.
604 Глава 17 Строка 8 называется объявлением типа документа (Document Type Declaration, DTD), указывающим имя эле- мента документа (html) и уникальный идентификатор ресурса (Uniform Resource Identifier, URI) для DTD. В строках 10 и 11 содержатся начальные теги <нтмь> и <head> соответственно. Документы HTML имеют корне- вой элемент html и размечают информацию о документе в элементе head. В строке 12 задается название Web- страницы. В строках 13—19 отображается серия метаэлементов (meta), содержащих информацию о документе. Двумя важными атрибутами метаэлемента являются name (идентифицирует метаэлемент) и content (сохраняет данные метаэлемента). Visual Studio генерирует эти метаэлементы при создании файла ASPX. В строке 22 содержится открывающий тег <body>, отмечающий начало просматриваемого содержимого Web- страницы; в теле (body) содержится информация, отображаемая браузером. Форма (form), содержащая другие элементы управления, определена в строках 23—35. Обратите внимание на атрибут runat в строке 23, установ- ленный на значение "server". Этот атрибут указывает на то, что сервер обрабатывает форму form и генерирует HTML для отправки клиенту. В строках 24—28 и 30—34 отображена разметка для двух элементов управления Label. Свойства, задаваемые в окне Properties,— например, Font-Size и Text, являются в данном случае атрибутами. Префикс теГа asp: в объявлении тега Label указывает, что данная метка является элементом управления ASP.NET. Каждый эле- мент управления Web отображается в соответствующий элемент HTML. Совет по повышению переносимости ___________________________________________________ Один тип элемента управления Web может отображаться в разные элементы HTML, в зависимости от браузера клиента и настроек свойств элемента управления. В данном примере элемент управления asp:Label отображается в элемент HTML span. Последний просто со- держит текст. Именно этот элемент используется потому, что элемент span упрощает применение стилей к тек- сту. Несколько значений свойств, примененных к меткам, представлены как часть атрибута style элемента span. Далее будут рассмотрены элементы span, созданные данным элементом управления. Каждый элемент управления Web в примере содержит пару "атрибут = значение" (runat="server"), потому что эти элементы управления должны обрабатываться на сервере. При отсутствии данной пары атрибутов элемент asp:Label записывается клиенту (т. е. элемент управления не будет преобразован в элемент span, и Web-браузер некорректно визуализирует элемент). В листинге 17.2 представлен файл фонового кода рассматриваемого примера приложения. Вспомните, что файл ASPX в листинге 17.1 ссылается в строке 4 на этот файл. Здесь представлен полный листинг кода. В остальных примерах части кода, не относящиеся к теме обсуждения, опускаются. В таких примерах будут помещены ком- ментарии для указания места, в которое будет вставлен сгенерированный код. Листинг 17.2. Файл Фонового кода для страницы, отображающей установленное время на Web-cepeepe к S 1 // Листинг 17.2: WebTime.aspx.cs 2 // Файл фонового кода для страницы, 3 // отображающей время Web-сервера 4 5 using System; б using.System.Collections; 7 using.System.ComponentModel; 8 using.System.Data; 9 using.System.Drawing; 10 using.System.Web; 11 using.System.Web.Sessionstate; 12 13 // определения для графич. элементов управления, использованных в Web-формах 14 using.System.Web.UI; 15 using.System.Web.UI.WebControls; 16 using.System.Web.UI.HtmlControls; 17 18 namespace WebTime 19 { 20 III <summary> 21 /// отображение текущего времени 22 /// </summary> 23 public class WebTimeTest : System.Web.UI.Page 24 { 25 protected System.Web.UI.WebControls.Label promptLabel;
ASP.NET, Web-формы и элементы управления Web 605 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 protected System.Web.UI.WebControls.Label timeLabel; // обработчик события Load private void Page_Load( object sender, System.EventArgs e) { // отображение текущего времени timeLabel.Text = String.Format("{0:D2}:{1:D2}:{2:D2}", DateTime.Now.Hour, DateTime.Now.Minute, DateTime.Now.Second); ) // обработчик события Init; установка // timeLabel на время Web-сервера #region Web Form Designer generated code override protected void Onlnit(EvenrArgs e) I // // CODEGEN: Данный вызов необходим // для ASP.NET Web Form Designer // InitializeComponent(); base.Onlnit(e); Рис. 17.4. Окно браузера с загруженным файлом ASPX III <summary> /// Требуемый метод для поддержки Designer: не изменять /// содержимое этого метода в редакторе кодов /// </summary> private void InitializeComponentО { this.Load += new System.EventHandler( this.Page_Load); } #endregion // конец класса WebTimeTest } // конец пространства имен WebTime Обратите внимание на директивы using в строках 10—16. Они указывают пространства имен, содержащие классы для разработки приложений на базе Web. Основное пространство имен — System.Web, содержащее клас- сы, управляющие клиентскими запросами и ответами сервера. Некоторые из перечисленных пространств имен определяют доступные элементы управления и различные манипуляции с ними; по мере необходимости прочие пространства имен будут рассматриваться в главе. В строке 23 начинается определение класса WebTimeTest, наследующего от класса Раде. Данный класс опреде- ляет запрашиваемую Web-страницу и расположен в пространстве имен System.Web.ui (строка 14), содержащем классы для создания Web-приложений и элементов управления ими. Класс Раде также предоставляет обработ- чики событий и объекты, необходимые для создания приложений на базе Web. Помимо класса Раде (от которо- го прямо или косвенно наследуют все Web-формы), пространство имен System.web.UI также включает в себя класс Control. Данный класс является базовым, обеспечивающим общую функциональность всех элементов управления Web. В строках 25 и 26 объявляются ссылки для двух элементов Label, которые являются элементами управления Web, определенными в пространстве имен System.Web.UI.WebControls (строка 15). Данное пространство имен содержит элементы управления Web, задействованные в разработке пользовательского интерфейса страницы. Элементы управления Web в этом пространстве имен являются производными от класса WebControl. В строках 42—50 определяется метод onlnit, вызываемый при событии ln.it. Данное событие, являющееся первым при запросе клиентом Web-формы, указывает на то, что страница готова к инициализации. Метод Onlnit вызывает метод InitializeComponent (определен в строках 56—60). Как и в Windows Forms, данный метод используется для задания первоначальных свойств компонентов приложения. Этот метод также можно
606 Глава 17 использовать для регистрации событий. Метод InitializeComponent создает и присваивает обработчик события Load, имеющий место при загрузке страницы (данное событие возникает после инициализации и загрузки на страницу всех элементов управления Web). После исполнения метода InitializeComponent метод Onlnit вызы- вает метод Onlnit базового класса (Раде) для выполнения любой необходимой дополнительной инициализации (строка 49). Как используются файл ASPX и файл фонового кода для создания отправляемой клиенту Web-страницы? Во- первых, вспомним, что класс WebTimeTest является базовым, указанным в строке 5 файла ASPX (см. лис- тинг 17.1). Класс WebTimeTest наследует от класса Раде, определяющего общую функциональность Web- страницы. Кроме наследования данной функциональности, WebTimeTest определяет часть своей собственной (т. е. отображение текущего времени). Данную функцию реализует файл фонового кода, а файл ASPX определя- ет GUI. При запросе клиентом файла ASPX создается невидимый класс, содержащий как визуальный аспект страницы (определенный в файле ASPX), так и ее логику (определенную в файле фонового кода). Данный но- вый класс также наследует из Раде. При первом запросе страницы этот класс компилируется и создается его экземпляр, представляющий страницу: формируется отправляемый клиенту документ HTML. Компоновочный блок, создаваемый из скомпилированного класса, размещается в каталоге bin проекта. Совет по повышению производительности_____________________________________________________ После создания экземпляра Web-страницы для доступа к ней этим экземпляром могут пользоваться многие кли- енты: перекомпиляции не требуется. Проект перекомпилируется только при модификации приложения програм- мистом; такие изменения выявляются средой выполнения, и проект перекомпилируется для отражения изме- ненного содержимого. Рассмотрим вкратце исполнение кода Web-страницы. После создания сервером экземпляра страницы для об- служивания клиентского запроса сначала имеет место событие init, активизирующее метод Onlnit. Данный метод вызывает метод InitializeComponent. Кроме того, метод Onlnit может содержать код для инициализа- ции объектов. Затем генерируется событие Load, вызывающее метод Page_Load. Данный обработчик событий исполняет любой вид обработки, необходимый для восстановления данных, использованных в предыдущих вы- зовах страницы. В строках 33—36 обработчика события Load свойство Text элемента timeLabel устанавливает- ся на время Web-сервера. Данный код включен в обработчик события Load с тем, чтобы время обновлялось с каждым запросом страницы. По окончании исполнения этого обработчика события страница обрабатывает лю- бые события, вызванные элементами управления страницы. Сюда входит обработка любых событий, сгенериро- ванных пользователем, например, нажатия кнопок. Когда объект Web-формы готов к "сбору мусора", генериру- ется событие Unload. Несмотря на свое отсутствие в данном примере, обработчик события Page Unload насле- дуется от класса Раде. Данный обработчик события содержит код, освобождающий ресурсы, особенно все ресурсы без управления (т. е. ресурсы, не обработанные CLR). В листинге 17.3 представлен документ HTML, сгенерированный приложением ASP.NET. Для просмотра этого документа выберите в Internet Explorer команду View | Source. 1 <%— Листинг 17.3: WebTime.html —%> 2 <%— Сгенерированный документ HTML при загрузке WebTime —%> 3 4 <!DOCTyPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN" > 5 6 <HTML> 7 <HEAD> 8 <title>WebTime</title> 9 <meta name="GENERATOR" г 10 Content="Microsoft Visual Studio 7.0"> 11 <meta name="CODE_LANGUAGE" Content=,,C#"> 12 <meta name="vs_defaultClientScript" content+"JavaScript"> 13 <meta name="vs_targetSchema" 14 content=http://schemas.microsoft.com/intellisense/ie5> 15 </HEAD> 16 17 cbody MS_POSITIONING="GridLayout"> 18 <form name="WebForml" method="post" 19 action="WebTime.aspx" id="WebForml"> 20 cinput type="hidden'’ name="_VIEWSTATE" 21 value="dDwtNjA2MTkwMTQ50zs+" /> 22
ASP.NET, Web-формы и элементы управления Web 607 23 <span id="promptLabel" 24 style="font-size:Medium;z-INDEX: 101 LEFT: 25px; 25 POSITION: absolute; TOP: 23px"> 26 A Simple.Web Form Example 27 </span> 28 29 <span id=”timeLabel" style="color:LimeGreen; 30 background-color:Black;font-size:XX-Large; 31 Z-INDEX: 102; LEFT: 25px; POSITION: absolute; 32 TOP: 55px">10:39:35 33 </span> 34 </form> 35 </body> 36 </HTML> Содержимое данной страницы сходно с содержимым файла ASPX. В строках 7—15 задается заголовок доку- мента, подобный представленному в листинге 17.1. В строках 17—35 определяется тело документа. Со стро- ки 18 начинается форма, являющаяся механизмом для сбора пользовательской информации и ее отправки на Web-сервер. В рассматриваемой программе пользователь не передает данные на сервер для обработки. Формы HTML могут содержать визуальные и невизуальные компоненты. В число визуальных компонентов входят кнопки и прочие элементы интерфейса, с которыми взаимодействует пользователь. Невизуальные ком- поненты, называемые скрытыми входными данными, сохраняют любые данные, указанные автором документа, например, адреса электронной почты, вводимые пользователями Web-страницы. Один из типов таких скрытых входных данных определен в строках 20 и 21. Далее в главе будет рассмотрено точное значение скрытых вход- ных данных. Атрибут method элемента form (строка 18) задает метод, с помощью которого Web-браузер переда- ет форму на сервер (в данном примере — post). Атрибут action в элементе form идентифицирует имя и место- положение ресурса, который будет запрошен при предоставлении данной формы, в данном случае — WebTime.aspx. Помните, что элемент form файла ASPX содержал пару "атрибут = значение" — runat=,,server". Когда form обрабатывается на сервере, в форму HTML, отправляемую в клиентский браузер, добавляются пары "атрибут = значение" — name^’WebForml" и action=”WebTime.aspx". В файле ASPX метки формы являлись элементами управления Web. Здесь рассматривается документ HTML, созданный приложением, поэтому form содержит элементы span для обозначения текста в метках. В данном конкретном случае ASP.NET отображает элементы Web-управления Labels на элементы span HTML. Каждый элемент span содержит информацию о форматировании, например, размер шрифта и размещение отображаемо- го текста. Большая часть информации, обозначенная как свойства timeLabei и promptLabei, указана в атрибуте style каждого элемента span. Теперь, когда подробно рассмотрен файл ASPX и файл фонового кода, опишем вкратце процесс создания дан- ного приложения1. 1. Создание проекта. В основном окне Visual Studio .NET выберите пункт меню File | New | Project... для от- крытия диалогового окна New Project (рис. 17.5). В левой части этого окна выберите строку Visual C# Projects, после чего — в правой части — ASP.NET Web Application. Обратите внимание, что поле Name проекта заблокировано— подсвечено серым цветом. Вместо использования этого поля имя и местопо- ложение проекта задается в поле Location. Необходимо, чтобы проект был размещен по Web-адресу http://localhost, являющимся URL для корневого каталога Internet Information Server (обычно C:\InetPub \wwwroot). Имя localhost указывает на то, что клиент и сервер размещены на одной машине. Если бы сервер располагался на другой машине, тогда имя localhost было бы замещено на соответствующий IP-адрес или имя хоста. По умолчанию Visual Studio присваивает проекту имя webApplicationl, которое мы изменили на webTime. Для успешного создания проекта IIS должен находиться в рабочем состоянии. Запустить IIS можно исполнением inetmgr.exe, щелчком правой кнопки мыши на команде Default Web Site в открывающемся ок- не и выбором команды Start. Примечание___________________________________________________ Для отображения строки Default Web Site пользователям может потребоваться развернуть узел, представляю- щий их компьютеры. 1 Представленные в главе шаги дают читателям возможность создания собственных Web-приложений. При запуске примеров гла- вы (имеются на Web-сайте www.daitel.com) сначала в Microsoft Internet Information Services необходимо создать виртуальный ка- талог. Подробные инструкции представлены в ссылке Downloads/Resources по адресу www.deitel.com. После создания виртуаль- ного каталога и открытия приложения читателям потребуется задать стартовую страницу для Web-приложения. Для этого щелкни- те правой кнопкой мыши на файле ASPX и выберите команду Set as Start Page.
608 Глава 17 Рис. 17.5. Создание ASP.NET Web Application в Visual Studio Рис. 17.6. Создание и связка с помощью Visual Studio виртуального каталога для папки проекта WebTime Под полем Location появится текст "Project will be created at http://localhost/WebTime", что указывает на раз- мещение папки проекта в корневом каталоге Web-сервера. При нажатии разработчиком кнопки ОК создает- ся проект, а также виртуальный каталог, связанный с папкой проекта. Далее открывается диалоговое окно Create New Web, a Visual Studio создает на сервере Web-сайт (рис. 17.6). 2. Изучение вновь созданного проекта. На нескольких рисунках далее представлено содержимое нового проек- та; начнем с Solution Explorer (рис. 17.7). Как и в приложениях Windows, при создании нового ASP.NET Web Application Visual Studio создает несколько файлов. WebForml.aspx — это Web-форма. (WebForml — имя данного 'файла по умолчанию). Частью проекта также является файл фонового кода. Для просмотра файла фонового кода щелкните правой кнопкой мыши на файле ASPX и выберите команду View Code. Либо программист может щелкнуть мышью на пиктограмме отображения всех файлов, после чего развернуть узел для создаваемой страницы ASPX (рис. 17.7). Файл фонового кода I } SoaJHon WebTime (1 project) qqqqqq ф References bin *1 Assembiylnfo.es Global.asax ПВ wet .config & j^l WebForml .aspx ~~ ' ';!] WebForml .aspx.cs | ^3 WebTimevsdsco Отобразить все файлы ASPX-файл ttel Imagj □ Pane. •’taceHwer С'*"*!* RangeVifttafor' , T^RegularExpress^ - F-jr, CUstoffiValdatar TS □ VaJdatonSummary gi 'xmi /" Literal @ OystelteportVi<*yei «Ими ... .,. . n ;ЙЙйЙKStjuJ General, 1 Рис. 17.7. Окно Solution Explorer для проекта WebTime Рис. 17.8. Раздел Web Forms в панели инструментов: a — первая половина списка элементов; б — вторая половина списка элементов Примечание_____________________________________________________________________________ Для просмотра файла фонового кода в Solution Explorer может потребоваться нажатие пиктограммы, отобра- жающей все файлы. На рис. 17.8 показан набор элементов Web-формы (группа Web Forms), перечисленных в панели инстру- ментов (Toolbox). На рис. 17.8, а показано начало списка элементов управления Web, а на рис. 17.8, б — ос- тавшиеся элементы управления. Обратите внимание, что некоторые элементы управления дублируют эле- менты управления Windows, описанные ранее в книге.
ASP.NET, Web-формы и элементы управления Web 609 На рис. 17.9 представлена программа-проектировщик Web-форм для WebForml.aspx. Она состоит из сетки, по которой пользователи могут перетягивать из панели инструментов и по-разному размешать компоненты, например, кнопки и метки. Сетка Кнопка Кнопка Design HTML Рис. 17.9. Режим Design конструктора Web-форм Режим HTML конструктора Web-форм (рис. 17.10) дает программисту возможность просмотра разметки, обозначающей пользовательский интерфейс. При нажатии кнопки HTML в левом нижнем углу конструкто- ра Web-форм режим переключается в HTML. При нажатии же кнопки Design (слева от кнопки HTML) ре- жим переключается на проектирование. Рис. 17.10. Режим HTML конструктора Web-форм На рис. 17.11 представлен файл фонового кода WebForml.aspx.cs для файла WebForml.aspx. Напомним, что Visual Studio генерирует этот файл фонового кода при создании проекта. Файл можно просмотреть щелчком правой кнопки мыши на файле ASPX в окне Solution Explorer и выбором команды View Code., 3. Переименование файла ASPX. Мы показали содержимое, получаемое по умолчанию, файлов ASPX и фоно- вого кода. Теперь переименуем эти файлы. Щелкните правой кнопкой мыши на файле ASPX в окне Solution Explorer и выберите команду Rename. Введите новое имя файла и нажмите клавишу <Enter>. При этом имена файла ASPX и фонового кода обновятся. В данном примере используется имя WebTime.aspx. 4. Проектирование страницы. Проектирование Web-формы настолько же просто, насколько проектирование формы Windows. Для добавления в страницу элементов управления просто перетягивайте и "сбрасывайте" 39 Зак. 3333
610 Глава 17 их из панели инструментов Toolbox на Web-форму. Подобно самой Web-форме, каждый элемент управления является объектом, имеющим свойства, методы и события. Разработчики могут устанавливать эти свойства и события, пользуясь окном свойств Properties. // Put user code to initialize the page here the ffendregion •Ln 29 using using using using using using using using using using InitializeComponent() base.Onlnit(e): flregion Web Form Designer generated code override protected void Onlnit(EventArgs - do not modify rode editor. private void Page_Load(object sender. System.EventArgs e) // CODEGEN: This call is required by // ASP.NET Web Form Designer. this.Load +- nev System.EventHandler(this.Page_Load) iff <summary> /// Sunznary description for WebForml. /// </summary? public class VebForml : System.Web.UI.Page Required method for Designer support the contents of this method with the </surertiary> . private void InitializeComponent() 'WebTme.WebFonnl System; System.Collections; System.ComponentModel; System.Data; System.Drawing; System- I'eb; System.Web.Sess ionState, System.Web.UI; System.Web.UI.WebControls; System.Web.UI.HtmlContrо is, H J^kOnlritffventArgs' V* WebTime - Microsoft Visual C# .NET [design] WebForml.aspx cs St Л Реде Wetf.r,. t-ayr Рис. 17.11. Файл фонового кода для приложения WebForml.aspx, сгенерированного Visual Studio .NET Свойство PageLayout определяет расположение элементов управления на форме (рис. 17.12). По умолчанию это свойство установлено в значение GridLayout, указывающее на то, что все элементы управления распо- ложены именно там, где они "сброшены" на Web-форме. Это называется абсолютным позиционированием. Либо, разработчик может задать свойству PageLayout Web-формы значение FlowLayout, при использовании которого элементы управления будут располагаться на Web-форме последовательно. Такое расположение называется относительным позиционированием, потому что положение каждого элемента управления будет соотноситься с верхним левым углом Web-формы. Во многих примерах главы используется параметр GridLayout. Для просмотра свойств Web-формы выберите в раскрывающемся списке в окне Properties объ- ект Document; Document — это имя, используемое для представления Web-формы в окне Properties. В данном примере используются две метки (Label), которые разработчики могут поместить на Web-форму либо способом drag-and-drop, либо двойным щелчком левой кнопкой мыши на элементе управления Label панели элементов. Присвойте первой метке имя promptLabel, а второй — timeLabel. Текст в timeLabel уда- ляется, потому что он задан в файле фонового кода. Когда Label не содержит текста, тогда ее имя отобража-
ASP.NET, Web-формы и элементы управления Web 611 ется в квадратных скобках в конструкторе Web-формы (рис. 17.13), но во время выполнения данное имя не отображается. Текст для propmtLabel: a Simple Web Form Example. GridLayout - элементы управления появляются в том месте, куда были сброшены FlowLayout- элементы управления располагаются друг за другом Курсор указывает, куда будет помещен следующий элемент а Рис. 17.12. Наглядное представление: а — GridLayout; б — FlowLayout Метки Web-форма ForeColor Нет*» wwtne False ХХЧагде False False Л Simple Web Form CM &* .eroject fal Wrti4meu»spx | /_________ X>Wet>Tin>g Microsoft V<m>U t at JXEI [desrjnJ- Weblime.aspa timeLabel Рис. 17.13. WebFomn aspx после добавления двух меток (Label) и установки их свойств Свойства BackColor, ForeColor и, Font-Size метки timeLabel устанавливаются на Black, LimeGreen и XX- Large соответственно. Для изменения свойств шрифта программист должен развернуть узел Font в окне Properties, после чего изменить каждое свойство отдельно. Перетягиванием элементов управления изменя- ются местоположения меток и их размеры. Наконец, свойства Web-формы EnableSessionstate и EnableViewState устанавливаются на false (эти свой- ства используются для активизации функции контроля сеансов, рассматриваемой в разд. 17.6). После уста- новки свойств меток Label в окне Properties Visual Studio обновляет содержимое файла ASPX. На рис. 17.13 показана интегрированная среда разработки после установки всех свойств. 5. Добавление логики страницы. После проектирования пользовательских интерфейсов в файл фонового кода необходимо добавить код С#. В данном примере строки 33—36 листинга 17.2 добавлены в этот файл. Оператор извлекает текущее время и форматирует его так, что оно выводится в виде hh-.mm-.ss (часы:минуты:секунды). Например, время 9 часов утра изображается как 09:00:00. 6. Запуск программы. Выберите в меню команду Debug | Start. Откроется окно Internet Explorer и загрузится Web-страница (файл ASPX). Обратите внимание на URL — http://localhost/WebTime/WebTime.aspx
612 Глава 17 (см. листинг 17.1), указывающий на то, что файл ASPX находится каталоге WebTime, расположенном, в свою очередь, в корневом каталоге Web-сервера. После создания Web-формы программист может просмотреть ее тремя разными способами. Во-первых, можно выбрать команду Debug | Start; приложение запустится с открытием окна браузера. При закрытии браузера IDE выходит из режима Run или Debug. Программист также может щелкнуть правой кнопкой мыши на конструкторе Web-формы или на имени фай- ла ASPX (в окне Solution Explorer) и выбрать команду View In Browser. При этом в Visual Studio открыва- ется окно браузера и отображается режим предварительного просмотра страницы. Такая методика дает раз- работчику возможность просмотра внешнего вида страницы при ее запросе клиентом, но не компиляции файла фонового кода. Третий способ запуска приложения ASP.NET — открытие окна браузера и ввод URL Web-страницы. При тестировании приложения ASP.NET на локальном компьютере следует ввести http://localhost 1папка_проекта1имя_страницы.азрх, где папка_нроекта — папка, в которой расположена страница (как правило, это — название (имя) проекта), а имя страницы — имя страницы ASP.NET. Обратите внимание, что при использовании данной методики страница уже должна быть скомпилирована. 17.5. Элементы управления Web В данном разделе представлены некоторые из элементов управления, расположенные в разделе Web Forms в панели инструментов Toolbox (см. рис. 17.8). В табл. 17.1 перечислены некоторые из элементов управления Web, описываемые в примерах главы1. Таблица 17.1. Элементы управления Web,, обычно используемые в приложениях ASP NET Элемент управления Web Описание Label Метка. Отображение текста, который пользователь не может отредактировать Button Отображение кнопки TextBox Текстовое поле. Накопление вводимых пользователем данных и отображение текста Image Изображение. Отображение графических изображений (например, в формате GIF и JPG) RadioButtbnList Группа переключателей DropDownList Раскрывающийся список. Отображение набора опций для выбора пользователем 17.5.1. Элементы управления текстом и графикой В форме, представленной в листинге 17 4, собраны вводимые пользователем данные с помощью элементоЕ управления, перечисленных в табл. 17.1. Примечание ___________________________________________________________ Данный пример не содержит функциональности (т е. при выборе пользователем строки Register никакое дейст- вие не совершается). В качестве упражнения пользователю предлагается добавить функциональность. В после- дующих примерах демонстрируется добавление функциональности многим элементам управления Web 1 <%— Листинг 17.4: WebControls.aspx —%> 2 <%— Демонстрация некоторых элементов управления Web —%> 3 4 <%@ Page language="c#" Codebehind="WebControls.aspx.cs" 5 AutoEventWireup=”false" Inherits="WebControls.WebForml" 6 EnableSessionState="False" enableViewState="False"%> 1 ASP.NET также предоставляет элементы управления, известные как элементы управления HTML, представляющие стандартные компоненты HTML Данные типы элементов управления используются в главе из-за довольно сложной функциональности, предос- тавляемой элементами управления Web в ASP NET
ASP.NET, Web-формы и элементы управления Web 613 8 C'.DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN” > 9 10 <HTML> 11 <HEAD> 12 13 <title>WebForml</title> 14 <meta name~"GENERATOR" 15 content="Microsoft Visual Studio 7.0"> 16 <meta name~"CODE_LANGUAGE" Content="C#"> 17 <meta name="vs_defaultClientScript" 18 content="JavaScript"> 19 <meta name="vs_targetSchema" 20 4 content="http://schemas.microsoft.com/intellisense/ie5"> 21 22 </HEAD> 23 24 <body MS_POSITIONING="GridLayout"> 25 26 <form id="Forml" method="post runat="server"> 27 28 <asp:Label id="welcomeLabel" style="Z-INDEX: 101; 29 LEFT: 21px; POSITION: absolute; TOP: 17px" 30 runat="server" Font-Bold="True" Font-Size”"Medium"> 31 This is a sample registration form. 32 </asp:Label> 33 34 <asp:Image id="operatinglmage" style="Z-INDEX: 121; 35 LEFT: 21px; POSITION: absolute; TOP: 371px" 36 runat="server" ImageUrl="images\os.png"> 37 </asp:Image> 38 39 <asp:Image id="publicationlmage" style="Z-INDEX: 120; 40 LEFT: 21px; POSITION: absolute; TOP: 245px" 41 runat="server" ImageUrl="images\downloads.png"> 42 </asp:Image> 43 44 <asp:Image id="userlmage" style-"Z-INDEX: 119; 45 LEFT: 21px; POSITION: absolute; TOP: 91px" 46 runat-c"server" ImageUrl="images\user.png"> 47 </asp:Image> 48 49 <asp:TextBox id="emailTextBox" style="Z-INDEX: 118; 50 LEFT: 95px; POSITION: absolute; 51 TOP: 161px" runat="server"> 52 </asp:TextBox> 53 54 <asp:TextBox id="firstTextBox" style="Z-INDEX: 117; 55 LEFT: 95px; POSITION: absolute; TOP: 127px" 56 runat="server"> 57 </asp:TextBox> 58 59 <asp:TextBox id="lastTextBox” style="Z-INDEX: 116; 60 LEFT: 341px; POSITION: absolute; 61 TOP: 127px" runat="server"> 62 </asp:TextBox> 63 64 <asp:TextBox id="phoneTextBox" style="Z-INDEX: 115; 65 LEFT: 341px; POSITION: absolute; 66 TOP: 161px" runat="server"> 67 </asp:TextBox> 68 69 <asp:RadioButtonList id="operationRadioButtonList" 70 style="Z-INDEX: 114; LEFT: 21px; 71 POSITION: absolute; TOP;/ 409px" runat="server"> 72
614 Глава 17 73 <asp:ListItem Value="Windows NT">Windows NT 74 </asp:ListItem> 75 76 <asp:Listitem Value="Windows 2000">Windows 2000 77 </asp:ListItem> 78 79 <asp:ListItem Value="Windows XP">Wxndows XP 80 </asp:ListItem> 81 82 <asp:ListItem Value="Linux">Linux</asp:ListItem> 83 84 <asp:ListItem Value="Other">Other</asp:ListItem> 85 86 </asp:RadioButtonList> 87 88 <asp:HyperLink id="booksHyperLink" style="Z-INDEX: 113; 89 LEFT: 21px; POSITION: absolute; TOP: 316px" 90 runat=’,server" NavigateUrl="http://www.deitel.com"> 91 Click here to view more information about our books. 92 </asp:HyperLink> 93 94 <asp:DropDownLiSt id="booksDropDownList" 95 style®"Z-INDEX: 112; LEFT: 21px; 96 POSITION: absolute; TOP: 282px" runat="server"> 97 98 <asp:Listltem Value="XML How to Program le"> 99 XML How to Program le 100 </asp:ListItem> 101 102 <asp:Listitem Value="C# How to Program le"> 103 C# How to Program le 104 </asp:ListItem> 105 106 <asp:ListItem Value="Visual Basic.NET How to Program 2e"> 107 Visual Basic.NET How to Program 2e 108 </asp:ListItem> 109 110 <asp:ListItem Value="C++ How to Program 3e"> 111 C++ How to Program 3e 112 </asp:ListItem> 113 114 </asp:DropDownLiSt> 115 116 <asp:Image id="phonelmage" style="Z-INDEX: 111; 117 LEFT: 266px; POSITION: absolute; TOP: 161px" 118 runat="server" ImageUrl="images\phone.png"> 119 </asp:Image> 120 121 <asp:Image id="emaillmage" style="Z-INDEX: 110; 122 LEFT: 21px; POSITION: absolute; TOP: 161px" 123 runat="server” ImageUrl="images\email.png"> 124 </asp:Image> 125 126 <asp:Image id="last Image" style=”Z-INDEX: 109; 127 LEFT: 266px; POSITION: absolute; TOP: 127px" 128 runat="server" ImageUrl»"images\lname.png”> 129 </asp:Image> 130 131 <asp:Image id="firstlmage" style®"Z-INDEX: 108; 132 LEFT: 21px; POSITION: absolute; 133 TOP: 127px" runat="server" 134 ImageUrl="images\fname.png"> 135 </asp:Image> 136
ASP.NET, Web-формы и элементы управления Web 615 137 <asp:Button id="registerButton" style="Z-INDEX: 107; 138 LEFT: 21px; POSITION: absolute; TOP: 547px" 139 runat="server" Text="Register"> 140 </asp:Button> 141 142 <asp:Label id="bookLabel" style="Z-INDEX: 106; 143 LEFT: 216px; POSITION: absolute; TOP: 245px" 144 runat="server" ForeColor="DarkCyan"> 145 Which book would you like information about? 146 </asp:Label> 147 148 <asp:Label id="fillLabel" style="Z-INDEX: 105; 149 LEFT: 218px; POSITION: absolute; TOP: 91px" 150 runat="server" ForeColor="DarkCyan”> 151 Please fill out the fields below. 152 </asp:Label> 153 154 <asp:Label id="phoneLabel'’ style="Z-INDEX: 104; 155 LEFT: 266px; POSITION: absolute; 156 TOP: 198px” runat="server"> 157 Must be in the form (555) 555-5555. 158 </asp:Label> 159 160 <asp:Label id="operatingLabel" style="Z-INDEX: 103; 161 LEFT: 220px; POSITION: absolute; TOP: 371px" 162 runat="server" Height="9px" ForeColor="DarkCyan"> 163 Which operating system are you using? 164 </asp:Label> 165 166 <asp:Label id="registerLabel" style="Z-INDEX: 102; 167 LEFT: 21px; POSITION: absolute; TOP: 46px" 168 runat="server" Font-Italic="True"> 169 Please fill in all fields and click Register. 170 </asp:Label> 171 172 </form> 173 </body> 174 </HTML> В строках ЗА—37 определяется элемент управления image, размещающий изображение на Web-странице. Свой- ство imageUrl (строка 36) задает местоположение файла изображения. Для указания изображения щелкните ле- вой кнопкой мыши на эллипсовидной кнопке рядом со свойством ImageUrl в окне Properties; открывающееся диалоговое окно позволяет программисту найти нужное изображение. В верхней части данного диалогового окна отображается содержимое приложения. Если изображение явно не является частью проекта, тогда про- граммисту потребуется воспользоваться кнопкой Browse либо добавить изображение в проект. Если изображе- ние находится в каталоге проекта (или в каких-либо его подкаталогах), тогда изображение можно явно добавить в проект путем отображения всех файлов в окне Solution Explorer щелчком правой кнопки мыши на изображе- нии и выбором команды Include in Project. После этой операции изображение будет добавлено в список файлов проекта в диалоговом окне, открывающемся после выбора программистом свойства ImageUrl. В этом диалого- вом окне пользователю предоставляется возможность выбора URL TYPE, определяющего задание местополо- жения графического объекта в программе. Для URL TYPE разработчики имеют много опций: можно задать ссылку на изображение как размещенное на сервере, на локальной машине, либо относительно текущего пути. В данном случае местоположение задается как относительный путь. В строках 49—52 определяется элемент управления TextBox, позволяющий программисту загружать и отобра- жать текст. В строках 69—86 определяется элемент управления RadioButtonList, предоставляющий серию пе- реключателей, где пользователь может выбрать только один вариант. Каждый переключатель определен с по- мощью элемента Listitem (строки 73—84). Элемент управления HyperLink (строки 88—92) добавляет в Web- страницу гиперссылку. Свойство NavigateUrl (строка 90) данного элемента управления указывает запрашивае- мый ресурс (например, http://www.deitel.com) при щелчке пользователем кнопкой мыши на гиперссылке. В строках 94—114 определяется элемент управления DropDownList. Данный элемент управления похож на эле- мент управления RadioButtonList в том, что он позволяет выбрать только одну опцию. При щелчке пользовате- лем кнопкой мыши на раскрывающемся списке последний разворачивается и отображает список, в котором
616 Глава 17 пользователь может сделать свой выбор. В строках 98—112 определяется элемент Listitems, который отобра- жается при развернутом раскрывающемся списке. Подобно элементу управления Button Windows, элемент управления Web Button (строки 137—140) представляет кнопку и отображается на элемент HTML input, имеющий атрибут type и значение "button”. Результат выполнения кода из листинга 17.4 представлен на рис. 17.14. Элемент управления Image Элемент управления RadioButtonList Элемент управления TextBox Элемент управления Button Элемент управления DropDownList Элемент управления HyperLink Рис. 17.14. Демонстрация элементов управления Web 17.5.2. Элемент управления AdRotator Web-страницы часто содержат рекламу товаров и услуг, в которую, как правило, включены графические изо- бражения. Несмотря на то, что создатели Web-сайтов стремятся продать рекламу как можно большему количе- ству спонсоров, на Web-страницах можно поместить ограниченное число рекламных материалов. Для решения этой проблемы ASP.NET предлагает элемент управления Web AdRotator, позволяющий отображать рекламные материалы. Используя рекламную информацию файла XML, элемент управления AdRotator произвольно выби- рает графическое изображение и создает гиперссылку на Web-страницу, относящуюся к этому изображению. В браузерах, не поддерживающих графику, отображается текст, указанный в документе XML. В листинге 17.5 показан элемент управления AdRotator. В число изображений, поворачиваемых в данном при- мере, входят флаги одиннадцати государств. При щелчке пользователем на изображении флага браузер перена- правляется на Web-страницу, содержащую информацию о государстве, представляемом флагом. При нажатии пользователем кнопки обновления или при повторном запросе страницы произвольно выбирается и отобража- ется один из одиннадцати флагов. 1 <%— Листинг 17.5: AdRotator.aspx —%> 2 <%— Web-форма, демонстрирующая класс AdRotator —%> 3 4 <%@ Page language="c#" Codebehind=”AdRotator.aspx.cs" 5 AutoEventWireup="false" Inherits="AdRotatorTest.AdRotator" 6 EnableSessionState="False" enableViewState="False"%>
ASP.NET, Web-формы и элементы управления Web 617 CDOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN" > 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 <HTML> z <HEAD> <title>WebForml</title> <meta name=,,GENERATOR" content="Microsoft Visual Studio 7.0"> <meta name="CODE_LANGUAGE" Content="C#"> <meta name="vs_defaultClientScript" cont ent=" JavaScript’’> <meta name="vs_targetSchema" content="http://schemas.microsoft.com/intellisense/ie5"> </HEAD> <body MS_POSITIONING="GridLayout"> background^' images/background.png”> <form id="Forml" method="post runat="server"> <asp:AdRotator id=,’adRotator’' style="Z-INDEX: 101; LEFT: 17px; POSITION: absolute; TOP: 69px” runat="server" Width="86px" Height="60px" AdvertisementFile=,,AdRotatorInformation.xml"> </asp:AdRotator> <asp:Label id="adRotatorLabel" style="Z-INDEX: 102; LEFT: 17px; POSITION: absolute; TOP: 26px" runat=" server " Forn-S i-ze=" Large " > AdRotator Example </asp:Label>&nbsp; </form> </body> </HTML> Файл ASPX, показанный в листинге 17.5, похож на файл ASPX листинга 17.4. Однако вместо двух меток данная страница содержит один элемент управления Label и один элемент управления AdRotator с именем adRotator. Свойство Background страницы установлено на визуализацию изображения backgrounding. Для задания файла щелкните кнопкой мыши на кнопке рядом со свойством Background и воспользуйтесь открывающимся диалого- вым окном для поиска изображения с именем backgrounding. В окне Properties свойство AdvertisementFile класса AdRotator устанавливается на AdRotatorlnformation.xml (строка 28). Элемент управления Web определяет рекламный материал файла, который будет отображен. Со- держимое данного файла XML представлено в листинге 17.7. Как показано в листинге 17.6, программисту нет необходимости добавлять код в файл фонового кода, потому что эту работу за него выполняет элемент управле- ния AdRotator. На рис. 17.15 представлены два разных запроса: при первом запросе страницы появляется изображение флага США (рис. 17.15, а), а при втором запросе отображается флаг Латвии (рис. 17.15,6). На рис. 17.16 показана Web-страница, загружаемая после щелчка пользователя на изображении флага Латвии. 1 // Листинг 17.6: AdRotator.aspx.cs 2 // Файл фонового кода для страницы, 3 // демонстрирующей класс AdRotator 4 5 using System; 6 using System.Collectipns; 7 using System.ComponentModel; 8 using System.Data; 9 using System.Drawing; 10 using System.Web; 11 using System.Web.Sessionstate; 12 using System.Web.UI
618 Глава 17 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 using System.Web.UI.WebControls; using System.Web.UI.HtmlControls; namespace AdRotatorTest { /// страница, демонстрирующая класс AdRotator public class AdRotator : Sustem.Web.UI.Page { protected System.Web.UI.WebControls.AdRotator adRotator; protected System.Web.UI.WebControls.Label adRotatorLabel; // код, сгенерированный Visual Studio .NET } // конец класса AdRotator } // конец пространства имен AdRotatorTest Рис. 17.15. Вид страницы, загруженной в браузер: а — отображается флаг США; б — отображается флаг Латвии 3Webforml МгсгомЛ hit» Add ss 'Я http ilocalteij» ' Jnki *| — =—д AdRotator Example Рис. 17.16. Web-страница, загружаемая после щелчка мышью на изображении флага Латвии Документ XML AdRotatorlnformation.xml (листинг 17.7) содержит несколько элементов Ad, каждый из которых предоставляет информацию о рекламном объявлении для AdRotator. Элемент ImageUrl указывает относитель- ный путь изображения рекламного объявления, а элемент NavigateUrl задает URL для Web-страницы, загру-
ASP.NET, Web-формы и элементы управления Web 619 жаемой при щелчке кнопкой мыши на рекламном объявлении. Элемент AlternateText содержит текст, выводи- мый вместо графического изображения, если браузер не может определить местоположение или визуализиро- вать его (либо из-за отсутствия файла, либо из-за невозможности отображения браузером). Текст элемента AlternateText также является всплывающей подсказкой, отображаемой браузером, когда пользователь помеща- ет курсор мыши на изображение (см. листинг 17.6). Элемент impressions указывает частоту появления того или иного изображения относительно других изображений. Рекламный материал, имеющий более высокое значение impressions, появляется чаще, нежели реклама с более низким значением impressions. В данном примере рек- ламные объявления появляются с одинаковой вероятностью, потому что значение impressions каждого уста- новлено на 1. Рекламодатели часто приобретают рекламное место на сайтах, исходя из значения impressions, присвоенного тому или иному рекламному объявлению. r-w.............---------------чжггг-жчжвиг. • ----——ж------г—7Х""£ ( Листинг |7'.7. Класс sementFile, использованный в примере Aaxotator 1 <?xml version="1.0” encoding="utf-8"?> 2 3 <!— Листинг 17.7: AdRotator Informat ion. xml —> 4 <!— Файл XML, содержащий информацию о рекламном объявлении —> 5 6 Advertisement s> 7 . Ad> 8 <ImageUrl>images/us.png</ImageUrl> 9 <NavigateUrl> 10 http://www.odci.gov/cia/publications/factbook/geos/us.html 11 <NavigateUrl> 12 <AlternateText>United States Information</AlternateText> 13 <Impressions>l</Impressions> 14 </Ad> 15 16 <Ag> 17 <ImageUrl>images/france.png</ImageUrl> 18 <NavigateUrl> 19 http://www.odci.gov/cia/publications/factbook/geos/fr.html 20 <NavigateUrl> 21 AlternateText>France Information</AlternateText> 22 <Impressions>l</Impressions> 23 </Ad> 24 25 <Ad> 26 <ImageUrl>images/germany.ppg</ImageUrl> 27 <NavigateUrl> 28 http://www.odci.gov/cia/publications/factbdok/geos/gm.html 29 <NavigateUrl> 30 <AlternateText>Germany Information</AlternateText> 31 <lmpressions>l</Impressions> 32 </Ad> 33 34 Ad> 35 <ImageUrl>images/Italy.png</ImageUrl> 36 <NavigateUrl> 37 http://www.odci.gov/cia/publications/factbook/geos/it.html 38 <NavigateUrl> 39 <AlternateText>Italy Infopnation</AlternateText> 40 <Impressions>l</Impressions> 41 </Ad> 42 43 <Ad> 44 <ImageUrl>images/spain.png</ImageUrl> 45 <NavigateUrl> 4 6 http://www.odci.gov/cia/publications/factbook/geos/sp.html 47 <NavigateUrl> 48 <AlternateText>Spain Information</AlternateText> 49 <Impressions>l</Impressions> 50 </Ad> 51
620 Глава 17 52 53 54 55 56 57 58 59 60 <Ad> <ImageUrl>images/latvia.png</ImageUrl> <NavigateUrl> http://www.odci.gov/cia/publications/factbook/geos/lg.html <NavigateUrl> <AlternateText>Latvia Information</AlternateText> <Impressions>l</Impressions> </Ad> 61 , 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 В9 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 <Ad> <ImageUrl>images/peru.png</ImageUrl> <NavigateUrl> http://www.odci.gov/cia/publications/factbook/geos/pe.html <NavigateUrl> <AlternateText>Peru Information</AlternateText> <Impressions>l</Irrpressions> </Ad> <Ad> <ImageUrl>images/senegal.png</ImageUrl> <NavigateUrl> http://www.odci.gov/cia/publications/factbook/geos/sg.html <NavigateUrl> <AlternateText>Senegal lnformation</AlternateText> <Impressions>l</Impressions> ✓ </Ad> <Ad> <ImageUrl>images/sweden.png</ImageUrl> <NavigateUrl> http://www.odci.gov/cia/publicat ions/factbook/geos/sw.html <NavigateUrl> <AlternateText>Sweden Information</AlternateText> <Impressions>l</Impressions> </Ad> <Ad> <ImageUrl>images/thailand.png</ImageUrl> <NavigateUrl> http://www.odci.gov/cia/publications/factbook/geos/th.html <NavigateUrl> <AlternateText>Thailand Information</AlternateText> <Impressions>l</Impressions> </Ad> <Ad> <ImageUrl>images/unitedstates.png</lmageUrl> <NavigateUrl> http://www.odci.gov/cia/publications/factbook/geos/us.html <NavigateUrl> <AltgrnateText>United States Information</AlternateText> <Impressions>l</Impressions> </Ad> 105 </Advertisements> 17.5.3. Контролирующие элементы управления В данном разделе представлены контролирующие элементы управления (validation controls) ASP.NET или реви- зоры (validators), определяющие правильность формата данных в других элементах управления Web. Например, с помощью ревизоров можно определить, ввел ли пользователь информацию в нужное поле, либо факт наличия именно пяти цифр в почтовом индексе. Ревизоры обеспечивают механизм проверки достоверности вводимых пользователем данных на стороне клиента. При создании HTML для страницы ревизор преобразуется в сцена-
ASP.NET, Web-формы и элементы управления Web 621 puu ЕСМА\ выполняющий проверку достоверности. ECMAScript— это язык сценариев, повышающий функ- циональность и усовершенствующий внешний вид Web-страниц. ECMAScript обычно исполняется на стороне клиента. Однако, если клиент не поддерживает механизмы написания сценариев, либо отключена проверка дос- товерности, сценарий выполняется на сервере. В примере данного раздела пользователю предоставляется подсказка о вводе номера телефона в форме 555-4567 (т. е. три цифры, за которыми следует дефис, и еще четыре цифры) — рис. 17.17, а. После ввода поль- зователем номера ревизоры подтверждают, что поле номера телефона заполнено в корректном формате до того, как программа отправит номер на сервер для дополнительной обработки. После ввода телефонного номера Web-сервер отвечает страницей HTML, содержащей все возможные комбинации букв, обозначающие номер телефона (рис. 17.18). Буквы, используемые для каждой цифры, представлены на наборной панели каждого те- лефона. Например, на кнопке с цифрой 5 имеются буквы j, к и 1. Позицию в телефонном номере, где появляется цифра 5, можно заменить на любую из этих трех букв. Такая методика часто используется на предприятиях для упрощения запоминания телефонных номеров. В листинге 17.8 представлен файл ASPX. j—-у».--— тгясг--•?.—s-—«г«тч“.г— ^Листинг 17.8. Ревизоры, которые используются в Web-форме, генерирующей возможные комбинации букв fcnqтелефонномУ^омеру ' ~.....| 1 <%— Листинг 17.8: Generator.aspx —%> 2 <%— Web-форма, демонстрирующая использование ревизоров —%> 3 4 <%@ Page language="c#" Codebehind="Generator.aspx.cs" 5 AutoEventWireup="false" Inherits="WordGenerator.Generator" %> 6 7 <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN" > 8 9 <HTML> 10 <HEAD> 11 <title>WebForml</title> 12 <meta name="GENERATOR" 13 content="Microsoft Visual Studio 7.0"> 14 <meta name="CODE_LANGUAGE" Content="C#"> 15 <meta name="vs_defaultClientScript” content="JavaScript"> 16 <meta name="vs_targetSchema" content= 17 "http: //schemas.microsoft.com/intellisense/ie5'1> 1.8 </HEAD> 19 20 <body MS_POSITIONING="GridLayout"> 21 <form id="Forml" method="post runat="server"> 22 <asp:Label id="promptLabel'\ style="Z-INDEX: 101; 23 LEFT: 16px; POSITION: absolute; TOP: 23px" 24 runat="server"> 25 Please enter a phone number in the form 555-4567: 26 </asp:Label> 27 28 <asp:RegularExpressionValidator 29 id="phoneNumberValidator" style="Z-INDEX: 106; 30 LEFT: 217px; POSITION: absolute; TOP: 73px" 31 runat="server" ErrorMessage= 32 "The phone number must be in the form 555-4567." 33 ControlToValidate="inputTextBox" 34 Vai idat ionExpress ion=" л\d{3}-\d{4}$"> 35 </asp:RegularExpressionValidator> 36 37 <asp:RequiredFieldValidator 38 id="phoneInputValidator" style="Z-INDEX: 105; 39 LEFT: 217px; POSITION: absolute; TOP: 47px" 1 ECMAScript (обычно называется JavaScript) — стандарт сценариев, разработанный Европейской ассоциацией производителей компьютеров (European Computer Manufacturer's Association, ECMA). Стандарту ECMAScript соответствуют стандарты JavaScript от Netscape и Jscript от Microsoft, но каждый из них предоставляет дополнительные функции, выходящие за рамки спецификации. Информацию о текущем стандарте ECMAScript см. на сайте www.ecma.ch/ecmal/stand/ecma-262.htm.
622 Глава 17 40 runat="server" ErrorMessage= 41 ’’Please enter a phone number." 42 ControlToValidate=" inputTextBox’’> 43 </asp:RequiredFieldValidator> 44 45 <asp:TextBox id="outputTextBox” style="Z-INDEX: 104; 46 LEFT: 16px; POSITION: absolute; TOP: 146px” 47 runat=’’server" Visible=’’False’’ TextMode="MultiLine" 48 Height=’’198px" Width="227px" Font-Bold=’’True" 49 Font-Names="Courier New"> 50 </asp:TextBox> 51 52 <asp:Button ±d="submitButton" style="Z-INDEX: 103; 53 LEFT: 16px; POSITION: absolute; TOP: 86px" 54 runat="server" text=”Submit"> 55 </asp:Button> 56 57 <asp:TextBox id=’’inputtextBox" style="Z-INDEX: 102; 58 LEFT: 16px; POSITION: absolute; TOP: 52px" 59 runat="server"> 60 </asp:TextBox> 61 </form> 62 </body> 63 </HTML> Отправленная в браузер клиента страница HTML получает номер телефона в формате 555-4567, после чего при- водит список всех допустимых комбинаций букв, которые можно сгенерировать из первых трех цифр и из ос- тавшихся четырех. В данном примере для сопоставления содержимого другого элемента Web-управления отно- сительно регулярного выражения используется класс RegularExpressionValidator. (Применение регулярных выражений описано в главе 12.) В строках 28—35 создается класс RegularExpressionValidator с именем phoneNumberValidator. Текст свойства ErrorMessage (строки 31 и 32) отображается в Web-форме при сбое про- верки допустимости номера. Свойство vaiidationExpression задает регулярное выражение, с помощью которо- го будет проверяться корректность вводимых пользователем данных (строка 34). Вводимые данные допустимы, если они совпадают с регулярным выражением A\d{3}—\d{4 }$ (т. е. с началом строки, за которым следуют 3 цифры, дефис, 4 дополнительные цифры и конец строки). При щелчке мышью на свойстве VaiidationExpression в окне Properties открывается диалоговое окно, содер- жащее список регулярных выражений для номеров телефонов, почтовых индексов и прочих общих данных. Од- нако в данном примере авторы написали собственное регулярное выражение, потому что номер телефона не должен содержать междугороднего кода. В строке 33 inputTextBox соотносится с phoneNumberValidator путем установки свойства ControlToValidate на inputTextBox. Это указывает на то, что phoneNumberValidator под- тверждает правильность содержимого объекта inputTextBox. Если пользователь вводит информацию в некор- ректном формате и делает попытку отправки формы, тогда текст ErrorMessage выделяется красным цветом (рис. 17.17, б). В данном примере также используется класс RequiredFieldValidator, обеспечивающий ввод данных в за- данный элемент управления. В строках 37—43 определяется свойство phoneinputvalidator класса RequiredFieldValidator, подтверждающее, что inputTextBox имеет содержимое. Если пользователь не вводит данные в inputTextBox и пытается отправить форму, то происходит сбой проверки правильности, и текст ErrorMessage для данного ревизора выделяется красным цветом. При успешной проверке корректности (стро- ки 45—50) outputTextBox отображает текст, сгенерированный по цифрам номера телефона (рис. 17 18). В листинге 17.9 представлен файл фонового кода для файла ASPX из листинга 17.8. Обратите внимание, что данный файл не содержит реализации, относящейся к ревизорам. Об этом будет сказано дополнительно. 1 // Листинг 17.9: Generator.aspx.cs 2 // Файл фонового кода для страницы, 3 // генерирующей слова из номера телефона 4 5 using System; 6 using System.Collections; 7 using System.ComponentModel;
ASP.NET, Web-формы и элементы управления Web 623 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 using System.Data; using System.Drawing; using System.Web; using System.Web.SessionState; using System.Web.UI; using System.Web.UI.WebControls; using System.Web.UI.HtmlControls; namespace WordGenerator { // страница, рассчитывающая все комбинации букв для первых // трех цифр и последних четырех цифр телефонного номера public class Generator : System.Web.UI.Page { protected System.Web.UI.WebControls.TextBox outputTextBox; protected System.Web.UI.WebControls.TextBox inputTextBox; protected System.Web.UI.WebControls.RegularExpressionValidator phoneNumberValidator; protected System.Web.UI.WebControls.RequiredFieldValidator phoneinputvalidator; protected System.Web.UI.WebControls.Button.submitButton; protected System.Web.UI.WebControls.Label.promptLabel; private void Page_Load( object sender, System.EventArgs e) { // если страница загружена вследствие обратной регистрации if (IsPostBack) { outputTextBox.Text = ""; ft извлечение номера и удаление string number = Request.Form[ "inputTextBox” ]; number = number.Remove(3, 1); // генерирование слов для первых трех цифр outputTextBox.Text += "Here are the words for\n"; outputTextBox.Text += "the first three digits:\n\n"; ComputeWords(number.Superstring(0, 3), ""); outputTextBox.Text += "\n"; // генерирование слов для остальных четырех цифр outputTextBox.Text += "Here are the words for\n"M outputTextBox.Text += "the last four digits:\n\n"; ComputeWords(number.Superstring(0, 3), outputTextBox.Visible = true; } // конец if } // конец метода Page_Load // код, сгенерированный Visual Studio .NET private void ComputerWords( string number, string temporaryword)
624 Глава 17 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 { if (number == "") { outputTextBox.Text += temporaryword + "\n"; return; int current = Int32.Parse(number.Substring(0, 1)); number = number.Remove(0, 1); switch (current) { // 0 может быть буквой q или z case 0: Computerwords(number, temporaryword + "q"); Computerwords(number, temporaryword + "z"); break; // цифра 1 не имеет связанных с ней букв case 1: ComputerWord(number, temporaryword + ""); break; // цифра 2 может быть буквой а, b или с . case 2: Computerwords(number, temporaryword + "a"); ComputerWords(number, temporaryword + "b"); ComputerWords(number, temporaryWord + "c"); break; // цифра 3 может быть буквой d, е или f case 3: ComputerWords(number, temporaryword + "d"); ComputerWords(number, temporaryWord + "e"); ComputerWords(number, temporaryword + "f"); break; // цифра 4 может быть буквой g, h или i case 4: ComputerWords(number, temporaryWord + "g"); ComputerWords(number, temporaryWord + "h"); ComputerWords(number, temporaryword + "i"); break; // цифра 5 может быть буквой j, к или 1 case 5: ComputerWords(number, temporaryWord + "j"); ComputerWords(number, temporaryWord + "k"); ComputerWords (number, temporaryWord + ”1”); break; // цифра б может быть буквой m/n или о case 6: ComputerWords(number, temporaryWord + "m”); ComputerWords(number, temporaryWord + "n"); ComputerWords(number, temporaryWord + "o"); break; // цифра 7 может быть буквой р, г или s case 7: ComputerWords(number, temporaryWord + "p"); ComputerWords(number, temporaryWord + "r");
ASP.NET, Web-формы и элементы управления Web 625 136 ComputerWords(number, temporaryWord'+ "s"); 137 break; 138 139 // цифра 8 может быть буквой t, и или v 140 case 8: 141 ComputerWords(number, temporaryWord + "t"); 142 ComputerWords(number, temporaryWord + "u"); 143 ComputerWords(number, temporaryWord + "v"); 144 break; 145 146 // цифра 9 может быть буквой w, х или у 147 case 9: 148 ComputerWords(number, temporaryWord + "w"); 149 ComputerWords (number, temporaryWord + "x"); 150 ComputerWords(number, temporaryWord + "y"); 151 break; 152 153 } // конец switch 154 155 } // конец метода Computerword 156 157 } // конец класса Generator 158 159 } // конец пространства имен WordGenerator б Рис. 17.18. Текст, сгенерированный по цифрам номера телефона: а — по первым трем цифрам; б — по последним четырем цифрам Рис. 17.17. Файл ASPX, загруженный в браузер: а — пустая форма; б— заполненная форма с сообщением об ошибке а б 40 Зак. 3333
626 Глава 17 Разработчики ASP.NET часто проектируют Web-страницы так, что текущая страница перезагружается, когда пользователь предоставляет форму для обработки данных ввода. Данная методика, называемая обратной реги- страцией, объединяет данные ввода с логической последовательностью обработки, что упрощает поддержку файла ASPX. В данном примере на странице имеется текстовое окно, в которое пользователи могут вводить номера телефонов (см. рис. 17.17). После ввода этой информации данная страница перегружается с отображе- нием результатов под исходным текстовым полем. Одним из преимуществ является то, что пользователь может продолжать вводить номера телефонов без необходимости возвращения на предыдущую страницу. В строке 41 используется свойство IsPostBack класса Раде для определения того, загружается ли страница вследствие об- ратной регистрации. При первом запросе Web-страницы свойство IsPostBack установлено на false. Когда име- ет место обратная регистрация (из-за нажатия пользователем кнопки Submit), свойство IsPostBack принимает значение true. Для подготовки текстового поля вывода (outputTextBox) к отображению свойство Text сбрасы- вается в пустую строку (’"’) в строке 43. После этого в строке 46 используется объект Request для извлечения значения phoneTextBox из массива Form. При регистрации данных на Web-сервере данные формы HTML стано- вятся доступными для Web-приложения через массив Form объекта Request. В строке 47 из строки цифр номера телефона удаляется дефис. В метод Computewords передается подстрока, содержащая первые три цифры, и пус- тая строка (строка 53). В строке 62 свойство Visible объекта outputTextBox устанавливается на true. Метод ComputeWords, определенный в строках 70—155, является рекурсивным методом, генерирующим список слов из строки, содержащей цифры номера телефона без дефиса. Первый аргумент — number — содержит циф- ры, преобразуемые в буквы. Первое обращение к методу Computeword (строка 53) передает первые три цифры, а второе обращение (строка 60) передает последние четыре цифры. Второй аргумент — temporaryword — создает список, отображаемый программой. Каждый раз при обращении к этому методу аргумент number содержит на один символ меньше, нежели при предыдущем обращении, тогда как temporaryword содержит на один символ больше. В строках 73—77 определяется базовый случай рекурсии, имеющий место, когда аргумент number ра- вен пустой строке. Когда он происходит, аргумент temporaryword, составленный по предыдущим вызовам, до- бавляется в outputTextBox, и метод передает управление обратно. Рассмотрим процесс работы ComputeWords, когда базовый случай не оценивается как true. В строке 79 объявля- ется переменная -current, и ее значение инициализируется первым символом из аргумента number. Затем этот символ удаляется из аргумента number. В оставшейся части метода используется оператор switch (строки 84— 153) для выполнения корректных рекурсивных обращений на основе числа в current. Для каждой цифры в ар- гумент temporaryword необходимо добавить соответствующую букву. Для большинства цифр существуют две или три буквы, которые может представлять цифра в current. Например, кнопка с цифрой 3 на наборной панели телефона представлена буквами d, е и f. В данном примере необходимо исчерпать все возможные комбинации букв, поэтому для каждого варианта выполняется рекурсивное обращение к ComputeWords (строки 105—109). Каждое обращение передает number в качестве первого аргумента (содержащего на одну цифру меньше в ре- зультаты вызова метода Remove в строке 82). Второй аргумент содержит temporaryword, связанный с новой бук- вой. При каждом обращении добавляется буква для текущей цифры до тех пор, пока не будут обработаны все цифры. На данном этапе достигнут базовый случай, и temporaryword добавляется к outputTextBox. В листинге 17.10 представлена страница HTML, отправленная в браузер клиента. Обратите внимание, что стро- ки 25—28 и 72—113 содержат сценарий ECMAScript, обеспечивающий реализацию контролирующих элемен- тов управления. Данный ECMAScript генерируется ASP.NET. Программисту не нужно создавать или даже по- нимать ECMAScript: функциональность, определенная для элементов управления в данном приложении, преоб- разуется для создания сценария ЕСМА. 1 <%— Листинг 17.10: Generator.html —> 2 <%— Страница HTML, отправленная в браузер клиента —> 3 - 4 ClDOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN" > 5 <HTML> 6 <HEAD> 7 <title>WebForml</title> 8 <meta name="GENERATOR" 9 content="Microsoft Visual Studio 7.0"> 10 <meta name="CODE LANGUAGE" Content="C#"> 11 <meta name="vs_defaultClientScript" 12 content="JavaScript"> 13 <meta name="vs_targetSchema" 14 content="http://schemas.microsoft.com/intellisense/ie5”> 15 </HEAD> 16 ’
ASP.NET, Web-формы и элементы управления Web 627 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 $1 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 <body MS_POSITIONING="GridLayout"> <form name="Forml” method=”post” action="Generator.aspx" language="javascript" onsubmit="Vai idat orOnSubmit();" id="FORMl"> <input type="hidden" name="___VIEWSTATE" value="dDwxMjgyMzM3ozs+" /> <script language="javascript" src= ". aspnet-dient / system_web/1_0_3215_11 / WebUIVal idat ion. j s " > </script> <span id="phoneNumberValidator" control validate=,’inputTextBox" errormessage= "The phone number must be in the form 555-4567." evaluationfunction= "RegularExpressionValidatorEvaluatelsValid" validationexpression="A\d{3}-\d{4}$" style="color:Red;Z-INDEX:106;LEFT:217px; POSITION:absolute;TOP:73px;visibility:hidden;"> The phone number must be in the form 555-4567. </span> <input name="inputTextBox" type="text" id="inputTextBox" style="Z-INDEX: 102; LEFT: 16px; POSITION: absolute; TOP: 52px”/> Cinput type="submit" name+'submitButton" value="Submit” onclick= "if (" + "typeof(Page_ClientValidate) = 'function') " + "Page_ClientValidate(); " language="javascript" id="submitButton" stylb="Z-INDEX: 103; LEFT: 16px; POSITION: absolute; TOP: 86px" /> <span id="phoneInputValidator" controlvalidate="inputTextBox" errormessage="Please enter a phone number." evaluationfunction= "Requi redFieIdVa1idatorEvaluateIsVa1id" initialvalue='"' style="color:Red;Z-INDEX:105; LEFT:217px;POSITION:absolute;TOP:47px; visibility:hidden;">Please enter a phone number. </span> <span id="promptLabel" style="Z-INDEX: 101; LEFT: 16px; POSITION: absolute; TOP: 23px"> Please enter a phone number in the form 555-4567: </span> <script 1anguage="javascript"> <! — var Page_Validators = new Array( document.all["phoneNumberValidator”1, document.all["phoneinputvalidator"]); // —> </script>
628 Глава 17 80 <script language="javascript"> 81 <! — 82 var Pahe_ValidationActive = false; 83 84 if ( 85 typeof(clientinformation) != "undefined" && 8 6 clientlnformation.appName.indexOf("Explorer") 87 != -1) { 88 89 if (typeof(Page_ValidationVer) ++ "undefined") 90 alert( 91 "Unable to find script library " + 92 "'/aspnet_cxlient/system_web/'"+ 93 " ’ l_0_3215_'ll/WebUIValidation. js'. "+ 94 "Try placing this file manually, or " + 95 "reinstall by running 'aspnet_regiis -c'."); 96 else if (Page_ValidationVer != "125") 97 alert( 98 "This page uses an incorrect version " + 99 "of WebUIValidation.js. The page " + 100 "expects version 125. " + 101 "The script library is " + 102 Page_ValidationVer + "."); 103 else 104 ValidatorOnLoadO; 105 } 106 107 function ValidatorOnSubmit() { 108 if (Page_ValidationActive) { 109 ValidatorCommonSubmit(); 110 } 111 ) 112 // —> . 113 </script> 114 </form> 115 </body> 116 </HTML> В предыдущих файлах ASPX атрибут Enableviewstate явно устанавливался на false. Данный атрибу! опреде- ляет, сохраняются ли свойства элемента управления Web при возникновении обратной регистрации. По умол- чанию данный атрибут имеет значение true; это указывает на сохранение значений элемента управления. Скры- тые входные данные в документе НТМ (строки 22 и 23) сохраняют свойства элементов управления данной страницы между клиентскими запросами. Этот элемент всегда имеет имя___viewstate; свойства элементов управления сохраняются в нем в виде кодированных данных. Совет по повышению производительности______________________________________________ Присвоение атрибуту EnabledViewState значения false снижает объем данных, передаваемых на Web-сервер. 17.6. Контроль сеансов Поначалу критики обвиняли Интернет и электронную коммерцию в невозможности предоставления специали- зированных услуг; особенно остро это ощущалось в деятельности компаний-продавцов строительных материа- лов. Для решения данной проблемы предприятия электронной торговли начали внедрять механизмы, с по- мощью которых стало бы возможна своеобразная индивидуализация просмотра содержимого браузера от- дельными пользователями с исключением не относящейся к делу информации. Предприятия достигают такого уровня обслуживания путем контроля (отслеживания) перемещений каждого пользователя в Интернете и объе- динения собранных данных с информацией, предоставляемой клиентом, включая информацию о составлении счетов, личные данные, интересы и хобби. Индивидуализация (personalization) позволяет предприятиям электронной коммерции эффективно взаимодейст- вовать с заказчиками и повышает их возможности нахождения нужных товаров и услуг. Компании, предостав-
ASP.NET, Web-формы и элементы управления Web 629 ляющие пользователям нужную информацию, могут налаживать связи с заказчиками, которые со временем бу- дут расширяться. Более того, делая потребителям конкретные предложения, рекламируя необходимые товары и услуги, предприятия электронной торговли способствуют повышению лояльности своих заказчиков. На таких Web-сайтах, как MSN.com и CNN.com, с помощью довольно сложных технологий стала возможной индивиду- альная настройка домашних страниц под конкретные потребности и предпочтения. Подобным же образом сай- ты электронной торговли часто сохраняют для заказчиков персональную информацию, направляют уведомле- ния и специальные предложения, полностью соответствующие запросам и интересам потребителей. Такие службы могут создавать базы данных клиентов, которые посещают такие сайты чаще других и более регулярно делают на них покупки Впрочем, между индивидуализированной службой электронного бизнеса и обеспечением конфиденциальности (privacy protection) существует компромисс. В то время как одним клиентам импонирует идея наличия на сайте заказного содержимого, другие боятся, что утечка информации, которую они предоставляют предприятиям электронной коммерции, либо которая собрана путем отслеживания новых технологий, может отрицательно сказаться на их существовании. Потребители и защитники конфиденциальности задают вопросы: "А вдруг ком- пании электронной торговли, которым мы предоставляем персональные данные, продадут или передадут эту информацию другим организациям без нашего ведома? А если мы не хотим, чтобы наши операции в Интерне- те — условно анонимной среде — отслеживались и фиксировались неизвестными сторонами? Что будет, если лица без полномочий получат доступ к важным персональным данным, например, к номерам кредитных карт или историям болезни?" Все это — вопросы, которые должны обсуждаться и подробно изучаться в равной сте- пени потребителями, компаниями электронной торговли и законодательными органами. Для обеспечения заказчикам индивидуализированных услуг компании электронной торговли должны уметь распознавать особых клиентов при запросе последними информации с сайта. Как уже обсуждалось, HTTP акти- визирует систему "запрос/ответ", по которой работает Web. К сожалению, HTTP — протокол без отслеживания состояния (stateless protocol): он не поддерживает устойчивых подключений, позволяющих Web-серверам от- слеживать информацию о состоянии конкретных клиентов. То есть, Web-серверы не могут определить автора запроса' либо запрос создается конкретным клиентом, либо серия запросов создается этим же или другими кли- ентами. Для обхода этой проблемы на таких сайтах, как MSN.com и CNN.com, имеются механизмы, с помощью которых они идентифицируют отдельных клиентов. Каждый конкретный клиент в Интернете определяется идентификатором сеанса (session ID). Если клиент выходит с сайта, а позже возвращается, то он идентифици- руется как тот же пользователь. Для того чтобы сервер мог распознавать клиентов, каждый из них должен иден- тифицировать себя. Отслеживание отдельных клиентов, называемое контролем сеансов (session traking), дости- гается одним из нескольких способов. В одной из наиболее популярных методик используются специальные средства идентификации (cookies— см. разд. 17.6.1), в других применяется объект HttpSessionState NET Framework (см. разд. 17.6.2). Дополнительные способы контроля сеансов включают добавление скрытых вход- ных элементов формы и перезапись URL. При использовании скрытых элементов Web-форма записывает дан- ные контроля сеанса в форму (form) на Web-странице, возвращаемой клиенту в ответ на предыдущий запрос. При вводе пользователем формы в новую Web-страницу все данные формы, включая скрытые поля, передаются в обработчик формы на Web-сервере. При применении сервером перезаписи URL Web-форма вводит информа- цию контроля сеанса непосредственно в URL гиперссылок, к которым обращается пользователь для отправки последующих запросов на Web-сервер. Читателю следует отметить, что в предыдущих разделах свойству EnableSessionstate Web-формы присваива- лось значение false. Однако, по причине того, что в последующих примерах планируется использование кон- троля сеансов, оставим данное свойство в режиме по умолчанию — true. 17.6.1. Cookies Популярным способом настройки Web-страниц под требования конкретных пользователей является использо- вание особых средств идентификации, называемых файлами cookies (фрагменты данных о предыстории обра- щений пользователя к WWW-серверу, автоматически создаваемые сервером на машине пользователя). Cookie — это текстовый файл, сохраняемый сайтом на компьютере пользователя с тем, чтобы сайт мог отсле- живать пользовательские действия и предпочтения. При первом посещении сайта пользовательский компьютер может получить файл cookie, содержащий уникальный идентификатор данного пользователя — пароль. Этот пароль активизируется с каждым последующим посещением пользователем сайта. Сайт требует cookies для идентификации пользователя и сохранения информации о нем, например, почтового индекса, либо любых дру- гих данных, упрощающих процесс распределения интересующего пользователя содержимого. Предполагается, что собираемая информация является анонимной записью для идентификации в изитов пользователя на данный сайт в будущем. В файлах cookies программных приложений электронной торговли могут храниться уникаль- ные идентификаторы пользователей. При добавлении пользователем наименования товара в виртуальную "кор-
630 Глава 17 зину", либо при выполнении любого другого действия, результатом которого становится запрос сервера, по- следний получает файл cookie, содержащий уникальный идентификатор пользователя. Сервер использует его для определения местонахождения "корзины" пользователя и выполняет всю необходимую обработку. Помимо идентификации пользователей, cookies могут содержать их предпочтения. При подключении клиента к Web-форме последняя может просмотреть cookie, отправленный клиенту в процессе предыдущих обращений, определить его предпочтения и тут же представить интересующие клиента товары. В каждом сеансе взаимодействия клиента с сервером посредством HTTP участвует заголовок, содержащий ин- формацию либо о запросе (когда связь осуществляется от клиента к серверу), либо об ответе (когда связь имеет место от сервера к клиенту). При получении Web-формой запроса заголовок содержит такую информацию, как тип запроса (например, get) и любые файлы cookies, сохраненные сервером на клиентской машине. При фор- мировании сервером ответа в информацию заголовка входят все файлы cookies, которые сервер предпочитает сохранить на клиентской машине, а также такую информацию, как, например, тип MIME ответа. Если сервер не задал дату истечения срока действия файла cookie, тогда браузер поддерживает cookie весь срок сеанса (который, как правило, завершается с закрытием браузера). В противном случае браузер поддержи- вает файл cookie до даты истечения срока его действия. Последнюю можно задать через свойство Expires файла cookie. При запросе браузером ресурса в Web-сервере файл cookie, отправленный ранее сервером клиенту, воз- вращается на сервер как часть запроса. После истечения срока действия файлов cookies они удаляются. В представленном далее Web-приложении демонстрируется использование файлов cookies. Пример содержит две страницы. В первой странице (листинги 17.11 и 17.12) пользователи выбирают любимый язык программи- рования в группе переключателей, после чего отправляют форму (form) HTML на Web-сервер для обработки (рис. 17.19, а). Сервер отвечает созданием файла cookie и сохраняет запись выбранного языка, а также номер ISBN книги по данной теме. Затем сервер возвращает документ HTML в браузер; при этом пользователь может либо выбрать другой язык программирования (рис. 17.19, б, в), либо просмотреть вторую страницу приложения (листинги 17.13 и 17.14), где перечислена рекомендуемая литература по языку программирования (рис. 17.19, г), выбранному пользователем ранее. При щелчке пользователем кнопкой мыши на гиперссылке считывается ранее сохраненный на клиентской машине файл cookie, используемый для создания списка реко- мендуемой литературы. Г”................ —z— ----------------------------------------------------------------------:—1------------------------------------------------- j Листинг 17.11. Файл ASPX. представляющий список Языков программирования Ъ '« лапт» • iHH iimaii * • naaawmm awnamuamHwioanna iHnaini ai m aaaatwranaknnamaaaaakawatxxnwMo»^ „к. «о I» .о. . %. >ал ••tkkaa>akakM>«kaM«a«a».>k*Waak»ba»ak.>>». 1 <%— Листинг 17.11: OptionsPage.aspx • —%> 2 <%— Страница ASPX позволяет выбрать язык программирования —%> 3 4 <%@ Page language="c#" Codebehind="OptionsPage.aspx.es" 5 AutoEventWireup="false" 6 Inherits="Cookies.OptionsPage" %> 7 8 <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN" > 9 10 <HTML> 11 <HEAD> 12 <title>RecoinmendationsPage</title> 13 <meta name="GENERATOR" Content= 14 "Microsoft Visual Studio 7.0"> 15 <meta name="CODE_LANGUAGE" Content="C#"> 16 <meta name="vs_defaultClientScript" contfent= 17 "JavaScript"> 18 <meta name="vs_targetSchema" content= 19 "http://schemas.microsoft.com/intellisense/ie5"> 20 </HEAD> 21 22 <body> 23 <form id="RecommendationsPage" method="post" 24 runat="server"> 25 <P> 26 <asp:Label id="promptLabel" runat="server" 27 Font-Bold="True">Select a programming language: 28 </asp:Label> 29
ASP.NET, Web-формы и элементы управления Web 631 30 <asp:Label id="welcomeLabel" runat="server" 31 Font-Bold="True" Visible="False"> 32 Welcome to Cookies! You selected 33 </asp:Label> 34 </P> 35 36 <P> 37 <asp:RadioButtonList id="languageList" runat= 38 "server"> 39 <asp:ListItem Value="C#">C#</asp:ListItem> 40 41 <asp:ListItem Value="C++">C++</asp:LiStItem> 42 43 <asp:ListItem Value="C">C</asp:ListItem> 44 45 <asp:ListItem Value="Python">Python 46 </asp:ListItem> 47 48 <asp:ListItem Value="Visual Basic .NET"> 49 Visual Basic .NET 50 </asp:ListItem> 51 </asp:RadioButtonList> 52 </P> 53 54 <P> 55 <asp:Button id="submitButton" runat="server" Text= 56 "Submit"> 57 </asp:Button> 58 </P> 59 60 <P> 61 <asp:HyperLink id="languageLink" runat="server" 62 NavigateUrl="OptionsPage.aspx" Visible="False"> 63 Click here to choose another language. 64 <asp:HyperLink> 65 </P> 66 67 <P> 68 <asp:HyperLink id="recommendationsLink" runat= 69 "server" NavigateUrl="RecommendationsPage.aspx" 70 Visible="False">Click here to get book recommendations. 71 </asp:HyperLink> 72 </P> 73 </form> 74 </body> 75 </HTML> Файл ASPX в листинге 17.11 содержит пять переключателей (строки 39—50) со значениями С#, C++, С, Python и Visual Basic .NET. Программист задает эти значения щелчком кнопки мыши на свойстве items в окне Properties с последующим добавлением наименований через редактор элементов списка List Item Collection Editor. Данный процесс похож на настройку объекта ListBox в приложении Windows. Пользователь выбирает язык программирования выбором одного из переключателей. Данная страница также содержит кнопку Submit, при нажатии которой создается файл cookie, содержащий запись выбранного языка. После создания файл cookie добавляется в заголовок ответа HTTP, и имеет место обратная регистрация. Всякий раз при выборе пользовате- лем языка и нажатия кнопки Submit на машину клиента записывается файл cookie. При возникновении обратной регистрации одни компоненты оказываются скрытыми, а другие — отображен- ными. Ближе к концу страницы показаны две гиперссылки: одна запрашивает текущую страницу (строки 61— 64), а другая — файл Recommendations.aspx (строки 68—71). Обратите внимание, что щелчок по первой гипер- ссылке (запрашивающей текущую страницу) не вызывает обратной регистрации. Файл OptionsPage.aspx указан в свойстве NavigateUrl гиперссылки. При нажатии на эту гиперссылку страница запрашивается абсолютно но- вым запросом с тем, чтобы пользователь мог выбрать новый язык программирования.
632 Глава 17 В листинге 17.12 представлен файл фонового кода. В строке 35 book определяется как класс Hashtable (про- странство имен System.Collections), являющийся структурой данных, сохраняющей пары "ключ/значение". В данной программе ключ используется для сохранения и извлечения ассоциированного в классе Hashtable зна- чения. В примере ключи являются строками (string), содержащими названия языков программирования, а зна- чения являются строками, содержащими номера ISBN рекомендуемых книг. Класс Hashtable имеет метод Add, принимающий в качестве аргументов ключ и значение. Значение для конкретной записи класса Hashtable можно получить индексированием таблицы хэширования с помощью ключа этого значения. Например, выра- жение HashtableName [ keyName ]; возвращает значение из пары "ключ/значение", где keyName— ключ. Пример приведен в строке 92: books [ language 1 возвращает значение, соответствующее ключу, содержащемуся в language. Подробно класс Hashtable рассматривается в главе 20. 1 // Листинг 17.12: OptionsBage.aspx.cs 2 // Перечисление языков программирования для выбора пользователем 3 4 using System; 5 using System.Collections; 6 using System.ComponentModel; 7 using System.Data; 8 using System.Drawing; 9 using System.Web; 10 using System.Web.Sessionstate; 11 using System.Web.UI; 12 using System.Web.UI.WebControls; 13 using System.Web.UI.HtmlControls; 14 15 namespace Cookies 16 { 17 // страница содержит выбор языков в RadioButtonList, 18 // добавление cookies для сохранения пользовательского выбора 19 public class OptionsPage : System.Web.UI.Page 20 { 21 protected System.Web.UI.WebControls.Label promptLabel; 22 protected System.Web.UI.WebControls.Label welcomeLabel; 23 24 protected System.Web.UI.WebControls.RadioButtonList 2 5 languageLi st; 26 27 protected System.Web.UI.WebControls.HyperLink 28 languageLink; 29 protected System.Web.UI.WebControls.HyperLink 30 recommendationsLink; 31 32 protected System.Web.UI.WebControls.Button 33 submitButton; 34 35 protected Hashtable books = new Hashtable(); 36 37 // обработчик события Load 38 private void Page_Load( 39 object sender, System.EventArgs e) 40 { 41 if (IsPostBack) 42 { 43 // при обратной регистрации пользователь ввел 44 // информацию; отображение информации приветствия 45 // и соответствующие гиперссылки 46 welcomeLabel.Visible = true;
ASP.NET, Web-формы и элементы управления Web 633 47 . 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 languageLink.Visible • true; recommendationsLink.Visible = true; // скрыть информацию о выборе submitButton.Visible = false; promptLabel.Visible = false; languageList.Visible = false; // уведомление пользователя о его выборе if (languageList.Selectedltem != null) welcomeLabel.Text += languageList.Selectedltem.Tostring() + "."; else welcomeLabel.Text += "no language."; • } // конец if } // конец метода Page_Load override protected void Onlnit(EventArgs e) { // добавление значений в Hashtable books.Add("С#", "0-13-062221-4"); books.Add("C++", "0-13-089571-7"); books.Add("C", "0-13-089572-5"); books.Add{"Python", "0-13-092361-3"); books.Add("Visual Basic .NET", "0-13-456955-5”); InitializeComponent(); base.Onlnit(e); ) // код, сгенерированный Visual Studio .NET // при нажатии пользователем кнопки Submit // создаются cookies для сохранения пользовательского выбора private void submitButton_Click( * object sender, System.EventArgs e) { // если пользователь сделал выбор if (languageList.Selectedltem != null) { string language = languageList.Selectedltem.ToString(); string ISBN = books [ language ].ToString(); // создание cookies, пара "имя, значение" — 11 язык и номер ISBN, ‘ выбранные из Hashtable HttpCookie cookie = new HttpCookie( language, ISBN); ) // добавление cookies к ответу //с размещением его на машине пользователя Response.Cookies.Add(cookie); } // конец if } // конец метода submitButton_Click ) // конец класса OptionsPage } // конец пространства имен Cookies
634 Глава 17 б Рис. 17.19. Приложение, демонстрирующее использование cookies: а — выбор переключателя и отправка данных на сервер; б — отображение страницы, сообщающей о создании cookies, переход по гиперссылке для выбора другого переключателя; в — выбор другого переключателя и отправка данных; г — переход по другой гиперссылке Как упоминалось ранее, нажатие кнопки Submit вызывает обратную регистрацию. В результате этого условие в операторе if метода Page_Load (строка 41) принимает значение true, и выполняются строки 46—60. В строке 56 определяется, выбрал ли пользователь язык. Если да, то этот язык отображается в welcomeLabel (строки 57 и 58). В противном случае текст, указывающий, что язык не выбран, отображается в welcomeLabel (строка 60). Обе гиперссылки становятся видимыми в строках 47 и 48. Создается новый объект файла cookie (типа HttpCookie) для сохранения языка (language) и соответствующего ему номера ISBN (строки 96 и 97). После этого данный файл cookie добавляется (Add) в коллекцию Cookies, передаваемую как часть заголовка ответа HTTP (строка 101). 1 <%— Листинг 17.13: RecommendationsPage.aspx —%> 2 <%— На странице показаны рекомендации, —%> 3 <%— полученные из класса Hashtable —%> 4 5 <%@ Page language="c#" Codebehind=,,RecommendationsPage.aspx.es” 6 AutoEventWireup="false" 7 Inherits="Cookies.RecommendationsPage" %> 8 9 <!DCXZTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN" > 10 11 <HTML> 12 <HEAD>
ASP.NET, Web-формы и элементы управления Web 635 13 <title>WebForml</title> 14 <meta name="GENERATOR" Content= 15 "Microsoft Visual Studio 7.0"> 16 <meta name="CODE_LANGUAGE" Content="C#"> 17 <meta name="vs_defaultClientScript" content="JavaScript"> 18 <meta name="vs_targetSchema" content= 19 "http://schemas.microsoft.com/intellisense/ie5"> 20 </HEAD> 21 22 <body MS_POSITIONING="GridLayout"> 23 24 <form id="Forml" method="post runat="server"> 25 26 <asp:Label id="recommendationsLabel" 27 style="Z-INDEX: 101; LEFT: 21px; POSITION:absolute 28 TOP: 25px" runat="server" Fonr-Bold="True" 2 9 Font-Si ze="X-Large">Recommendations 30 </asp:Label> 31 32 <asp:ListBox id="bookListBox" style="Z-INDEX: 102; 33 LEFT: 21px; POSITION: absolute; TOP: 82px" runat= 34 "server" Width="383px" Height="91px"> 35 </asp:ListBox> 36 </form> 37 </body> 38 </HTML> Файл RecommendationsPage.aspx содержит метку (строки 26—30) и список (строки 32—35). Метка отображает текст "Recommendations'* (рис. 17.20), если пользователь выбрал один или несколько языков; в противном слу- чае отображается текст "No Recommendations”. В списке отображаются рекомендации, созданные файлом фо- нового кода; они представлены в листинге 17.14. 1 // Листинг 17.14: RecommendationsPage.aspx.cs 2 // Считывание данных cookies с клиентской машины 3 4 using System; 5 using System.Collections; 6 using System.ComponentModel; 7 using System.Data; 8 using System.Drawing; 9 using System.Web; 10 using System.Web.Sessionstate; 11 using System.Web.UI; 12 using System.Web.UI.WebControls; 13 using System.Web.UI.HtmlControls; 14 15 namespace Cookies 16 { 17 // страница отображает информацию cookies и рекомендации 18 public class RecommendationsPage : System.Web.UI.Page 19 { 20 protected System.Web.UI.WebControls.ListBox bookListBox; 21 protected System.Web.UI.WebControls.Label 22 recommendationsLabel; 23 24 // код, сгенерированный Visual Studio .NET 25 26 override protected void Onlnit(EventArcs e) 27 < 28 InitializeComponent(); 29 base.Onlnit(e); 30
636 Глава 17 31 32 33 // извлечение клиентских cookies HttpCookieCollection cookies = Request.Cookies; 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 5 // если существуют cookies, отличные от cookies ID; // перечислить соответствующие книги и номера ISBN if (cookies != null && cookies.Count != 1) for (int i = 1; i < cookies.Count; i++) bookListBox.Items.Add( cookies[ i ] .Name + "How to Program. ISBN#: " + cockiest i ].Value); // если кроме ID cookies нет, то опции не // выбраны, рекомендации не сделаны else { rfecommendationsLabel.Text = "No Recommendations."; bookListBox.Items.Clear(); bookListBox.Visible = false; } // конец метода Onlnit } // конец класса RecommendationsPage_ 56 } // конец пространства имен Cookies -3 WebForml - Microsoft Internet t xplorer £dt Fawurites Tfcbfc Help ‘ -i»BaCK * J J? JI Search jjFjvortes J J)- LAddr0sj^l ~ http:/flocalhost/Cool<ies/Recon>FnendationsPaqe.aspx wynks Recommendations C# How to Program. ISBN# 0-13-062221-4 Python Howto Program. ISBN# 0-13-092361-3 Рис. 17.20. Вывод рекомендаций Метод Onlnit (строки 26—52) извлекает файлы cookies с клиентской машины с помощью свойства Cookies объекта Request (строка 32). При этом возвращается коллекция типа HttpCookieCollection, содержащая файлы cookies, ранее записанные на клиентскую машину. Приложение может считывать файлы cookies только в слу- чае, если они созданы в домене, в котором выполняется приложение: Web-сервер никогда не получит доступа к файлам cookies, созданным за пределами домена, относящегося к данному серверу. Например, файл cookie, соз- данный Web-сервером в домене deitel.com, никогда не будет загружен сервером в домене bug2bug.com. Строка 36 определяет существование, как минимум, двух файлов cookies. ASP.NET всегда добавляет в ответе файл cookie с именем ASP.NET_sessionid, поэтому строка 36 обеспечивает наличие минимум одного файла cookie, кроме ASP.NET Sessionid. В строках 38—41 информация добавляется в другой файл (файлы) cookie в списке. Цикл for итерируется через все файлы cookies, кроме первого (ASP.NET SessioniD). Приложение извле- кает имя и значение каждого файла cookie с помощью i — переменной цикла for — для определения текущего значения в коллекции файлов cookies. Свойства Name и value класса HttpCookie, содержащие язык и номер. ISBN, соответственно, связываются с "How to Program. ^ISBN#" и добавляются в список (ListBox). В последнем отображается максимум пять книг. Строки 47—-49 исполняются, если язык не был выбран. В табл. 17.2 кратко описаны некоторые широко используемые свойства класса HttpCookie.
ASP.NET, Web-формы и элементы управления Web 637 Таблица 17.2. Свойства класса HttpCookie Свойства Описание Domain Возвращает строку, содержащую домен файла cookie (т. е. домен Web-сервера, с которого загружался файл cookie). Этим определяется, какой Web-сервер может получить файл cookie. По умолчанию фай- лы cookies отправляются на Web-сервер, с которого файл cookie был изначально отправлен клиенту Expires Возвращает объект DateTime, указывающий дату, когда браузер может удалить файл cookie Name Возвращает строку, содержащую имя файла cookie Path Возвращает строку, содержащую префикс URL для файла cookie. Файлы cookies могут быть "нацелены" на конкретные URL, включающие каталоги на Web-сервере, что позволяет программисту задать место- положение файла cookie. По умолчанию файл cookie возвращается службам, функционирующим в том же каталоге, что и служба, отправившая файл cookie или подкаталог данного каталога Secure Возвращает булево значение (bool), указывающее на передачу файла cookie по защищенному прото- колу. Значение true обуславливает использование защищенного протокола Value Возвращает строку, содержащую значение файла cookie 17.6.2. Контроль сеансов с помощью класса HttpSessionState Язык C# обеспечивает возможность контроля сеансов в классе HttpSessionState библиотеки классов FCL. Для демонстрации основных методов контроля сеансов листинг 17.14 был изменен так, что в нем стали использо- ваться объекты HttpSessionState. В листинге 17.15 представлен файл ASPX, а в листинге 17.16 — файл фоно- вого кода. Файл ASPX похож на представленный файл в листинге 17.11. I Листинг 17.15. Опции, представленные на странице ASPX V » !5-£' 1 <%— Листинг 17.15: OptionsPage.aspx —%> 2 <%— Страница со списком вариантов языков —%> 3 4 <%@ Page language="c#" Codebehind="OptionsPage.aspx.cs" 5 AutoEventWireup="false" Inherits= 6 "Sessions.OptionsPage” %> 7 8 <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN" > 9 <HTML> 10 <HEAD> 11 <title>RecommendationsPage</title> 12 <meta name="GENERATOR" Content= 13 "Microsoft Visual Studio 7.0"> 14 <meta name=="CODE_LANGUAGE" Content="C#"> 15 <meta name="vs_defaultClientScript" content="JavaScript"> 16 <meta name="vs_targetSchema" content= 17 "http://schemas.microsoft.com/intellisense/ie5"> 18 </HEAD> 19 20 <body> 21 <form id="RecoiranendationsPage" method="post" 22 runat="server”> 23 <P> 24 <asp:Label id="promptLabel" runat="server" 25 Font-Bold="True">Select a programming language: 26 </asp:Label> 27 28 <asp:Label id="welcomeLabel" runat="server" 29 Font-Bold="True" Visible="False"> 30 Welcome to Cookies! You selected 31 </asp:Label> 32 </P> 33
638 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 Глава 17 <Р> <asp:RadioButtonList id="languageList" runat= "server"> <asp:ListItem Value=”C#">C#</asp:ListItem> <asp:ListItem Value="C++">C++</asp:ListItem> <asp:ListItem Value="C">C</asp:ListItem> <asp:Listitem Value="Python">Python </asp:ListItem> <asp:ListItem Value="Visual Basic .NET"> Visual Basic .NET s </asp:ListItem> </asp:RadioButtonList> </P> <P> <asp:Button id="submitButton" runat="server" Text="Submit"> </asp:Button> </P> <P> <asp:Label id="idLabel” tunat="server"> </asp:Label> </P> <P> <asp:Label id="timeoutLabel" runat="server"> </asp:Label> </P> <P> <asp:Label id="newSessionLabel" runat="server"> </asp:Label> </P> <P> <asp:HyperLink id="languageLink" runat="server" NavigateUrl="OptionsPage.aspx" Visible="False"> Click here to choose another language. <asp:HyperLink> </P> <P> <asp:HyperLink id="recommendationsLink" runat= "server" NavigateUrl=** Recommendations Page. aspx" Visible="False"> Click here to get book recommendations. </asp:HyperLink> </P> </form> </body> </HTML> Каждая Web-форма включает в себя объект HttpSessionState, доступ к которому осуществляется через свойст- во Session класса Раде. В настоящем разделе для манипуляций объектом HttpSessionState страницы использу- ется свойство Session. При запросе Web-страницы создается объект HttpSessionState и присваивается свойст- ву Session класса Раде. Свойство Session часто называется объектом Session. При нажатии пользователем кнопки Submit в файле фонового кода (листинг 17.16) активизируется метод submitButton Click. Метод
ASP.NET, Web-формь: и элементы управления Web 639 submi t But tonclick добавляет пару "ключ/значение" в объект Session, указывая выбранный язык и номер ISBN книги, посвященной выбранному языку. После этого имеет место обратная регистрация. Каждый раз при нажа- тии пользователем кнопки Submit метод submitButton Ciick добавляет новую пару "язык/ISBN" в объект HttpSessionState. По причине того, что многое в данном примере сходно с последним рассмотренным приме- ром, особое внимание обращается на новые функции. Замечание по технологии программирования________________________________________________ Для поддержки информации о состоянии клиента в Web-форме не следует использовать переменные экземпля- ра, потому что клиенты, осуществляющие параллельный доступ к этой Web-форме, могут подменить совместно используемые переменные экземпляра. Web-формы должны поддерживать информацию о состоянии клиента в объектах HttpSessionState, потому что такие объекты индивидуальны для каждого клиента. 1 // Листинг 17.16: OptionsPage.aspx.cs 2 // Перечисление языков программирования 3 // выбор сохраняется в объекте Session страницы 4 5 using System; 6 using System.Collections; 7 using System.ComponentModel; 8 using System.Data, 9 using System.Drawing; 10 using System.Web; 11 using System.Web.Sessionstate; 12 using System.Web.UI; 13 using System.Web.UI.WebControls; 14 using System.Web.UI.HtmlControls; 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 namespace Sessions { // страница содержит выбор языков в RadioButtonList, // добавление cookie для сохранения пользовательского выбора public class OptionsPage : System.Web.UI.Page { protected System.Web.UI.WebControls.Label promptLabel; protected System.Web.UI.WebControls.Label welcomeLabel; protected System.Web.UI.WebControls.Label idLabel; protected System.Web.UI.WebControls.Label timeoutLabel; protected System.Web.UI.WebControls.HyperLink languageLink; protected System.Web.UI.WebControls.HyperLink recommendat ionsLink; protected System.Web.UI.WebControls.RadioButtonList languageList; protected System.Web.UI.WebControls.Button submitButton; private Hashtable books = new Hashtable(); // обработчик события Load private void Page_Load( object sender, System.EventArgs e) { // если страница загружена из-за обратной регистрации, // загрузить информацию сеанса, скрыть выбор языков if (IsPostBack) { // отображение компонентов, содержащих информацию о сеансе welcomeLabel.Visible = true; languageLink.Visible = true; recommendationsLink.Visible = true;
640 Глава 17 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 ) // скрыть компоненты submitButton.Visible = false; promptLabel.Visible = false; languageList.Visible = false; // установка меток для отображения информации о сеансе if (languageList.Selectedltem !« null) welcomeLabel.Text += languageList.Selectedltem.ToStringO + else welcomeLabel.Text += "no language."; idLabel.text += "Your unique session ID is: "+ Session.SessionlD; timeoutlabel.Text += "Timeout: " + Session.Timeout + "minutes"; } // конец if ) // конец метода Page_Load override protected void Onlnit(EventArgs e) { // добавление значений в Hashtable books.Add("С#", "0-13-062221-4"); books.Add("C++", "0-13-089571-7”); books.Add("C", "0-13-089572-5"); books.Add("Python", "0-13-092361-3"); books.Add("Visual Basic .NET", "0-13-456955-5"); InitializeComponent(); base.Onlnit(e); // код, сгенерированный Visual Studio .NET // при нажатии пользователем кнопки Submit // сохранение пользовательского выбора в объекте Session private void submitButton_Click( object sender, System.EventArgs e) { if (languageList.Selectedltem != null) { string language = languageList.Selectedltem.ToString(); string ISBN = books [ language ].ToString(); // сохранение в объекте Session как пары "имя, значение" — // язык и номер ISBN соответствующей книги // выбраны из Hashtable Session.Add(language, ISBN); ) // конец if } // конец метода submitButton_Click } // конец класса OptionsPage // конец пространства имен Sessions Подобно cookies, объект HttpSessionState может сохранять пары "имя, значение". На языке сеанса они назы- ваются элементами сеанса (sessions items) и размещаются в объекте HttpSessionState вызовом метода Add.
ASP.NET, Web-формы и элементы управления Web 641 В строке 102 вызывается метод Add для размещения названия языка и номера ISBN рекомендуемой книги в объекте HttpSessionState. Одним из основных преимуществ использования объектов HttpSessionState (вместо cookies) является то, что объекты HttpSessionState могут сохранять в качестве значения атрибута объекты любого типа (а не только строки). Это обеспечивает программистам C# большую свободу в определении типа информации о состоянии, которую они хотят поддерживать для своих клиентов. ЕсЛи приложение обращается к методу Add для добавления атрибута с тем же именем, что и атрибут, ранее сохраненный в сеансе, тогда объект, относящийся к этому атрибуту, подменяется. После добавления значений в объект HttpSessionState приложение обрабатывает события обратной регистра- ции (строки 44—70) в методе Page_Load. Здесь информация о текущем сеансе клиента извлекается из свойств объекта Session и отображается на Web-странице. Приложение ASP.NET содержит информацию об объекте HttpSessionState для текущего клиента. Свойство SessioniD (строки 63 и 64) содержит уникальный идентифи- катор сеанса. При первом подключении клиента к серверу для этого клиента создается уникальный идентифи- катор сеанса. При дополнительных запросах клиента его идентификатор сеанса сравнивается с идентификато- рами сеанса, сохраненными в памяти сервера. Свойство Timeout (строка 66) задает максимальное время, кото- рое объект HttpSessionState может быть неактивным до своего уничтожения. В табл. 17.3 представлены некоторые свойства объекта HttpSessionState. Результат работы приложения представлен на рис. 17.21. б J Recommendat- Microsoft ЕЛ «* 4WU1 tools w ... ^. 2^ „ ГТ AddrBifJ^Sesstons/Opt^sP^i.aspx Welcome to Sessions! You selected Visual Basic .NET. Your unique session ID is: ulliu055xoesgp455qkfal45 Timeout: 20 minutes Click here to choose another language. Click here to set book recommendations. Рис. 17.21. Приложение, демонстрирующее использование сеансов: а — выбор переключателя и отправка данных на сервер; б — отображение страницы с номером сеанса и переход по гиперссылке для выбора другого переключателя; в — выбор другого переключателя и отправка данных; г — переход по другой гиперссылке Как в примере с cookies, в данном приложении имеется ссылка на файл RecommendationsPage.aspx (лис- тинг 17.17), в котором представлен список рекомендуемой литературы, исходя из выбранного пользователем языка. В строках 30—33 определен элемент управления Web ListBox, используемый для предоставления поль- зователю рекомендаций. В листинге 17.18 для данного файла ASPX представлен файл фонового кода. 41 Зак. 3333
642 Глава 17 Таблица 17.3. Свойства объекта HttpSessionState Свойства Описание Count Задает количество пар "ключ/значение" в объекте Session IsNewSession Указывает, новый ли это сеанс (т. е. создавался ли сеанс при загрузке страницы) IsReadOnly Указывает, предназначен ли объект Session только для чтения Keys Возвращает коллекцию, содержащую ключи объекта Session SessionlD Возвращает уникальный идентификатор сеанса Timeout Задает максимальное количество минут, в течение которых сеанс может быть неактивным (т. е. отсутствуют запросы) до истечения срока его действия. По умолчанию данное свойство имеет значение 20 минут --------------] ^Листинг 17.17. Информация сеанса, отображенная в ListBox : >,*, .f-ч .. ’’Л' л > v __---------------------------- 1 <%— Листинг 17.17: RecommendationsPage.aspx —%> 2 <%— Считывание данных пользовательского сеанса —%> 3 4 <%@ Page language="c#" Codebehind="RecommendationsPage.aspx.cs" 5 AutoEventWireup="false" 6 Inherits="Sessions.RecommendationsPage” %> 7 8 <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN" > 9 10 <HTML> 11 <HEAD> 12 <title>WebForml</title> 13 <meta name="GENERATOR" Content= 14 "Microsoft Visual Studio 7.0"> 15 <meta name="CODE_LANGUAGE" Content="C#"> 16 <meta name="vs_defaultClientScript" content= 17 "JavaScript"> 18 <meta name="vs_targetSchema" content= 19 "http://schemas.microsoft.com/intellisense/ie5"> 20 </HEAD> 21 22 <body MS_POSITIONING="GridLayout"> 23 <form id="Forml" method="post runat="server"> 24 <asp:Label id="recommendationsLabel" 25 style="Z-INDEX: 101; LEFT: 21px; POSITION:absolute; 26 TOP: 25px" runat="server" Fonr-Bold="True" 27 Font-Si ze="X-Large”>Recommendat ions 28 </asp:Label> 29 30 <asp:ListBox id="bookListBox” style="Z-INDEX: 102; 31 LEFT: 21px; POSITION: absolute; TOP: 84px" runat= 32 "server" Width="383px" Height="91px"> 33 </asp:ListBox> 34 </form> 35 </body> 36 </HTML> 1 // Листинг 17.18: RecomendationsPage.aspx.es 2 // Считывание данных сеанса с пользовательской машины 3 4 using System; 5 using System.Collections;
ASP.NET, Web-срормы и элементы управления Web 643 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 using System. ComponentModel; using System.Data; using System.Drawing; using System.Web; e using System.Web.Ses^ionState; using System.Web.UI; using System.Web.UI.WebControls; using System.Web.UI.HtmlControls; / namespace Sessions { /I страница отображает информацию о сеансе и рекомендации public class RecommendationsPage : System.Web.UI.Page { protected System.Web.UI.WebControls.ListBox bookListBox protected System.Web.UI.WebControls.Label recommendationsLabel; // код, сгенерированный Visual Studio .NET // обработчик события Init override protected void Oninit(EventArgs e) { InitializeComponent(); base.Oninit(e); // определение наличия информации в объекте Session if (Session.Count != 0) { // итерация через значения Session, // отображение в ListBox for (int i = 0; i < Session.Count; i++) { // сохранение текущего ключа в sessionName string keyName = Session.Keys[ i ]; 11 использование текущего ключа для , II отображения пар "имя, значение" сеанса booksListBox.Items.Add(keyName + " How to Program. ISBN#: " + Session[ keyName ]); } // KOHew for } else recommendationsLabel.Text = "No Recommendations"; booksListBox.Visible = false; } } // конец метода Oninit } // конец класса RecommendationsPage } // конец пространства имен Sessions 1 Обработчик события Oninit (строки 28—58) извлекает информацию о сеансе. Если пользователь никогда не выбирал языки программирования во время посещения сайта, тогда свойство count объекта Session будет иметь нулевое значение. Данное свойство подсчитывает число элементов сеанса, содержащихся в объекте Session. Если значение свойства Count объекта Session равно нулю (т. е. ни один язык не выбирался), тогда отображается текст "No Recommendations”.
644 Глава 17 Если пользователь выбрал язык, тогда цикл for (строки 38—49) итерируется через объект Session. Свойство Count содержит количество пар "ключ/значение", сохраненное в данном сеансе. Значение пары "ключ/значение" извлекается из объекта Session путем индексирования объекта Session именем ключа с помощью того же про- цесса, с каким извлекалось значение из хэш-таблицы в предыдущем разделе. Затем осуществляется доступ к свойству Keys класса HttpSessionState (строка 41), которое возвращает коллек- цию, содержащую все ключи в сеансе. Данная строка индексирует коллекцию для извлечения текущего ключа. В строках 45—47 значение keyName связывается со строкой "How to Program. ISBN#:" и co значением из объек- та сеанса, для которого keyName является ключом. Результирующая строка является рекомендацией, появляю- щейся в списке ListBox (рис. 17.22). Рис. 17.22. Страница с выведенными рекомендациями 17.7. Учебный пример: гостевая книга в режиме on-line Многие Web-сайты дают пользователям возможность изложения своих впечатлений о сайте в так называемой гостевой книге (guest book). Обычно для входа на страницу гостевой книги пользователям достаточно щелкнуть кнопкой мыши по соответствующей ссылке на домашней странице сайта. Эта страница, как правило, состоит из формы HTML, содержащей поля для ввода имени пользователя, адреса электронной почты и сообщений. По- ступающие в гостевую книгу данные часто сохраняются в базе данных, размещенной на машине сервера. В на- стоящем разделе будет рассмотрено создание приложения Web-формы для гостевой книги. Пользовательский интерфейс здесь немного сложнее, чем в вышеприведенных примерах; в нем имеется DataGrid (рис. 17.23). Представленная пользователю форма HTML состоит из полей ввода имени пользователя, адреса электронной почты и сообщения. В листинге 17.19 представлен файл ASPX, а в листинге 17.20— файл фонового кода при- ложения гостевой книги. Ради простоты будем записывать информацию гостевой книги в текстовый файл. Файл ASPX, сгенерированный GUI, показан в листинге 17.19. После перетаскивания двух кнопок на форму дважды щелкните кнопкой мыши на каждой кнопке для создания соответствующих им обработчиков событий. В файл фонового кода Visual Studio автоматически добавляет обработчики событий (листинг 17.20). Объект DataGrid с именем dataGrid отображает все записи в гостевой книге. Этот элемент управления можно добавить из панели инструментов Toolbox. Цвета для объекта DataGrid задаются через ссылку Auto Format,**, располо- женную в нижней части окна Properties. Эта ссылка появляется только при просмотре разработчиком свойств объекта DataGrid. Откроется диалоговое окно с несколькими вариантами выбора. В данном примере в этом ок- не выбрана опция Colorful 4. Добавление информации в DataGrid описывается далее. I Листинг 17.19. Файл ASPX гостевой книги > - ’ГМ” Л . %* а 2«г*п,,и£а*,№1 га «1> аааа|(Лаппаам»иНаакпаапг>шмам»'<1аи1аМтаай|«йаипаМ1>апааннн><|*<тпа«ВпАаа»ай'«|(аам}пн4паынан>1п»анй1тН >4<*,***k*,*t<baa>>*Vi •••*•<••••»••.йамаЙиОаиаамиИ 1 <%— Листинг 17.19: Welcome.aspx —%> 2 <%— Web-форма, демонстрирующая гостевую книгу —%> 3 4 <%@ Page language="c#" Codebeh-ind="Welcome.aspx.cs" 5 AutoEventWireup="false" 6 Inherits="Guestbook.GuestBookTest" %> 7 8 <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN" > 9
ASP.NET, Web-формы и элементы управления Web 645 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 <HTML> <HEAD> <t itle>WebForml</1itle> <meta name="GENERATOR" Content3 "Microsoft Visual Studio 7.0"> <meta name="CODE_LANGUAGE" Content3"C#"> “ <meta name3”vs_defaultClientScript" content3"JavaScript"> <meta name3" vs_target Schema" content3 "http://schemas.microsoft.com/intellisense/ie5"> </HEAD> <body MS_POSITIONING3"GridLayout"> <form id="Forml" method="post" runat3"server"> <asp:DataGrid id ="dataGrid" style3"Z-INDEX: 101; LEFT: 13px; POSITION: absolute; TOP: 301px" runat3 "server" Width3"698px" HorizontalAlign3"Left" Bordercolor3"#E7E7FF" Borderwidth3"Ipx" Gridlines="Horizontal" BackColor="White" DataSource="<%# dataView %> BorderStyle="None" CellPadding="3"> <SelectedItemStyle Font-Bold="True" ForeColor= "#F7F7F7" BackColor="#738A9C"> </SelectedItemStyle> CAlternatingltemStyle BackColor3”#F7F7F7”> </AlternatingItemStyle> <ItemStyle HorizontalAlign="Left" ForeColor3 "#4A3C8C" BackColor3"#E7E7EF"> </ItemStyle> <HeaderStyle Font-Bold="True" ForeColor="#F7F7F7" BackColor="#4A3C8C"> </HeaderStyle> <FooterStyle ForeColor3"#4A3C8C" BackColor "#B5C7DE"> </FooterStyle> <PagerStyle HorizontalAlign="Right" ForeColor3 "#4A3C8C" BackColor3"#E7E7EF" Mode3 "NumericPages"> </PagerStyle> <asp:DataGrid> <asp:Button id="clearButton" style="Z-lNDEX: 111; LEFT: 354px; POSITION: absolute; TOP: 262px" runat3"server" Width="57px" Text="Clear"> </asp:Button> <asp:Button id3"submitButton" style3"Z-INDEX: 110; LEFT: 205px; POSITION: absolute; TOP: 264px” runat3"server" Text3"Submit"> </asp:Button> <asp:TextBox id="messageTextBox" style="Z-INDEX: 109; LEFT: lllpx; POSITION: absolute; TOP: 139px" runat="server" Width="427px" Height3"107px" TextMode="MultiLine"> </asp:TextBox>
646 Глава 17 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 ' 95 96 97 98 99 100 101 102 103 104 105 <asp:Label id="messageLabel" style="Z-INDEX: 108; LEFT: 13px; POSITION: absolute; TOP: 149px" runat="server" Width="59px" Height="9px"> Tell the world: </asp:Label> <asp:Label id="emailLabel" style="Z-INDEX: 107; LEFT: 13px; POSITION: absolute; TOP: 91px" runat="server" Width="76px">E-mail address: </asp:Label> <asp:TextBox id="emailTextBox” style="Z-INDEX: 106; LEFT: lllpx; POSITION: absolute; TOP: 99px" runat="server" Width="428px"> </asp:TextBox> <asp:Label id="nameLabel" style="Z-INDEX: 104; LEFT: 13px; POSITION: absolute; TOP: 59px" runat="server" Width="84px">First Name: </asp:Label> <asp:TextBox id="nameTextBox" style="Z-INDEX: 105; LEFT: lllpx; POSITION: absolute; TOP: 59px" runat="server" Width="428px"> </asp:TextBox> <asp:Label id="promptLabel" style="Z-INDEX: 102; LEFT: 13px; POSITION: absolute; TOP: 12px" runat="server" ForeColor="Blue" Font-Size="X-Large"> Please leave a message in our guest book: </asp:Label> </form> </body> </HTML> Рис. 17.23. GUI приложения гостевой книги
ASP.NET, Web-формы и элементы управления Web 647 -------w '------ Листинг 17.29. Файл Фонового кода для гостевой книги 1 2 3 4 5 б 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 // Листинг 17.20: Welcome.aspx.cs // Файл фонового кода для страницы гостевой книги using System; using System.Collections; using System.ComponentModel; using System.Data; using System.Drawing; using System.Web; using System.Web.Sessionstate; using Systein.Web.UI; using System.Web.UI.WebControls; using System.Web. UI". HtmlControls ; using System.IO; namespace Guestbook { // пользователь может оставлять сообщения public class GuestBookForm : System.Web.UI .Pac/e { protected System.Web.UI.WebControls.Label promptLabel; protected System.Web.UI.WebControls.Label nameLabel; protected System.Web.UI.WebControls-Label emailLabel; protected System.Web.UI.WebControls.Label messageLabel; protected System.Web.UI.WebControls.Datagrid dataGrid; protected System.Web.UI.Wetcontrols.Button submitButton; protected System.Web.UI.WebControls.Button clearButton; protected System.Web.UI.WebControls.TextBox nameTextBox; protected System.Web.UI.WebControls.TextBox emailTextBox; protected System.Web.UI.WebControls.TextBox messageTextBox protected System.Data.DataView dataView; // обработка события Load класса Page private void Page_Load( object sender, System.EventArgs e) { dataView = new DataView(new DataTable()); } // конец метода Page_Load // код, сгенерированный Visual Studio .NET // размещение всех сообщений в гостевой книге //в таблице; сообщения разделены горизонтальными линиями public void FillMessageTable() { DataTable table = dataView.Table; table.Columns.Add("Date"); table.Columns.Add("First Name"); table.Columns.Add("e-mail"); table.Columns.Add("Message"); // открыть файл гостевой книги для считывания StreamReader resder = new StreamReader( Request.PhysicalApplicatibnPath + "guestbook.txt'9;
648 Глава 17 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 } char[] separator = { ’ \t' }; // считывание одной строки из файла string message = reader.ReaderLine(); while (message != null) { // разбиение строки на четыре части string!] parts = message.Split(separator); // загрузка данных в таблицу table.LoadDataRow(parts, true); // считывание одной строки из файла message = reader.ReadLine(); ) // обновление сетки dataGrid.DataSource = table; dataGrid.DataBind(); reader.Close(); } // конец метода FillMessageTable // добавление записи пользователя в гостевую книгу private void submitButton_Click( object sender, EventArgs e) { // открытие потока для добавления в файл Streamwriter guestbook = new Streamwriter(Request.PhysicalApplicationPath + "guestbook.txt", true); // запись, нового сообщения в файл guestbook.WriteLine( DateTime.Now.Date.ToString().SubstringfO, 10)+ "\t" + nameTextBox.Text + •\t" + eroailTextBox.Text + "\t" + messageTextBox.Text); // сброс информации текстовых полей и закрытие потока nameTextBox.Text = emailTextBox.Text = messageTextBox.Text = guestbook.Close(); FillMessageTable(); } // конец метода submitButton_Click // сброс информации всех текстовых полей private void clearButton_Click( object sender, System.EventArgs e) { nameTextBox.Text = emailTextBox.Text « messageTextBox.Text = } // конец метода clearButton_Click } // конец класса GuestBookForm // конец пространства имен Guestbook
ASP.NET, Web-формы и элементы управления Web 649 а б Рис. 17.24. Работа с гостевой книгой: а — ввод данных о пользователе; б — список посетителей Обработчик события для ciearButton (строки 114—121) сбрасывает информацию всех текстовых полей (TextBox) установкой их свойств Text на пустые строки. В строках 90—111 содержится код обработки события submitButton, добавляющего пользовательскую информацию в guestbook.txt — текстовый файл, сохраненный в проекте. Различные записи в этом файле, включая самую последнюю, будут отображены в объекте DataGrid. Рассмотрим этот процесс в коде. В строках 94—96 создается объект streamwriter, делающий ссылки на файл, содержащий записи гостевой кни- ги. Для извлечения пути корневого каталога приложения (это будет путь папки проекта для текущего приложе- ния) используется свойство PhysicalApplicationPath объекта Request, после чего оно связывается с именем файла (т. е. guestbook.txt). Второй аргумент (true) указывает, что в файл будет добавлена новая информация (в конец файла). В строках 99—102 нужное сообщение добавляется в гостевую книгу. До завершения своей работы обработчик события вызывает метод FillMessageTable (строка 110). Методом FillMessageTable (строки 51—87) записи гостевой книги размещаются в таблице DataTable. В стро- ках 53—57 из свойства Table объекта DataView создается объект DataTable, после чего с помощью метода Add коллекции Columns формируются необходимые столбцы. В строках 67—79 считывается каждая строка в тексто- вом файле. Метод Split разбивает каждое считывание из файла на четыре метки, добавляемые в таблицу table вызовом метода LoadDataRow (строка 75). Второй аргумент к методу LoadDataRow имеет значение true, что ука-
650 Глава 17 зывает на прием любых изменений в результате добавления. Таблица DataTable размещает в каждом столбце одну часть данных. После заполнения таблицы DataTable данные связываются с сеткой DataGrid. Для обновле- ния объекта просмотра данных DataView вызывается метод DataBind. Примечание__________________________________________________________________ Объект DataView dataView был присвоен свойству Datasource объекта DataGrid в окне Properties после его объявления в коде. Результат работы приложения представлен на рис. 17.24. 17.8. Учебный пример: подключение к базе данных в ASP.NET В данном учебном примере представлено Web-приложение, в котором пользователь может просмотреть список публикаций того или иного автора. Программа состоит из двух Web-форм. Первая запрашиваемая пользовате- лем страница — Login.aspx (листинг 17.21). После обработки этой страницы пользователи выбирают свои имена в раскрывающемся списке и вводят свои пароли. Если пароли корректны, тогда пользователи перенаправляются на страницу Authors.aspx (листинг 17.24), на которой представлен список авторов. После выбора пользователем автора и нажатия кнопки Select имеет место обратная регистрация, и на обновленной странице отображается таблица со всеми названиями книг,’ написанных выбранным автором, их номерами ISBN и издателями. 1 <%— Листинг 17.21: Login.aspx —%> 2 <%— Страница регистрации пользователя —%> 3 4 <%@ Page language="c#" Codebehind="login.aspx.cs" 5 AutoEventWireup=”false" Inherits="Database.Login" %> 6 <%@ Register TagPrefix="Header" TagName="ImageHeader" 7 Src="Imageheader.ascx" %> 8 9 <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN" > 10 <HTML> 11 <HEAD> 12 <title>WebForml</title> 13 <meta name="GENERATOR" Content="Microsoft Visual Studio 7.0"> 14 <meta name="CODE_LANGUAGE" Content="C#"> 15 <meta name="vs_defaultClientScript" content="JavaScript"> 16 <meta name="vs_targetSchema" 17 content="http://schemas.microsoft.com/intellisense/ie5"> 18 </HEAD> 19 <body MS_POSITIONING="GridLayout" bgColor="#ffebff”> 20 <form id="Forml" method="post" runat="server”> 21 <asp:label id ="nameLabel" style="Z-INDEX: 101; 22 LEFT: 15px; POSITION: absolute; TOP: 188px" 23 runat="server">Name 24 </asp:label> 25 26 <asp:label id="promptLabeI" style="Z-INDEX: 108; 27 LEFT: 15px; POSITION;absolute; TOP: 145px" 26 runat="server">Please select your name and 29 enter your password to log in: 30 </asp:label> 31 32 <asp:customvalidator id="invalidPasswordValidator" 33 style="Z-INDEX: 107; LEFT: 262px; POSITION: absolute; 34 TOP: 221px" runat="server" 35 ControlToValidate="passwordTextBox" Font-Bold="True” 36 ForeColor="DarkCyan" ErrorMessage="Invalid password!”> 37 </asp:customvalidator> 38
ASP.NET, Web-формы и элементы управления Web 651 39 <asp:requiredfieldvalidator id=,,requiredPasswordValidator" 40' style="Z-INDEX: 106; LEFT: 262px; POSITION: absolute; 41 TOP: 221px" runat="server" 42 ControlToValidate="passwordTextBox” Font-Bold="True" 43 ForeColor="DarkCyan" 44 ErrorMessage="Please enter a password!"> 45 </asp:requiredfieldvalidator> 46 47 <asp:dropdownlist id="nameList" style="Z-INDEX: 105; 48 LEFT: 92px; POSITION: absolute; TOP: 185px" 49 runat="server" Width=’’154"> 50 </asp:dropdownlist> 51 52 <asp:button id="submitButton" style="Z-INDEX: 104; 53 LEFT: 92px; POSITION: absolute; TOP: 263px" 54 runat="server" Text="Submit"> 55 </asp:button> 56 57 <asp:textbox id="passwordTextBox" style="Z-INDEX: 103; 58 LEFT: 92px; POSITION: absolute; TOP: 221px" 59 runat="server" TextMode="Password"> 60 </asp:textbox> 61 62 <asp:label id="passwordlabel" style="Z-INDEX: 102; 63 LEFT: 15px; POSITION: absolute; TOP: 220px" 64 runat="server">Password 65 </asp:label> 66 67 <Header:ImageHeader id ="ImageHeader1" runat="server"> 68 </Header:ImageHeader> 69 </form> 70 </body> 71 </HTML> Доступ к большей части информации, предоставляемой этой Web-страницей, осуществляется через сохранен- ные в проекте базы данных. Login.aspx извлекает существующие имена пользователей сайта через Login.mdb, а вся информация об авторах поступает из базы данных Books.mdb (также используемой в главе 16). В строках 6 и 7 в файл ASPX добавляется пользовательский элемент управления Web. Определения пользова- тельских элементов управления для приложений Windows представлены в главе 10\ пользовательские элементы управления для Web-форм можно определить с помощью той же методики. Из-за того что в запрашиваемых пользователями файлах ASPX пользовательские элементы управления для Web-форм не определяются, такие элементы управления не имеют элементов html или body. Программисты задают эти элементы управления с помощью директивы <%Register.. .%>. Например, программисту может потребоваться включение в каждую страницу сайта навигационной панели (т. е. набора кнопок для просмотра Web-сайта). Если сайт состоит из большого количества страниц, то добавление разметки или создание навигационной панели для каждой страни- цы может занять довольно много времени. Более того, если разработчик впоследствии будет модифицировать навигационную панель, но обновлять придется навигационные панели на каждой странице. Созданием пользо- вательского элемента управления программист может задать месторасположение навигационной панели для каждой странице, используя всего несколько строк разметки. При изменении навигационных панелей страницы, на которых они размещены, будут обновляться только при будущих запросах. Подобно Web-формам, большинство пользовательских элементов управления Web состоят из двух страниц: файла ASCX и файла фонового кода. В строках 6 и 7 определяется имя тега пользовательского элемента управ- ления (т. е. имя данного экземпляра элемента управления) и префикс тега — ImageHeader и Header соответст- венно. Элемент ImageHeader добавляется в файл в строках 67 и 68. Атрибут Src указывает на размещение фай- ла— Headerlmage.aspx (листинг 17.22). Программист может создать данный файл щелчком правой кнопкой мыши на имени проекта в окне Solution Explorer и выбором строки Add | Add New Item..., В открывающемся диалоговом окне выбирается строка Web User Control, и к программному решению добавится новый файл ASCX. На данном этапе разработчик может добавлять элементы управления и задавать любую функциональ- ность в файле фонового кода пользовательского элемента управления. После создания последнего его можно перетащить мышью из окна Solution Explorer непосредственно в открытый файл ASCX. После этого будет соз- дан и добавлен в Web-форму экземпляр данного элемента управления.
652 Глава 17 Листинг 17.22. Код ASCX Для заголовка ---------—-----------------;--___________:______......_......_ 1 <%— Листинг 17.22: ImageHeader.ascx —%> 2 <%— Листинг пользовательского элемента управления Header —%> 3 4 <%@ Control language="c#" AutoEventWireup="false" 5 Codebehind="ImageHeader.aspx.cs" 6 Inherits=”Database.ImageHeader" 7 TargetSchema=http://schemas.microsoft.com/intellisense/ie5 %> 8 9 <asp:Image id="Imagel" runat="server" ImageUrl="bug2bug.png"> 10 </asp:Image> Содержимое файла ImageHeader.ascx в режиме конструктора представлено на рис. 17.25. Рис. 17.25. Содержимое файла ASCX в режиме конструктора Форма, представленная в листинге 17.23, включает в себя несколько меток (Label), текстовое поле (TextBox с именем PasswordTextBox) и раскрывающийся список (DropDownList с именем nameList), заполняемый в файле фонового кода Login.aspx.cs именами пользователей, извлеченными из базы данных— рис. 17.26. Здесь также имеются два ревизора: RequiredFieldValidator и CustomValidator. CustomValidator позволяет задать условия, при которых значение в поле ввода будет допустимым. Данные условия задаются в обработчике события ServerValidate ревизора CustomValidator. Код обработки события, размещенный в файле фонового кода для Login.aspx.cs, рассматривается далее. Значение свойств ControlToValidate обоих ревизоров установлено на PasswordTextBox. 1 // Листинг 17.23: Login.aspx.cs 2 // Файл фонового кода для страницы регистрации пользователей 3 4 using System; 5 using System.Collections; 6 using System.ComponentModel; 7 using System.Data; 8 using System.Drawing; 9 using System.Web; 10 using System.Web.Sessionstate; 11 using System.Web.UI; 12 using System.Web.UI.WebControls; 13 using System.Web.UI.HtmlControls; 14 using System.Security; 15 16 namespace Database 17 { 18 // пользователь может зарегистрироваться 19 public class Login : System.Web.UI.Page
ASP.NET, Web-формы и элементы управления Web 653 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 protected System.Data.OleDb.OleDbDataAdapter oleDbDataAdapterl; protected System.Data.OleDb.OleDbCommand oleDbSelectCommandl; protected System.Data.OleDb.OleDbCommand oleDblnsertCommandl; protected System.Data.OleDb.OleDbCommand oleDbUpdateCommandl; protected System. Data.OleDb.OleDbCommand oleDbDeleteCommandl; protected Systern.Data.OleDb.OleDbConnect ion oleDbConnect ionl; protected Systern.Web.UI.WebControls.Label promptLabel; protected System.Web.UI.WebControls.Label nameLabel; protected System.Web.UI.WebControls.Label promptLabel; protected System.Web.UI.WebControls.DropDownLiSt namelist; protected System.Web.UI.WebControls.Button submitButton;' protected System.Web.UI.WebControls.RequiredFieldValidator requiredPasswordValidator; protected System.Web.UI.WebControls.CustomValidator invalidPasswordValidator; protected System.Web.UI.WebControls.TextBox passwordTextBox; protected System.Data.OleDb.OleDbDataReader dataReader; // обработка события Load класса Page private void Page_Load(object sender, System.EventArgs e) { // если страница загружается из-за обратной регистрации, // обработать информацию; при первой загрузке // страницы ничего не предпринимать if (!IsPostBack) { 11 открыть подключение к базе даТнных oleDbConnection.Open(); /Л выполнить запрос dataReader = oleDbDataAdapterl.SelectCommand.ExecuteReader(); // строку можно считать из результата запроса, но следует // добавить первое наименование в раскрывающийся список while (dataReader.Reader()) nameList.Items.Add(dataReader.GetString(0)) ; .// закрыть подключение к базе данных oleDbConnectionl.Close(); } } // конец Page_Load // код, сгенерированный Visual Studio .NET // подтверждение корректности имени пользователя и пароля private void invalidPasswordValidator_ServerValidate( object source, System.Web.UI.WebControls.ServerValidateEventArgs args) { // открыть подключение к базе данных oleDbConnectionl.Open();
654 Глава 17 83 // задать команду SELECT для поиска пароля имени пользователя 84 //в раскрывающемся списке 85 oleDbDataAdapterl.SelectCommand.CoiumandText= 86 "SELECT * FROM Users WHERE loginlD = + 87 Request.From[ "nameList ].Tostring() + 88 89 dataReader = 90 oleDbDataAdapterl.SelectCommand.ExecuteReader(); 91 92 dataReader.Read(); 93 94 // при корректном пароле создать мандат аутентификации 95 // для данного пользователя и перенаправить его к файлу 96 // Authors.aspx; иначе задать IsValid значение false 97 if (args.Value = dataReader.GetString(1)) 98 { 99 FormsAuthentication.SetAuthCoQkie( 100 Request.Form[ "nameList" ], false); 101 Session. Add( 102 "name", Request.Form[ "nameList" ].ToString()); 103 Response.Redirect("Authors.aspx"); 104 ) 105 else 106 args.IsValid = false; 107 108 // закрыть подключение к базе данных 109 , oleDbConnectionl.Close(); 110 111 } // конец метода invalidPasswordValidator_ServerValidate 112 113 } // конец класса Login 114 115 } // конец пространства имен Database В листинге 17.23 обработчик события Page_Load определен в строках 49—71. Если страница загружается впер- вые, тогда исполняются строки 55—70. В строках 60 и 61 выполняется запрос SQL, генерируемый Visual Studio во время проектирования; данный запрос просто извлекает все строки из таблицы Authors базы данных Books. В строках 65 и 66 происходит итерация через строки таблицы с размещением наименований в первом столбце каждой строки (имя автора) в списке имен (nameList). Читатели наверняка заметили, что в данной программе фигурирует OleDbDataReader — объект, считывающий данные из базы. Ранее объект такого типа не использовался, потому что OleDbDataReader — не настолько гибок, насколько программы считывания, рассмотренные в главе 16. Этот объект может считывать данные, но не об- новлять их. В приведенном примере объект OleDbDataReader применяется из-за необходимости простого счи- тывания имен авторов, а он выполняет этот процесс быстро и без проблем. В данном примере также используется ревизор Customvalidator для подтверждения корректности пользова- тельских паролей. В строках 76—111 определяется обработчик события ServerValidate ревизора CustomValidator, исполняющийся всякий раз при нажатии пользователем кнопки Submit. Этот обработчик со- бытия принимает параметр serverValidateEventArgs, названный args. Объект, на который ссылается args, об- ладает двумя важными свойствами: value (хранит значение элемента управления, допустимость которого под- тверждается ревизором CustomValidator) и IsValid (содержит булево значение (bool), представляющее резуль- тат проверки корректности). Если по завершении исполнения обработчика события параметр IsValid принимает значение true, то HTML-форма передается на Web-сервер; если параметр IsValid принимает значе- ние false, тогда отображается сообщение об ошибке (ErrorMessage) ревизора CustomValidator, и HTML-форма на сервер не передается. Для создания и связывания обработчика с событием ServerValidate дважды щелкните кнопкой мыши на реви- зоре CustomValidator. Определение данного обработчика события (строки 76—111) проверяет имя выбранного пользователя и введенный им пароль. Если они соответствуют существующим имени пользователя и паролю в базе данных, тогда пользователь считается аутентифицированным (authenticated), т. е. подтверждается его идентичность, и браузер перенаправляется в файл Authors.aspx (листинг 17.24). В строках 99—103 происходит аутентификация пользователя и обеспечивается его доступ к файлу Authors.aspx вызовом метода SetAuthCookie типа static класса FormsAuthentication. Данный класс размещен в пространстве имен Sys tern. Web. Security (строка 14). Метод SetAuthCookie записывает на машину клиента закодированный файл cookie (encrypted
ASP.NET, Web-формы и элементы управления Web 655 cookie), содержащий информацию, необходимую для аутентификации пользователя. Закодированные данные переводятся в код, понятный только отправителю и получателю, т. е. передаваемая информация остается кон- фиденциальной. Метод SetAuthCookie принимает два аргумента: строку, содержащую имя пользователя, и бу- лево значение, указывающее на возможную необходимость сохранения данного файла cookie на компьютере клиента по завершении текущего сеанса. В нашем случае необходимо, чтобы программа аутентифицировала пользователя только для текущего сеанса, поэтому значение bool устанавливается на false. После аутентифи- кации пользователя его браузер перенаправляется к файлу Authors.aspx. Если запрос базы данных не идентифи- цировал пользователя, тогда свойство isvalid ревизора Customvalidator устанавливается на false; в этом слу- чае программа выдает сообщение об ошибке, и пользователь может попробовать зарегистрироваться повторно. .Web Form! - Microsoft Internet f нгЯоге. Jtfew FewtM; Loots И* t-° *> J a 4 j 60 *’ http://kx.bost/Database/Login?a4i^ Bug2Bug.com Please select your name and enter your password to log m. Name |TestUser2” Password j Please enter a password? Submit ] I WebForm 1 Microsoft Internet Explorer Bug2Bug.com Please select your name and enter your password to log in: Name |TestUser2 Password | Invalid password! Submit | J___l Рис. 17.26. Работа с регистрационной формой: а — выбор имени пользователя из раскрывающегося списка; б — сообщение с просьбой ввести пароль; в — сообщение о неверном пароле В приведенном примере используется методика, называемая аутентификацией форм (forms authentication), защищающая страницу так, что доступ к ней могут получить только зарегистрированные пользователи. Аутен- тификация является крайне важным инструментом для Web-сайтов: она обеспечивает доступ ко всему сайту или его частям только зарегистрированным членам. Аутентификация и отказ в доступе незарегистрированных поль- зователей требует нескольких строк в файле Web.config (файл конфигурации программы). Файл XML — часть любого приложения ASP.NET, созданного в Visual Studio. Элемент аутентификации по умолчанию: Authentication mode="None" /> отключает аутентификацию.
656 Глава 17 Для запрета доступа к сайту незарегистрированных пользователей замените эту строку на следующее выра- жение: <authentication mode="Forms"> <forms name="DatabaseCookie" loginUrl="Login.aspx" protection="Encryption" /> </authentication> <authorization> <deny users="?" /> </authori zat ion> При такой замене значение атрибута mode в элементе authentication меняется с "None" на "Forms", что указы- вает на необходимость использования аутентификации форм. Элемент forms определяет способ, по которому подтверждаются пользовательские полномочия. Внутри элемента форм атрибут name задает имя cookie, создан- ного на машине пользователя; в данном случае имя — Databasecookie. Атрибут loginuri определяет страницу регистрации для программы; пользователи, которые хотят получить доступ к любой странице приложения без регистрации, перенаправляются на эту страницу. Атрибут protection определяет, закодировано ли значение пароля. В данном случае значение атрибута protection установлено на "Encryption” для кодировки данных элемента идентификации. Элемент authorization указывает тип доступа, который может получить тот или иной пользователь. В данном приложении необходимо, чтобы зарегистрированные пользователи получали доступ ко всем страницам сайта. Элемент deny помещается в элемент authorization для определения пользователей, доступ для которых запре- щен. Когда атрибут принимает значение "?", всем анонимным (без полномочий) пользователям доступ к сайту закрыт. После аутентификации пользователи отсылаются к странице Authors.aspx (листинг 17.24). На этой странице представлен список авторов, в котором пользователь может выбрать интересующего (рис. 17.27, а). После того как выбор сделан, в таблице отображается информация о книгах, написанных этим автором (рис. 17.27, б). Bug2Bug.com You Chose Kate Stembuhler <' *' ... . Ttf’Tj'VT. УЧс, Л„ 1. I-- -УТ-— I Ф.С ame e-Business jnd e-Commerce for La. ins =rs 0130323640 Prentice Hall e-Business and e-Commerce for Managers 0130323640 Prentice Hall PTG I Wireless Internet and Mobile Business How to Program 0130622265 Prentice Hall ; Wireless Internet and Mobile Business How to Program 0130622265 Premice Hall PTG I < Previous Next > 6 Рис. 17.27. Получение сведений о книге выбранного автора: а — выбор автора; б — вывод перечня книг, написанных автором
ASP.NET, Web-формы и элементы управления Web ; Листинг 17.24. Файл ASPX, обеспечивающий выбор автора из раскрывающегося списка 4;.......... ....... ....'JffiiKifji.,, ,,‘,йдЖ»К.&&..ЗЕЙН...аГ.*Д 657 'Я 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 <%— Листинг 17.24: Authors.aspx —%> <%— Страница дает пользователю возможность выбора автора —%> <%— и отображает книги этого автора —%> <%@ Page language="c#” Codebehind="Authors.aspx.cs" AutoEventWireup="false" Inherits="Database.Authors" %> <%@ Register TagPrefix="Header" TagName-="ImageHeader" Src="Imageheader.ascx" %> <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN" > <HTML> <HEAD> <title>Author</title> <meta name="GENERATOR" Content="Microsoft Visual Studio 7.0"> <meta name="CODE_LANGUAGE" Content="C#"> <meta name="vs_defaultCllentScript" content="JavaScript"> <meta name="vs_targetSchema" content="http://schemas.microsoft.com/intellisense/ie5"> </HEAD> <body MS POSITIONING="GridLayout" bgColor="#ffebff"> <form id="Authors" method="post" runat="server"> <asp.:DropDownLiSt id ="nameList" style="Z-INDEX: 103; LEFT: 90px; POSITION: absolute; TOP: 157px" runat="server" Width="158px" Height="22px"> </asp:DropDownList> <Header:ImageHeader id="Headl" runat="server"> </Header:ImageHeader> <asp:Label id ="Label2" style="Z-INDEX: 102; LEFT: 28px; POSITION: absolute; TOP: 157px" runat="server" Width="48px" Height="22px">Authors: </asp:Label> <asp:Button id ="Buttonl" style="Z-INDEX: 104; LEFT: 29px; POSITION: absolute; TOP: 188px" runat="server" Width="78px" Text="Select"> </asp:Button> <asp:Label id ="Label3" style="Z-INDEX: 105; LEFT: 19px; POSITION: absolute; TOP: 127px" runat="server" Width="210px" Visible="False">You chose </asp:Label> <asp:DataGrid id ="dataGrid" style="Z-INDEX: 106; LEFT: 28px; POSITION: absolute; TOP: 151px" runat="server" Height="23px” Width="700px" ForeColor="Black" AllowPaging="True" DataSource="<%# dataViewl %>" Visible="False" AllowSorting='True"> <EditItemStyle BackColor="White"x/EditItemStyle> <AlternatingItemStyle FireColor="Black" BackColor="LightGoldenrodYellow"> </AlternatingItemStyle> <ItemStyie BackColor="White"x/ItemStyle> <HeaderStyle BackColor=”LightGreen"x/HeaderStyle> 42 Зак 3333
658 Глава 17 61 <PagerStyle NextPageText="Next &amp;gt;" 62 PrevPageText=”&anp;It; Previous"> 63 </PageStyle> 64 </asp:DataGrid> 65 </form> 66 </body> 67 </HTML> Файл ASPX (листинг 17.24) для данной страницы создает несколько элементов управления: раскрывающийся список (DropDownList), три метки (Label), кнопку (Button) и сетку данных (DataGrid). Обратите внимание, что некоторые из этих элементов управления— одна метка (Label) и сетка (DataGrid)— обладают свойствами visible со значениями false (строки 41 и 48). Это означает, что при первой загрузке страницы данные элемен- ты не видны: пользователь еще не выбрал автора, поэтому информация для отображения отсутствует. Пользова- тели выбирают автора из раскрывающегося списка DropDownList и нажимают кнопку Submit, в результате чего имеет место обратная регистрация. После ее обработки сетка данных DataGrid заполняется и отображается. В листинге 17.25 представлен файл фонового кода для файла ASPX. 1 // Листинг 17.25: Authors.aspx.cs 2 // Файл фонового кода для страницы, позволяющей пользователям 3 // выбирать автора и просматривать список его книг 4 5 using System; 6 using System.Collections; 7 using System.ComponentModel; 8 using System.Data; 9 using System.Drawing; 10 using System.Web; 11 using System.Web.Sessionstate; 12 using System.Web.UI; 13 using System.Web.UI.WebControls; 14 using System.Web.UI.HtmlControls; 15 16 namespace Database 17 { 18 // пользователь выбирает автора и отображает список его книг 19 public class Authors : System.Web.UI.Page 20 { 21 protected System.Web.Ul.WebControls.DropDownList nameList; 22 protected System.Web.Ul.WebControls.Label choseLabel; 23 protected System.Web.UI.WebControls.Button selectButton; 24 protected System.Web.Ul.WebControls.Label authorsLabel; 25 protected System.Web.Ul.WebControls.DataGrid dataGrid; 26 27 protected System.Data.OleDb.OleDbDataAdapter 28 oleDbDataAdapterl; 29 protected System.Data.OleDb.OleDbConnection 30 oleDbSelectConnectionl; 31 protected System.Data.OleDb.OleDbDataReader dataReader; 32 33 protected System.Data.OleDb.OleDbCommand 34 oleDbSelectCommandl; 35 protected System.Data.OleDb.OleDbCommand 3 6 oleDblnsertCommandl; 37 protected System.Data.OleDb.OleDbConnection 38 oleDbUpdateCommandl; 39 protected System.Data.OleDb.OleDbConnection 40 oleDbDeleteCommandl; 41 42 protected System.Data.DataTable dataTablel = 4 3 new DataTable(); 44 protected System.Data.DataView dataViewl 45
&SP.NET, Web-формы и элементы управления Web 659 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68' 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 protected static string sirtString = "Title"; // при загрузке страницы private void Page_Load(object sender, System.EventArgs e) { // возможно ли загрузить страницу из-за обратной регистрации? if (!IsPostBack) { // открытие подключения к базе данных try { oleDbConnectionl.Open(); // исполнение запроса dataReader = oleDbDataAdapterl.SelectCommand.ExecuteReader(); // можно считать строку из результата запроса; // следует добавить первый элемент в раскрывающийся список while (dataReader.read()) nameList.Items.Add(dataReader.GetString(0) + "" + dataReader.GetString(l)); ) 11 если база данных не найдена catch(System.Data.QleDb.OleDbException) { authorsLabel.Text = "Server Error: Unable to load database!"; } // закрытие подключения к базе данных finally { oleDbConnectionl.Close(); } } else { // сокрытие элементов управления nameList.Visible = false selectButton.Visible = false choseLabel.Visible = false // установка видимости других элементов authorsLabel.Visible = true; dataGrid.Visible = true; // добавление к метке имени автора authorsLabel = "You Chose " + nameList.Selectedltem + int authorlD = nameList.Selectedlndex + 1; try { // открытие подключения к базе данных oleDbConnectionl.Open(); // выборка названия, ISBN и издателя каждой книги oleDbDataAdapterl.SelectCommand.Commandtext = "SELECT Titles.Title, Titles.ISBN, " + "Publishers.PublisherName FROM AuthoriSBN " + "INNER JQIN Titles ON AuthoriSBN.ISBN = " +
660 Глава 17 109 110 111 112 113 114 115- 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 } "Titles.ISBN, Publishers WHERE ” + "(AuthoriSBN.AuthorlD = ” + authorlD + /,/ заполнение dataView результатами oleDbDataAdapterl.Fill(dataTablel); dataViewl = new DataView(dataTablel); dataViewl.Sort = sortstring; dataGrid.DataBind(); // привязка сетки к источнику данных } // если база данных не найдена catch (System.Data.OleDb.OleDbException) { authorsLabel.Text = "Server Error: Unable to load database!"; } // закрытие подключения к базе данных finally { oleDbConnectionl.Close(); ) ) } // конец метода Page Load //на новой странице private void OnNewPage(object sender, DataGridPageChangedEventArgs e) { // установка текущей страницы на следующую страницу dataGrid.CurrentPagelndex = e.NewPageIndex; dataViewl.Sort = sortstring; dataGrid.DataBindO; // повторное связывание данных } // конец метода OnNewPage // код, сгенерированный Visual Studio .NET // обработка события Sort private void dataGrid_SortCornmand(object source, System.Web.UI.WebControls.DataGridSortCommandEventArgs e) { // получение таблицы для сортировки sortstring = e.SortExpression.ToStringO; dataViewl.Sort = sortStrxng; // сортировка dataGrid.DataBind(); // повторное связывание данных } // конец метода dataGrid_SortCommand } // конец класса Authors // конец пространства имен Database В данном примере большая часть кода относится к методу Page Load (строки 49—133). Условие (строка 52) определяет, была ли страница загружена в результате события обратной регистрации. После этого в строке 57 открывается подключение к базе данных, в строках 60 и 61 исполняется команда базы данных, извлекающая имена и фамилии всех авторов из базы данных. Строки 65—67 итерируются через результирующее множество и добавляют имена и фамилии авторов в объект nameList. После того как пользователь выберет автора и представит форму, условие (строка 52) принимает значение false, что обуславливает выполнение строк 86—131. Первоначальный набор элементов управления, отобра-
ASP.NET, Web-формы и элементы управления Web . 661 женный для пользователя (т. е. метка, раскрывающийся список и кнопка) скрывается при обратной регистрации. Однако ранее скрытые метка и сетка данных становятся видимыми. Строки 95 и 96 добавляют имя выбранного автора в элемент управления ярлыка. В строках 105—ПО создается запрос базы данных на извлечение названия, номера ISBN и издателя каждой книги выбранного автора, которые присваиваются свойству CommandText команды. Методом fill (строка 113) аргумент DataTable заполняется строками, возвращенными сделанным в строках 106—ПО запросом. С помощью свойства Sort класса DataView данные сортируются по строке, присвоенной классом sortstring (строка 115). В строке 46 строка изначально принимает значение "Title", указывая на то, что строки в таблице будут отсортированы в возрастающем порядке по названиям книг. Сортировка в восходящем порядке — уста- новка по умолчанию. Если бы объект sortstring имел значение "TitleDESC", тогда строки в таблице сортиро- вались бы по названиям в убывающем порядке. Метод OneNewPage (строки 136—145) управляет событием PagelndexChanged класса DataGrid, которое запуска- ется при нажатии пользователем на ссылку Next в нижней части элемента управления DataGrid для отображе- ния следующей страницы данных. Для активизации постраничной загрузки свойство AllowPaging класса DataGrid устанавливается в конструкторе Web-форм на значение true. Свойство PageSize класса DataGrid оп- ределяет количество записей на каждой странице, а его свойство Pagerstyle настраивает отображение сетки данных во время "пролистывания". Элемент управления DataGrid отображает десять наименований книг на од- ной странице. После присвоения аргумента события NewPagelndex (строка 140) свойству CurrentPagelndex класса DataGrid данные сортируются и повторно связываются (компонуются) так, чтобы появилась возмож- ность отображения следующей страницы данных (строки 142 и 143). Такая методика отображения данных по- вышает удобочитаемость сайта и обеспечивает более оперативную загрузку каждой последующей страницы (из- за того, что за один раз отображается меньшее количество данных). Метод dataGridjSortCommand (строки 150—158) управляет событием Sort элемента управления DataGrid. При активизированном свойстве AllowSorting в конструкторе Web-форм DataGrid отображает все заголовки табли- цы как элементы управления LinkButton (т. е. как кнопки, выполняющие роль гиперссылок). Событие Sortcommand вызывается, когда пользователь щелкает кнопкой мыши на заголовке столбца. В строке 154 ис- пользуется свойство SortExpression е. Данное свойство указывает на столбец, по которому сортируются дан- ные. Это значение присваивается строке sortstring, которая потом (в строке 155) присваивается свойству Sort класса DataView. В строке 156 отсортированные данные повторно привязываются к сетке DataGrid. 17.9. Трассировка Для отладки Web-приложений ASP.NET предоставляет функцию трассировки (tracing). Трассировка— это процесс размещения операторов в файле фонового кода, выдающих информацию о состоянии программы во время ее исполнения. В Windows-приложениях в процессе отладки могут быть полезными окна сообщений; при работе с Web- формами для этой цели можно пользоваться объектом Response.Write. Однако использование Response.Write для трассировки имеет несколько недостатков. Одним из недостатков является то, что при корректном исполнении приложения разработчик должен удалять из программы все операторы трассировки. Этот процесс занимает достаточно много времени и чреват ошибками, потому что программист должен отличать операторы, являющиеся частью логики программы, от операторов, предназначенных для целей тестирования. ASP.NET обеспечивает разработчиков двумя формами встроенного процесса трассировки: трассировкой страниц и трассировкой приложения. Трассировка страниц включает в себя трассировку операций отдельной страницы. Установка свойства Trace страницы в значение true в окне Properties обеспечивает трассировку данной страницы. Вместо обращения к методу Write объекта Response вызывается метод write объекта Trace. Объект Trace — это экземпляр класса Tracecontext, обеспечивающий возможности трассировки. В дополнение к методу Write объект Trace включает в себя метод warn, выводящий красным цветом предупреждающие сообщения. При отключении трассировки присвоением свойству Trace значения false операторы Trace не исполняются. На рис. 17:28 показана простая страница, отображающая предложение (код для этой страницы не приводится, поскольку он довольно простой). Событие Page Load для данной страницы включает в себя оператор Trace.warn ("Using warnings"). Свойство Trace имеет значение false, поэтому текст "Using warnings" на стра- нице не отображается; далее будет показано, когда и где отображаются операторы трассировки. На рис. 17.29 показана та же страница со свойством Trace, имеющим значение true. В верхней части рисунка представлена первоначальная страница, а информация трассировки, сгенерированная ASP.NET, представлена ниже. Раздел Request Details предоставляет информацию о запросе. Раздел Trace Information содержит выход-
662 Глава 17 ную информацию после обращения к методам write и Warn. Вторая строка содержит сообщение, отображаемое красным цветом. Рис. 17.28. Страница ASPX с отключенной функцией трассировки Рис. 17.29. Активизированная трассировка на странице В разделе Control Tree перечислены все элементы управления, содержащиеся на странице. Здесь же появляют- ся несколько дополнительных таблиц. В разделе Cookies Section содержится информация о файлах cookies про- граммы, а в разделе Headers Collection — каталог заголовков HTTP для страницы. В разделе Server Variables представлен список переменных сервера (т. е. информация, отправляемая браузером с каждым запросом) и их значения. Трассировка целых проектов также доступна. Для активизации трассировки на уровне приложения откройте файл Web.config проекта и задайте свойству enabled значение true в элементе trace. Для просмотра информа- ции трассировки проекта перейдите в браузере к файлу trace.axd в папке проекта. На жестком диске этого файла фактически не существует; он создается при запросе пользователем trace.axd. На рис. 17.30 показана Web- страница, сгенерированная, когда программист просматривает файл trace.axd. На данной странице приведен список всех запросов данного приложения, а также время доступа к страницам. При нажатии на одну из ссылок View Details браузер переходит к странице, похожей на ту, что представлена на рис. 17.30. В данной главе рассматривалась технология ASP.NET с языком C# и ее роль в трехзвенной архитектуре. Теперь читатели должны уметь создавать динамические Web-формы, реагирующие на вводимые пользователем дан- ные, отслеживать и поддерживать информацию пользовательского сеанса и взаимодействовать с базой данных из Web-формы. В следующей главе описываются Web-службы ASP.NET, обеспечивающие взаимодействие уда- ленных программных средств методами и объектами на Web-сервере.
ASP.NET, Web-формы и элементы управления Web 663 Рис. 17.30. Информация трассировки проекта 17.10. Резюме Технология ASP.NET корпорации Microsoft применяется при разработке программных Web-приложений. Web- приложения создают Web-содержимое для Web-клиентов, использующих файлы Web-форм. Файл Web-формы представляет собой Web-страницу, отправляемую на браузер клиента. Файлы Web-форм имеют расширение aspx и содержат GUI проектируемой страницы. Программисты настраивают Web-формы добавлением элемен- тов управления Web, в число которых входят текстовые поля, графические изображения и кнопки Каждый файл ASPX, созданный в Visual Studio .NET, имеет соответствующий ему класс, размещенный в файле фоново- го кода, обеспечивающем программную реализацию файла ASPX. При перетягивании мышью элементов управления на Web-форму Visual Studio .NET создает разметку на стра- нице ASPX. Когда атрибут runat элемента управления установлен на " server", то элемент управления исполня- ется на сервере с созданием эквивалентной разметки HTML. Префикс тега asp: в объявлении элемента управ- ления указывает на то, что последний принадлежит к ASP.NET. Каждый элемент управления Web отображается на соответствующий элемент HTML. Класс Раде определяет стандартную Web-страницу с предоставлением необходимых для создания Web- приложений обработчиков событий и объектов. Все фоновые классы для форм ASPX наследуют из класса Раде. При запросе клиентом файла ASPX создается скрытый класс, содержащий как визуальный аспект страницы (определенный в файле ASPX), так и логику страницы (определенную в файле фонового кода). Этот новый класс наследуется от класса Раде. При первом запросе Web-страницы данный класс компилируется, и создается экземпляр, представляющий страницу; этот экземпляр создаст HTML-код, отправляемый клиенту. Метод onlnit вызывается при событии init. Данное событие указывает, что страница готова к инициализации. Событие Load имеет место при каждом запросе или загрузке страницы. Обработчик события Page Load обычно используется для выполнения любой обработки, необходимой для восстановления данных из прошлых запро- сов. По окончании исполнения Page Load страница обрабатывает любые события, генерируемые элементами управления страницы. Свойство PageLayout определяет расположение элементов управления на форме. По умолчанию свойство PageLayout установлено на значение GridLayout, указывающее на то, что все элементы управления располага- ются в тех местах Web-формы, на которые они были отбуксированы мышью. С помощью элементов управления image в Web-форму вставляются графические изображения. Элемент TextBox позволяет программисту считывать и отображать текст. Элемент RadioButtonList предоставляет набор пере- ключателей для выбора. Элемент DropDownList предлагает список опций. Элемент HyperLink добавляет на стра- ницу гиперссылки. Элемент управления AdRotator используется для отображения рекламных баннеров и объяв- лений. Контролирующий элемент управления — ревизор (validator) проверяет корректность формата данных в другом элементе управления Web. Ревизор RegularExpressionValidator сравнивает содержимое элемента управления с регулярным выражением. Регулярное выражение, подтверждающее корректность введенных данных, присваи- вается свойству VaiidationExpression. Свойство ControlToValidate указывает элемент управления, коррект- ность которого будет проверяться. Ревизор RequiredFieldValidator используется для подтверждения получе- ния элементов данных ввода от пользователя после отправки формы. CustomValidator позволяет программи-
664 Глава 17 стам задавать обстоятельства (условия), при которых значение того или иного поля будет допустимым. Эти об- стоятельства задаются в обработчике события ServerValidate ревизора Customvalidator. Web-программисты, работающие с ASP.NET, часто проектируют страницы так, что после предоставления дан- ных текущая страница запрашивается повторно. Такое событие называется обратной регистрацией. Свойство IsPostBack класса Раде можно использовать для определения загрузки страницы в результате обратной регист- рации. Атрибут Enableviewstate определяет сохранение состояния элемента управления при возникновении обратной регистрации. Любой сеанс представляет уникального клиента. Если клиент выходит с сайта и позднее возвращается, то он должен распознаваться как один и тот же пользователь. Для этого каждый клиент должен идентифицироваться на сервере. Процесс отслеживания отдельных клиентов называется контролем сеансов. Файл cookie — это текстовый файл, сохраняемый Web-сайтом на компьютере пользователя, для отслеживания его действий дна сайте. При получении Wcb-формой запроса заголовок включает в себя данную информацию как тип запроса и файл cookie, отправленный ранее с сервера для сохранения на машине клиента. При формули- ровании сервером ответа на запрос в информацию заголовка входят любые файлы cookies, которые сервер предполагает сохранить на машине клиента, а также другая информация, например, тип MIME ответа. Объект файла cookie имеет тип HttpCookie. Файлы cookies отправляются и принимаются в форме коллекции типа HttpCookieCollection. Свойства Name и Value класса HttpCookie можно использовать для извлечения клю- ча и значения, соответственно, пары ''ключ/значение" в файле cookie. Язык C# предоставляет возможности отслеживания сеансов в классе HttpSessionState библиотеки классов (FCL). Каждая Web-форма включает в себя объект HttpSessionState, доступ к которому осуществляется через свойство Session класса Раде. При запросе Web-страницы создается объект HttpSessionState и присваивается свойству Session класса Раде. Web-формы должны поддерживать информацию о состоянии клиентов в объектах HttpSessionState, потому что такие объекты могут сохранять пары "имя/значение". Объекты HttpSessionState могут сохранять в качест- ве значений атрибутов любой тип объекта (не только strings). Значение в паре "ключ/значение" извлекается из объекта Session путем индексирования объекта Session именем ключа с помощью того же процесса, по кото- рому из хэш-таблицы можно извлечь значение. Свойство Keys класса HttpSessionState возвращает коллекцию, содержащую все ключи в сеансе. После успешной идентификации пользователя он получает право на доступ к странице. Методом SetAuthCookie на машину клиента записывается закодированный пароль идентификации, содержащий информацию, необхо- димую для аутентификации пользователя. Закодированные (зашифрованные) данные — это данные, переведен- ные в код, понятный только отправителю и адресату; поэтому он является конфиденциальным. Методика, называемая аутентификацией форм, защищает страницу так, что доступ к ней могут получить только зарегистрированные пользователи. Для того чтобы задать на странице проверку аутентификации с отказом в доступе неавторизованным пользователям, в файл конфигурации приложения Web.config необходимо ввести несколько дополнительных строк. Технология ASP.NET предоставляет функцию трассировки для отладки программ на базе Web. Трассировка — это процесс размещения операторов в файле фонового кода, которые будут передавать информацию о состоя- нии программы во время ее выполнения. ASP.NET обеспечивает разработчиков двумя формами встроенного процесса трассировки: трассировкой страниц и трассировкой приложения. Трассировка страниц заключается в отслеживании операций на отдельной странице. Трассировка на уровне приложения позволяет разработчику отслеживать весь проект, а не только одну страницу ASPX. При отключении трассировки присвоением свойству Trace значения false операторы Trace не исполняются. 17.11. Ресурсы Интернета и WWW О www.asp.net Сайт корпорации Microsoft с обзором ASP.NET и ссылкой для загрузки ASR.NET. В сайт включен пример витрины виртуального магазина электронной торговли IBuy Spy, в котором используется ASP.NET. Также включены ссылки на сайты Amazon и Barnes & Noble, на которых посетители могут приобрести книги по те- ме ASP.NET. □ www.aspl01.com/aspdotnet/aspplus/index.asp На сайте представлен обзор ASP.NET со статьями, примерами кодов и ссылками на ресурсы ASP.NET. При- меры кодов демонстрируют использование идентификационных паролей в приложении ASP.NET с описани- ем организации подключения к базе данных: двух ключевых возможностей многозвенных приложений.
ASP.NET, Web-формы и элементы управления Web 665 □ www.411asp.net Данный ресурс предоставляет программистам учебные материалы по ASP.NET и примеры кодов. На стра- ницах сообщества пользователей ASP.NET разработчики могут задавать вопросы, давать на них ответы и обмениваться сообщениями. □ www.aspfree.com На сайте представлены демонстрационные материалы ASP.NET и исходный код. Здесь также приведен спи- сок статей по различным темам и страница вопросов и ответов. □ www.aspng.com/aspng/index.asp Учебные материалы, ссылки и рекомендуемая литература по ASP.NET. Имеются ссылки на списки рассыл- ки, организованные по темам. На сайте есть статьи, посвященные многим темам, связанным с ASP.NET, на- пример, "Performance Tips and Tricks" ("Советы по повышению производительности"). □ www.aspnetfaq.com Ответы на наиболее часто встречающиеся вопросы об ASP.NET. □ www.123aspx.com На сайте представлен каталог ссылок на ресурсы ASP.NET, а также ежедневные и еженедельные информа- ционные бюллетени. I
ГЛАВА 18 ASP.NET и Web-службы Клиент для меня — всего лишь элемент, фактор проблемы. Сэр Артур Конан-Дойл ...радуйтесь, если вам понятны простейшие явления природы: значит, душа еще жива. Элеонора Дузе Протокол — это все Франсуаза Джулиани Они рабы того, кто лишь стоит и ждет. Джон Мильтон Темы данной главы. О понятие Web-службы; □ создание Web-служб; □ элементы, входящие в Web-службу, например, описания служб и файлы обнаружения; □ создание клиента, использующего Web-службу; □ возможность использования Web-служб с приложениями Windows и Web; □ понятие контроля сеансов в Web-службах; О обмен пользовательскими типами данных между Web-службами и Web-клиентами. 18.1. Введение Для упрощения возможности многократного использования программных средств и модульного принципа их организации— столпов качественного объектно-ориентированного программирования— в предшествующих главах книги создавались динамически подсоединяемые библиотеки (Dynamic Link Library, DLL). Однако ис- пользование DLL ограничено тем, что они должны быть размещены на той же машине, что и использующая их программа. В данной главе описывается применение Web-служб или Web-сервисов1 (иногда они называются Web-службами XML или Web-сервисами ХМЕ) для повышения возможностей многократного использования программных средств в распределенных системах. Технологии распределенных систем дают программам воз- можность работать на нескольких компьютерах в сети. Web-служба — это программное приложение, обеспечи- вающее распределенную обработку данных, позволяющее одному компьютеру обращаться к методам других компьютеров через общие форматы данных и протоколы, например, XML и HTTP. В архитектуре .NET такие обращения к методам реализуются с помощью протокола SOAP (Simple Object Access Protocol, простой прото- кол доступа к объектам) — протокола на базе XML, описывающего разметку запросов и ответов на них так, что становится возможной их передача по таким протоколам, как HTTP. С помощью SOAP программы представ- ляют и передают данные в стандартизированном формате <— XML. Клиентам, пользующимся Web-службой, не обязательно знать ее базовую реализацию. Корпорация Microsoft поддерживает распространителей программных средств и компании электронного бизне- са в их желании расширять спектр Web-служб. По мере того, как все больше людей во всем мире подключается к Интернету через сети, программные приложения, способные обращаться к методам по сети, становятся все более практичными. В предыдущих главах авторы рассматривали преимущества объектно-ориентированного программирования. Web-службы являются следующим шагом объектно-ориентированного программирования; 1 Для создания Web-службы в Visual Studio необходим запуск Internet Information Services (IIS).
ASP.NET и Web-службы 667 вместо того, чтобы разрабатывать приложения из небольшого числа библиотек классов, представленных в од- ном месте, программисты получают доступ к бесчисленным библиотекам, размещенным в разных местах. Данная технология также облегчает взаимодействие компаний, их совместный рост и расширение. Подписыва- ясь на Web-службы, имеющие непосредственное отношение к их роду деятельности, компании-производители программных средств тратят меньше времени на написание кодов, уделяя больше времени разработке новых продуктов из существующих компонентов. Кроме того, компании электронной торговли могут использовать Web-службы для предоставления своим клиентам гораздо больших возможностей приобретения нужных им товаров. В качестве простого примера можно упомянуть виртуальный музыкальный магазин, где можно купить диски или получить информацию о музыкантах. А представьте себе, что компания-распространитель билетов на различные концерты имеет Web-службу, определяющую даты концертов разных исполнителей и дающую поль- зователям возможность приобретения на них билетов. Разместив на своем сайте соответствующую Web-службу, виртуальный музыкальный магазин сможет продавать билеты на концерты, что, скорее всего, отразится на ак- тивности посещения пользователями данного сайта. Компания-распространитель билетов также выигрывает в плане делового взаимодействия. Помимо продажи большего количества билетов, она будет получать прибыль от музыкального магазина в обмен на использование его Web-службы. Visual Studio NET и .NET Framework обеспечивают простые методы создания Web-служб, подобных описанной в приведенном выше примере. В настоящей главе рассматриваются шаги по созданию Web-служб и доступу к ним. Для каждого примера представлен код Web-службы и приведен пример программного приложения, кото- рое может ее использовать. Первые примеры рассчитаны на краткое вбедение в понятие Web-служб и их работу в Visual Studio. В последующих разделах будут представлены более сложные Web-службы. 18.2. Web-службы Web-служба (или Web-сервис) — это программное приложение, хранящееся на одной машине, доступ к кото- рому может быть осуществлен с другой машины в сети. Из-за самой природы такого взаимоотношения компью- тер, на котором размещена Web-служба, обычно называется удаленной машиной (remote machine). Приложение, - осуществляющее доступ к Web-службе, посылает обращение к методу на удаленную машину, которая обраба- тывает запрос и отправляет приложению ответ. От такого типа распределенной обработки данных выигрывают многие системы, включая не имеющие доступа к определенным данным, и системы, обладающие недостаточ- ной мощностью для выполнения особых расчетов. В своей простейшей форме Web-служба — это класс. В предыдущих главах, когда в проект нужно было вклю- чить класс, то он либо объявлялся в самом проекте, либо на него добавлялась ссылка в компилируемую DLL. Скомпилированная DLL по умолчанию размещена в каталоге bin программного приложения. В результате все части приложения оказываются размещенными на одной машине. При использовании Web-служб класс (и его скомпилированная DLL), который нужно ввести в проект, сохранены на удаленной машине: скомпилированная версия данного класса не размещается в текущем приложении. Методы Web-службы активизируются удаленно с помощью удаленного вызова процедуры (Remote Procedure Call, RPC). Этй методы, отмеченные атрибутом WebMethod, часто называются методами Web-службы. Объявле- ние метода с таким атрибутом делает метод доступным для других классов посредством RPC. Объявление ме- тода Web-службы с атрибутом WebMethod называется раскрытием (exposing) метода (т. е. его можно вызывать удаленно). Распространенная ошибка программирования_______________________________________________ Попытка вызова удаленного метода в Web-службе, если метод не объявлен в атрибуте WebMethod, является ошибкой компилирования. Большая часть запросов Web-служб и ответов от них передается по протоколу SOAP. Это значит, что любой клиент, способный создавать и обрабатывать сообщения SOAP, может пользоваться Web-службой, независимо от языка, на котором она написана. Web-службы приводят к важным последствиям при взаимоотношениях между компаниями (business-to- business, В2В) Теперь, вместо использования патентованных приложений, компании могут осуществлять тран- закции между собой посредством Web-служб — более простого и эффективного средства ведения дел. Благода- ря тому, что Web-службы и SOAP независимы от платформ, компании могут взаимодействовать между собой и пользоваться Web-службами, не беспокоясь о совместимости различных технологий или языков программиро- вания. Таким образом, Web-службы представляют собой сравнительно недорогое, готовое решение, упрощаю- щее транзакции В2В. Web-служба, созданная в Visual Studio .NET, состоит из двух частей: файла ASMX и файла фонового кода. По умолчанию файл ASMX можно просмотреть в любом Web-браузере; он содержит ценную информацию о Web- службе, например, описание ее методов и способов тестирования этих методов. Файл фонового кода обеспечи-
668 Глава 18 вает реализацию методов, объединенных в Web-службе. На рис. 18.1 показано окно Internet Explorer с отобра- женным файлом ASMX. Ссылки на методы Web-службы Ссылка на описание службы Рис. 18.1. Файл ASMX, визуализированный в Internet Explorer В верхней части страницы представлена ссылка на раздел Service Description Web-службы. Описание служ- бы — это документ XML, соответствующий языку описания Web-службы (Web Service Description Language, WSDL) — словарю XML, в котором определены методы, доступные в Web-службе, и способы, которыми кли- енты Motyr взаимодействовать с этими службами. Документ WSDL также задает информацию нижнего уровня, которая может потребоваться клиенту, например, необходимые форматы запросов и ответов на них. Visual Studio .NET генерирует описание службы на языке WSDL. Клиентские программы могут использовать описание служб для подтверждения корректности обращения к методам при компилировании клиентских программ. Программисту не следует изменять описание службы, поскольку в нем представлена работа этой службы. При щелчке пользователем кнопкой мыши на ссылке Service Description в верхней части страницы ASMX отобра- жается WSDL, определяющий описание данной службы (рис. 18.2). 3 http- localhost Huqelnteger Hugetntener esnw. WSDL Microsoft Internet t «jdbna <?xml versions"!.О" encodlng="utf-8" ?> - «definitions xmlns ;http="http://schemas.xmlsoap.org/wsdl/http/" xmlns: soap="http://schemas. xmlsoap.org/wsdl/soap/" xmlns:s="http://www.w3.org/2001/XMLSchema" xmins:sO="http://www.deitel.com/csphtpl/ch21/" xm!ns:soapenc="http://schemas.xmlsoap.org/soap/encoding/" xmlns: tm="http://microsoft.com/wsdl/mime/textMatching/“ xmins:mime="http;//schemas.xmlsoap.org/wsdl/mime/" targetNamespace="http://www.deitel.com/csphtpl/ch21/" xmlns="http://schemas.xmlsoap.org/wsdl/"> - <types> - <s:schema elementFormDefault="qualified" targetNamespace="http://www.deitel.com/csphtp l/ch21/"> - <s:element name="Add"> - <s:complexType> - <s:sequence> <s:element min0ccurs="0" maxOccurs="l" name="first" type="s:string" /> <s:element minOccurs="0" maxOccurs="l" name="second" type="s:string" /> </s:sequence> </s: complexType> </s:element> - <s:element name="AddResponse"> - <s:complexType> . : - <s:sequence> <s:element mlnOccurs="0" maxOccurs="l" u. Рис. 18.2. Описание Web-службы
ASP.NET и Web-службы 669 Под ссылкой Service Description на странице, показанной на рис. 18.1, перечислены методы, имеющиеся в Web- службе (т. е. все методы в приложении, объявленные с атрибутами WebMethod). При щелчке кнопкой мыши на имени любого метода осуществляется запрос тестовой страницы, описывающей этот метод (рис. 18.3). После пояснения аргументов метода тестовая страница дает пользователям возможность тестирования метода вводом нужных параметров и нажатием кнопки Invoke. (Процесс тестирования Web-службы описан далее.) Под кноп- кой Invoke на странице отображены примерные сообщения запроса и ответа на него с помощью SOAP, HTTP GET и HTTP POST. Эти протоколы являются тремя опциями отправки и получения сообщений в Web-службах. Протокол, используемый для передачи сообщений запросов и ответов, иногда называется "проводным протоко- лом" (wire protocol) или "проводным форматом" (wire format), потому что данный протокол задает передачу информации "по проводам". Обратите внимание, что на рис. 18.3 для тестирования метода применяется прото- кол HTTP GET. Далее в главе с помощью Web-служб в программах C# в качестве проводного протокола будет применяться SOAP. Преимущества использования SOAP по сравнению с HTTP POST рассматриваются в сле- дующем разделе. На странице, показанной на рис. 18.3, пользователи могут протестировать метод вводом значения (Value) в по- ля first: и second: с последующим нажатием кнопки Invoke (в данном примере протестирован метод Bigger). Данный метод исполняется, и открывается новое окно браузера для показа документа XML, в котором содер- жится результат (рис. 18.4). Теперь, когда приведен простой пример использования Web-службы, в нескольких следующих разделах исследуется роль XML в Web-службах, а также другие аспекты функциональности Web- служб. Hugelnteger Click here for a complete list of operations. Bigger I Determines whether the first integer is larger than the second integer. Test To test the operation using the HTTP GET protocol, dick the 'Invoke' button. Parame^r Value • yj «... ' • .. .ж .. e first: |34325543436554767 ' ., second {432422222242424344 ~~~ ' | Invoke SOAP fl The following is a sample SOAP request and response. The placeholders shown need to be replaced with actual values. POST /Hugelnteger/Hugelnteger. asms HTTP/1-1 Host: localhost s. ' ,n • V Content-Type: text/xmlf charset=ut€-8 * <1....., , . ,. T -- ? . -ja * Рис. 18.3. Активизация метода Web-службы с Web-браузера Рис. 18.4. Результаты активизации метода Web-службы с Web-браузера
670 Глава 18 Совет по тестированию и отладке ______________________________________________________ Применение страницы ASMX Web-службы для тестирования и отладки методов делает службу более надежной и устойчивой; это также снижает вероятность, что клиенты столкнутся с ошибкой при работе с Web-службой. 18.3. Протокол SOAP и Web-службы Протокол SOAP (Simple Object Access Protocol, простой протокол доступа к объектам) — это независимый от платформы протокол, использующий XML для выполнения удаленных процедурных обращений по HTTP. Каж- дый запрос и ответ упакованы в сообщение SOAP — сообщение XML, содержащее всю информацию, необхо- димую для обработки его содержимого. Сообщения SOAP достаточно популярны, потому что они пишутся на простом для понимания и не зависимом от платформы языке XML. Аналогичным же образом для передачи со- общений SOAP был выбран протокол HTTP, потому что это — стандартный протокол для передачи информа- ции по Интернету. Использование XML и HTTP позволяет различным операционным системам передавать и получать сообщения SOAP. Другим преимуществом протокола HTTP является то, что его можно использовать в сетях, содержащих брандмауэры — специальные защитные барьеры, ограничивающие межсетевое взаимодей- ствие. Протокол SOAP поддерживает широкий диапазон типов данных. Читатели, возможно, заметили, что проводной формат, используемый для передачи запросов и ответов, должен поддерживать все типы данных, передаваемых программными приложениями. Web-службы, использующие SOAP, поддерживают еще большее количество типов данных, чем Web-службы, в которых применяются другие проводные форматы. В число типов данных, поддерживаемых SOAP, входит большинство базовых типов данных, а также DataSet, DataTime, XmlNode и не- сколько других. SOAP также обеспечивает передачу массивов всех этих типов. Кроме того, можно использовать специальные пользовательские типы (этот процесс описан в разд. 18.8). Программные приложения отправляют запросы Web-служб и получают ответы на них посредством протокола SOAP. Когда программа активизирует метод Web-службы, запрос и вся относящаяся к нему информация соби- рается в сообщение SOAP и отправляется по назначению. Когда Web-служба получает сообщение SOAP, она начинает обрабатывать его содержимое (называемое конвертом SOAP), указывающее нужный клиенту метод и аргументы, передаваемые клиентом в этот метод. После получения Web-службой этого запроса и его синтакси- ческого анализа вызывается нужный метод с заданными аргументами (если они есть), и клиенту отправляется ответ в другом сообщении SOAP. Клиент анализирует ответ для извлечения результата обращения к методу. Запрос SOAP, представленный в листинге 18.1, взят непосредственно из метода Bigger Web-службы Hugeinteger (см. рис. 18.3). Данная Web-служба обеспечивает программистов несколькими методами, манипу- лирующими целыми числами, большими, нежели можно сохранить в переменной типа long Большинство про- граммистов не работает с сообщениями SOAP, предоставляя Web-сйужбе обработку подробностей передачи. В листинге 18.1 показан стандартный запрос SOAP, созданный, когда клиенту необходимо выполнение метода Bigger Web-службы Hugeinteger. Когда запрос Web-службы создает такой запрос SOAP, символьные данные элементов first и second будут содержать фактические значения, введенные пользователем (строки 16 и 17). Если бы данный конверт содержал запрос, показанный на рис. 18.3, тогда элемент first и элемент second со- держали бы значения, введенные на рис. 18.3 Заполнитель length будет содержать значение длины данного сообщения SOAP. ....' ......................................----------. ........ Листинг 18.1. Запрос SOAP для Web-службы Hugeinteger •« *... -• «ёж - •.‘цййчйй ...... 1 POST /HugelntegerWebService/Hugelnteger.asmx HTTP/1.1 2 Host: localhost 3 Content-Type: text/xml; charset^utf-8 4 Content-Length: length 5 SOAPAction: "http://www.deitel.com/cspfepl/chl8/Bigger" 6 7 <?xml version="1.0" encoding="utf-8"?> 8 9 <soap:Envelope 10 xmlns:xsi="http://www/w3/org/2001/XMLSchema-instance" 11 xmlns:xsd="http://www/w3/org/2001/XMLSchema" 12 xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/> 13 14 <soap:Body> 15 <Bigger xmlns="http://www.deitel.com/cspfepl/chl8/>
ASP.NET и Web-службы 671 16 <first>string</first> 17 <second>string</second> 18 </Bigger> 19 </soap:Body> 20 21 <7 soap:Envelope> 18.4. Публикация и использование Web-служб В данном разделе приводится несколько примеров публикации и использования Web-служб. Приложение, ис- пользующее Web-службу, фактически состоит из двух частей: прокси-класса, представляющего Web-службу, и приложения клиента, осуществляющего доступ к Web-службе посредством экземпляра прокси-класса. Прокси- класс управляет передачей аргументов для метода Web-службы от клиентского приложения в Web-службу, а также передачей результата от метода Web-службы назад в приложение клиента. Visual Studio может генериро- вать прокси-классы (данный процесс рассматривается далее). В листинге 18.2 представлен файл фонового кода для Web-службы Hugeinteger (см. листинг 18.1). Название Web-службы основано на названии класса, его определяющего (в нашем случае — Hugeinteger). Данная Web- служба предназначена для выполнения расчетов с целыми числами, состоящими максимум из 100 цифр. Как упоминалось выше, переменные типа long не могут манипулировать числами такого размера (т. е. будет иметь место переполнение). Web-служба обеспечивает клиента методами, принимающими два "громадных числа" и определяющими, которое из них больше, а которое — меньше, равны ли они, их сумму или разность. Читатели могут рассматривать эти методы как службы, предоставляемые одним приложением, разработчикам других приложений (отсюда термин "Web-службы"). Любой программист может получить доступ к этой Web-службе, воспользоваться ее методами, избежав, таким образом, необходимости написания, тестирования и отладки свыше 200 строк кода. В приведенных далее примерах авторы опускают в файлах фонового кода части кода, сгенерированного Visual Studio. Это сделано для краткости и наглядности. 1 // Листинг 18.2: Hugeinteger.asmx.cs 2 // Web-служба Hugeinteger 3 4 using System; 5 using System.Text; 6 us ing System.Collect ions; 7 using System. ComponentModel; 8 using System.Data; 9 using System.Diagnostics; 10 using System.Web; 11 using System.Web.Services; // классы, относящиеся к Web-службе 12 13 namespace HugelntegerWebService 14 { 15 /// <summary> 16 III выполнение операций с большими числами 17 III </summary> 18 [WebService( 19 Namespace = "http1 11://www.deitel.com/cspfepl/chl8/, 20 Description = "A Web service which provides methods that" + 21 "can manipulate large integer values.")] 22 public class Hugeinteger : System.Web.Services.WebService 23 { 24 // конструктор по умолчанию 25 public Hugeinteger() 26 { 27 // CODEGEN: Данный вызов требуется конструктору 28 // Web-служб ASP.NET 29 InitializeComponent(); 30 31 number = new int [MAXIMUM]; 32 }
672 Глава 18 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48. 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 #region Component Designer generated code /// <summary> /// Требуемый метод для поддержки конструктора; не /// изменять содержимое метода редактором кода /// </summary> private void InitializeComponent() { J ttendregion /// <sunraary> /// Сброс всех используемых ресурсов. Ill </summary> protected override void Dispose(bool disposing) { } // ПРИМЕР WEB-СЛУЖБЫ // Пример службы HelioWorld() возвращает // строку "Hello, World" // Для построения отменить комментарии следующих // строк, сохранить и скомпоновать проект // Для тестирования Web-службы нажать клавишу <F5> // [WebMethod] // public string HelioWorld() // { // return "Hello, World"; U } private const int MAXIMUM = 100; public int[] number; // индексатор, принимающий параметр целого числа public int this[int index] { get { return number[index]; ) set { number[index] = (value >= 0 ? value : 0); ) } // конец индексатора // возврат строкового представления Hugelnteger public override string Tostring() { StringBuilder returnstring = new StringBuilder(); foreach (int digit in number) returnSt ring.Insert(0, digit); return returnstring.ToString(); ) // создание Hugelnteger на основе аргумента public static Hugelnteger FromString(string integer)
ASP NET и Web-службы 673 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 { Hugeinteger parsedlnteger = new Hugeinteger(); for (int i = 0; i < integer.Length; i++) parsedlnteger[i] = Int32.Parse( \ integer[integer.Length = i — 1].Tostring()); return parsedlnteger; } // WebMethod, выполняющий сложение целых чисел, // представленных аргументами строки [WebMethod (Description = "Adds two huge integers.")] public string Add(string first, string second) { int carry =0; Hugeinteger operandl - Hugeinteger.FromString(first); Hugeinteger operand2 = Hugeinteger.FromString(second); // сохранение результата сложения Hugeinteger result = new Hugeinteger(); // выполнение алгоритма сложения для каждой цифры for (int i - 0; i < MAXIMUM; i++) { // добавление двух цифр в тот же столбец; // результат — сумма плюс перенос из // предыдущей операции деления по модулю 10 result[i] = (operandl[i] + operand2[i]) % 10 + carry; // сохранение остатка деления сумм // двух цифр на 10 carry = (operandl[i] + operand2[i])/10; ) return result.ToString(); } // конец метода Add // WebMethod, выполняющий вычитание целых чисел, // представленных строковыми аргументами [WebMethod ( Description = "Subtracts two huge integers.")] public string Subtract(string first, string second) { Hugeinteger operandl = Hugeinteger.FromString(first); Hugeinteger operand2 = Hugeinteger.FromString(second); Hugeinteger result = new Hugeinteger(); // вычитание цифры старшего разряда из цифры младшего разряда for (int i = 0; i < MAXIMUM; i++) < // если цифра старшего разряда меньше цифры // младшего разряда, необходим заем if (operand[i] < operand2[i]) Borrow(operandl, i); 11 вычитание младшего из старшего result[i] = operandl[i] — operand2[i]; } 43 Зак. 3333
674 Глава 18 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218" 219 220 221 222 223 224 return result.Tostring(); } // конец метода Subtract 11 заем 1 из следующей цифры private void Borrow(Hugeinteger integer, int place) { // если занять негде, сообщить о проблеме if (place >= MAXIMUM - 1) throw new ArgumentException (); // в противном случае, если следующая цифра — 0, // занять от цифры слева else if (integer[place + 1] —0) Borrow(integer, place + 1); // прибавить 10 в текущее место, потому что из // предыдущей цифры занята и вычтена единица: // цифра, от которой произошел заем integer[place] += 10; integer[place + 1] -= 1; } 11 конец метода Borrow // WebMethod, возвращающий true, если первое число // больше второго [WebMethod (Description = "Determines whether first " + "integer is larger than the second integer.")] public bool Bigger(string first, string second) { chart] zeroes = { 'O’ }; try { // если результатом удаления всех нулей //из разности является пустая строка, //то числа равны, поэтому возвращается false; //в противном случае возвращается true if (Subtract(first, second).Trim(zeroes)=»= "") return false; else return true; ) // если возникает ArgumentException, то первое // число было меньше; возвращение - false catch (ArgumentException) < return false; } } // конец метода Bigger // WebMethod возвращает true, если первое целое 11 число меньше второго [WebMethod (Description = "Determines whether the " + "first integer is smaller than the second integer.")] public bool Smaller(string first, string second) { // если второе число больше первого, тогда // первое число меньше второго return Bigger(second, first); )
ASP.NET и Web-службы 675 225 // WebMethod, возвращающий true, если два целых числа равны 226 [WebMethod (Description = "Determines whether the ” + 227 "first integer is equal to the second integer.")] 228 public bool EqualTo(string first, string second) 229 { 230 // если первое число больше второго или первое число 231 // меньше второго, то числа не равны 232 if (Bigger(first, second) I I 233 Smaller(first, second)) 234 return false; 235 else 236 return true; 237 } 238 239 } // конец класса Hugelnteger 240 241 } // конец пространства имен HugelntegerWebService В строке 13 класс Hugelnteger размещается в пространстве имен HugelntegerWebService. В строке 19 про- странство имен Web-службы присваивается www.deitel.com/cspfepl/chl8/ для уникальной идентификации дан- ной Web-службы. Данное пространство имен задано с помощью свойства namespace атрибута WebService. В строках 20 и 21 для предоставления информации о Web-службе, которая появляется в файле ASMX, ис- пользуется свойство Description. В строке 22 указывается, что класс является производным от System.web.Services.WebService. По умолчанию Visual Studio определяет данную Web-службу так, что она наследует от класса WebService. Несмотря на то, что подкласс WebService не требует класса Web-службы, класс WebService предоставляет элементы, полезные при определении информации о клиенте и о самой Web-службе. Несколько методов класса Hugelnteger отмечены атрибутом WebMethod, раскрывающим метод так, что он мо- жет быть вызван удаленно. В отсутствие данного атрибута метод не доступен в Web-службе. Обратите внима- ние, что, подобно атрибуту WebService, атрибут WebMethod содержит свойство Description, предоставляющее информацию о методе на странице ASMX. О хорошем стиле программирования________________________________________________________ Для уникальной идентификации каждой Web-службы следует указывать ее пространство имен. О хорошем стиле программирования______________________________________________________ Для получения клиентом дополнительной информации о Web-службе и ее содержимом следует указывать опи- сания всех Web-служб и их методов, к О хорошем стиле программирования______________________________________________________ Методы Web-службы нельзя объявлять как static, иначе во время прогона будет иметь место ошибка при по- пытке просмотра страницы ASMX. Для доступа клиента к методу Web-службы должен существовать ее экземпляр. В строках 69—81 для класса Hugelnteger определяется индексатор. Он обеспечивает доступ к любой цифре в классе Hugelnteger так, будто доступ к ней осуществляется через массив number. В строках 108—136 и 142—163 определяются методы Add и Subtract класса webMethods, выполняющие сложение и вычитание соот- ветственно. Метод Borrow (строки 166—183) обрабатывает случай, когда цифра в левом операнде меньше соот- ветствующей цифры в правом операнде. Например, при вычитании 19 из 32 действие производится по цифрам, начиная справа. Цифра 2 меньше цифры 9, поэтому к 2 добавляется 10 (получается 12), из чего вычитается 9: крайняя правая цифра в ответе — 3. Затем из следующей цифры (3) вычитается единица: получается 2. Теперь соответствующая цифра в правом операнде — 1 в числе 19. 2 - 1 = 1, т. е. цифра в результате будет 1. Оконча- тельный результат при объединении обеих цифр равен 13. Метод Borrow прибавляет 10 к нужным цифрам и вы- читает 1 из цифры слева. Данный метод — утилитный, он не предназначен для удаленного вызова, поэтому не квалифицируется атрибутом WebMethod. Результат работы программы представлен на рис. 18.5. Изображение окна на рис. 18.5 идентично изображению на рис. 18.1. Клиентское приложение может активизи- ровать только пять методов, перечисленных на рис. 18.5 (т. е. методы, квалифицированные атрибутом WebMethod). Теперь продемонстрируем процесс создания данной Web-службы. Для начала следует создать проект типа ASP.NET Web Service. Как и Web-формы, Web-службы по умолчанию размещаются в корневом каталоге
676 Глава 18 wwwroot на сервере (iocalhost). Visual Studio по умолчанию помещает файл решения (с расширением sin) в пап- ку Visual Studio Projects в каталоге решения. (Папка Visual Studio Projects обычно размещается в папке Му Documents.) Рис. 18.5. Web-служба Hugeinteger Обратите внимание, что при создании проекта файл фонового кода по умолчанию отображается в режиме кон- структора (рис. 18.6). Если этот файл не открыт, его можно открыть щелчком кнопкой мыши на Servicel.asmx. Однако при этом открывается файл Servicel.asmx.cs (файл фонового кода для Web-службы). Это происходит потому, что при создании в Visual Studio Web-служб программисты работают почти исключительно с файлом фонового кода. На самом деле, если бы программист открыл файл ASMX, то он содержал бы только строки: <&@ WebService Language="c#" Codebehind="Servicel.asmx.cs" Class="WebServicel.Servicel" %> указывающие имя файла фонового кода, язык программирования, на котором написан этот файл, и класс, опре- деляющий Web-службу. Это информация, которую должен содержать файл. Рис. 18.6. Просмотр Web-службы в режиме конструктора
ASP.NET и Web-службы 677 Примечание_________________________________________________________________________________ По умолчанию файл фонового кода не представлен в окне Solution Explorer. Он отображается при двойном щелчке кнопкой мыши на файле ASMX в окне Solution Explorer. Этот файл может быть введен в список, ото- бражаемый в окне Solution Explorer, щелчком кнопкой мыши на пиктограмме, отображающей все файлы. Может показаться странным, что для Web-служб существует возможность просмотра в режиме конструктора, при том, что Web-службы не имеют графического пользовательского интерфейса. Просмотр в режиме конст- руктора представлен потому, что более сложные Web-службы содержат методы, манипулирующие не только строками или числами. Например, метод Web-службы может манипулировать базой данных. Вместо ввода це- лого кода, необходимого для создания подключения к базе данных, разработчики могут просто "перетащить" нужные компоненты в окно просмотра в режиме конструктора и работать с ними как в Windows- или Web- приложении. Соответствующий пример приведен в разд. 18.6. Теперь, после определения Web-службы продемонстрируем ее использование. Во-первых, необходимо создать клиентское приложение. В первом примере в качестве клиента создается приложение Windows. После создания этого приложения клиент должен добавить прокси-класс для доступа к Web-службе. Прокси-класс — это класс, созданный из файла WSDL Web-службы, позволяющий клиенту вызывать методы Web-службы по Интернету. Прокси-класс управляет всей "сантехникой", необходимой для вызова Web-службы. Всякий раз при вызове в клиентском приложении метода Web-службы, приложение фактически вызывает соответствующий метод в прокси-классе. Данный метод получает имя метода и его аргументов при форматировании их так, чтобы они могли быть отправлены как запрос в сообщении SOAP. Web-служба получает этот запрос и исполняет вызов метода с отправкой результата в другом сообщении SOAP. Когда клиентское приложение получает сообщение SOAP, содержащее ответ, прокси-класс расшифровывает его и форматирует результаты так, что они становятся понятными клиенту. После этого данная информация возвращается клиенту. Важно отметить, что прокси-класс изначально скрыт от программиста. Его нельзя просмотреть в окне Solution Explorer, если не выбрать все фай- лы. Цель прокси-класса — сделать так, чтобы клиенту казалось, будто он вызывает методы напрямую. Клиенту не обязательно просматривать прокси-класс или выполнять с ним какие-либо операции. В следующем примере демонстрируется создание клиента Web-службы и соответствующего ему прокси-класса. Начнем с создания проекта и добавления к нему Web-ссылки. При добавлении Web-ссылки к клиентскому при- ложению создается прокси-класс. После этого клиент создает экземпляр прокси-класса, используемый для вы- зова методов, входящих в Web-службу. Для создания прокси в Visual Studio щелкните правой кнопкой мыши на папке Reference в окне Solution Explorer и выберите команду Add Web Reference... (рис. 18.7). В открывающемся диалоговом окне Add Web Reference (рис. 18.8) введите адрес Web-службы и нажмите клавишу <Enter>. После выбора Web-службы по- явится ее описание, и разработчик может нажать кнопку Add Reference (рис. 18.8). При этом в окне Solution Explorer (рис. 18.9) добавится папка с узлом, названным по имени домена, в котором расположена Web-служба. В данном случае имя — localhost, потому что используется локальный Web-сервер. Это означает, что при ссылке на класс Hugeinteger она будет выполняться через класс Hugeinteger в пространстве имен localhost, а не через класс Hugeinteger в пространстве имен HugeIntegerWebServi.ee. Примечание_________________________________________________________________________________ Класс Web-службы и прокси-класс имеют одно и то же имя. Visual Studio создает прокси для Web-службы и до- бавляет его в качестве ссылки (рис. 18 9). О хорошем стиле программирования >_________________,_______________________________________ При создании программы, которая будет использовать Web-службы, сначала добавьте Web-ссылку. Этим обес- печится распознавание Visual Studio NET экземпляра класса Web-службы; Intellisense окажет разработчику по- мощь в использовании Web-службы. Описанный выше шаг эффективно работает, если программист знает ссылку на соответствующую Web-службу. А что, если планируется разместить новую Web-службу? Существуют две технологии, упрощающие данный процесс: универсальное описание, обнаружение и интеграция (Universal Description, Discoveiy and Integration, UDDI) и файлы обнаружения (Discovery files, DISCO). UDDI — это проект для разработки набора специфика- ций, определяющего "публикацию" Web-служб так, чтобы программисты могли их найти. Корпорация Microsoft запустила осуществление этого проекта для упрощения нахождения местоположения Web-служб, соответст- вующих определенным спецификациям, программистами, использующими механизмы поиска. UDDI организу- ет, описывает Web-службы с последующим размещением этой информации в центральном местоположении. Более подробное рассмотрение UDDI не входит в задачи данной книги, но читатели могут ознакомиться с этим проектом и просмотреть демонстрационные материалы на сайтах www.uddi.org и uddi.microsoft.com. На этих сайтах имеются инструментальные средства оперативного поиска Web-служб.
678 Глава 18 Рис. 18.7. Добавление в проект ссылки на Web-службу Рис. 18.8. Выбор Web-ссылки и описания Описание службы disco-файл Web-службы Прокси-класс Рис. 18.9. Окно Solution Explorer после добавления в проект ссылки на Web-службу В файле DISCO в отдельном каталоге перечислены Web-службы. Существуют два типа файлов обнаружения: файлы динамического обнаружения (с расширением vdisco) и файлы статического обнаружения (с расшире- нием disco). Эти файлы указывают как местоположение файла ASMX, так и описание каждой Web-службы (файл WSDL) в текущем каталоге, а также всех Web-служб в подкаталогах текущего каталога. Когда програм- мист создает Web-службу, Visual Studio генерирует для нее файл динамического обнаружения. При добавлении Web-ссылки клиент использует файл динамического обнаружения для выбора нужной Web-службы. После соз- дания Web-ссылки файл статического обнаружения помещается в проект клиента. Файл статического обнару- жения жестко кодирует местоположение файлов ASMX и WSDL. (Под "жестким кодированием" подразумевается, что местоположение вводится непосредственно в файл.) С другой стороны, файлы динамического обнаружения создаются так, что список Web-служб создается на сервере динамически при поиске клиентом Web-службы. Использование динамического обнаружения активизирует некоторые дополнительные опции, например, скры- тие определенных Web-служб в подкаталогах. Файлы обнаружения — это технология, созданная непосредст- венно под продукты Microsoft, a UDDI — нет. Впрочем, они могут работать совместно для обеспечения нахож- дения клиентом нужной Web-службы. При использовании обеих технологий клиент может применять механизм поиска для обнаружения местоположения различных Web-служб, относящихся к одному предмету, с после- дующим применением файлов обнаружения для просмотра всех найденных в данном местоположении Web- служб. После добавления Web-ссылки клиент может осуществлять доступ к Web-службе через прокси. По причине того, что прокси-класс имеет имя Hugelnteger и размещен в пространстве имен localhost, для ссылки на этот класс необходимо использовать localhost.Hugelnteger. В форме Windows, представленной в листинге 18.3, используется Web-форма Hugelnteger для выполнения расчетов с положительными числами длиной до 100 цифр. Примечание___________________________________________________________________________________ При использовании примеров книги, размещенных на сайте, читателю может потребоваться повторное создание прокси.
ASP.NET и Web-службы 679 1 // Листинг 18.3: UsingHygeIntegerService.es 2 // Использование Web-службы Hugeinteger 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 using System; using System.Drawing; using System.Collections; using System.ComponentModel; using System. Windows.Forms; using System.Web.Services.Protocols; // пользователь может выполнять операции с большими числами public class UsingHugelntService : System.Windows.Forms.Form { private System.Windows.Forms.Label promptLabel; private System.Windows.Forms.Label resultLabel; private System.Windows.Forms.TextBox firstTextBox; private System.Windows.Forms.TextBox secondtextBox; private System.Windows.Forms.Button addButton; private System.Windows.Forms.Button subtractButton; private System.Windows.Forms.Button biggerButton; private System.Windows.Forms.Button smallerButton; private System.Windows.Forms.Button equalButton; private System.ComponentModel.Container components = null; // объявление ссылки Web-службы private localhost.Hugeinteger remoteinteger; private chart] zeroes = { 'O' }; // конструктор по умолчанию public UsingHugelntService() { InitializeComponent(}; // создание remoteinteger remoteinteger = new localhost.Hugeinteger(); ] // код, сгенерированный Visual Studio .NET [STAThread] static void Main() { Application.Run(new UsingHugelntService()); } // конец Main // проверка равенства двух вводимых пользователем чисел protected void equalButton_Click( object sender, System.EventArgs e) { // проверка, что длина Hugelntd'gers не превышает 100 цифр if (Checksize(firstTextBox, secondTextBox)) return; // вызов метода Web-службы для определения // равенства чисел if (remoteinteger.EqualTo( firstTextBox.Text, secondTextBox.Text))
680 Глава 18 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 resultLabel.Text = firstTextBox.text.TrimStart(zeroes) + " is equal to " + secondTextBox.Text.TrimStart(zeroes); else resultLabel.Text = firsttextBox.text.TrimStart(zeroes) + " is NOT equal to " + secondTextBox.Text.TrimStart(zeroes); r } // конец метода equalButton_Click // проверка, не меньше ли первое введенное пользователем // число второго protected void smallerButton_Click( object sender, Syste.EventArgs e) { // проверка, что длина Hugelntegers не превышает 100 цифр if (Checksize(firstTextBox, secondTextBox)) return; // вызов метода Web-службы для определения, не меньше // ли первое число второго if (remoteinteger.Smaller( fi rstTextBox.Text, secondTextBox.Text)) resultLabel.Text = firstTextBox.Text.TrimStart(zeroes) + " is smaller than " + secondTextBox.Text.TrimStart(zeroes); else resultLabel.Text = firstTextBox.Text.TrimStart(zeroes) + " is NOT smaller than " + secondTextBox.Text.TrimStart(zeroes); } // конец метода smallerButton_Click_ // проверка, не больше ли первое введенное пользователем // число второго protected void biggerButton_Click( object sender, System.EvenrArgs e) { // проверка, что длина Hugelntegers не превышает 100 цифр if (Checksize(firstTextBox, secondTextBox)) return; // вызов метода Web-службы для определения, не больше //ли первое число второго if (remoteinteger.Bigger(firstTextBox.Text, secondTextBox.Text)) resultLabel.Text = firstTextBox.Text.TrimStart(zeroes) + " is larger than " + secondTextBox.Text.TrimStart(zeroes); else • resultLabel.Text = firstTextBox.Text.TrimStart(zeroes) + " is NOT larher than " + secondTextBox.Text.TrimStart(zeroes); } // конец метода biggerButton_Click
ASP.NET и Web-службы 681 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 П вычитание второго числа из первого protected void subtractButton_Click( object sender, System.EventArgs e") { // проверка, что длина'Hugelntegers не превышает 100 цифр if (Checksize(firstTextBox, secondTextBox)) return; 11 выполнение вычитания try < string result = remoteinteger.Subtract( firstTextBox.Text, secontTextBox.text).Trimstart(zeroes); resultLabel.Text = ((result — "") ? "0" : result); } // если метод выдает исключение, тогда первый // аргумент был меньше второго catch (SoapException) ( MessageBox.Show( "First argument was smaller than the second”); ) } // конец метода subtractButton_Click // сложение двух введенных пользователем чисел protected void addButton_Click( object sender, System.EventArgs e) { // проверка, что Hugelnteger не превышает 100 цифр // и оба числа не имеют длины в 100 цифр; // результат: переполнение if (firstTextBox.text.Length > 100 I I secondTextBox.text.Length > 100 I I (firstTextBox.Text.Length — 100 && secondTextBox.text.Length 100)) { MessageBox.Show("Hugelntegers must not be more " + "than 100 digits\nBoth integers cannot be of" + " length 100: this causes an overflow", "Error", MessageBoxButtons.OK, MessageBoxIson.Information); return; } // выполнение сложения resultLabel.Text = remoteinteger.Add(firstTextBox.Text, secondTextBox.Text).TrimStart(zeroes).ToString(); } // конец метода addButton_Click // проверка чрезмерности длины чисел private bool Checksize(TextBox first, TextBox second) { if (first.Text.Length > 100 I I second.Text.Length > 100) { MessageBox.Show("Hugelntegers must be less than 100" + "digits", "Error", MessageBoxButtons.OK, MessageBoxIcon.Information);
682 Глава 18 192 return true; 193 } 194 195 return false; 196 197 } // конец метода Checksize 198 199 } // конец класса UsingHugelntegerService Пользователь вводит два числа длиной до 100 цифр каждое (рис. 18.10). Нажатие любой из кнопок активизирует удаленный метод, выполняющий соответствующее вычисление, и возвращает результат. Отображается возвра- щенное значение каждой операции, а все предшествующие нули удаляются методом Trimstart типа string. Обратите внимание, что класс usingHugeinteger не может выполнять операции с числами, состоящими из 100 цифр. Вместо этого создаются строковые представления этих чисел, передаваемые в качестве аргументов в методы Web-службы, обрабатывающие такие операции. UsingHugeinteger Иве“*«. JP to 100 5дкгч* „ У’ '* ** ; i ,•** C .________________________ ;1з5 3535353535 3535353S 3535 353535353535 353535 35 3535 3535353535353535 Я * « |121212121212121 2121212121 г jг121212321212121212121212121212121212 "1 | й Siralei Than. ' Рис. 18.10. Работа приложения, использующего Web-службу Hugeinteger: а — нажатие кнопки Add; б — нажатие кнопки Subtract; в — нажатие кнопки Larger Than 18.5. Контроль сеансов в Web-службах В главе 17 рассматривалась важность поддержания информации о пользователях для персонализации их сеансов работы. В контексте данного обсуждения исследовался процесс контроля сеансов с помощью cookies и сеансов. В настоящем разделе контроль сеансов включается в использование Web-служб. Иногда клиентскому приложе- нию имеет смысл вызывать несколько методов одной Web-службы и обращаться к некоторым методам не- сколько раз. Для Web-службы будет полезно поддерживать информацию о состоянии для клиента. Использова-
ASP.NET и Web-службы 683 ние контроля сеансов имеет преимущества, потому что сведения, сохраненные как часть сеанса, не нужно пере- давать от Web-службы клиенту и наоборот. При этом не только ускоряется исполнение клиентского приложе- ния, но и со стороны программиста требуется меньше усилий (например, вследствие передачи меньших объе- мов информации в метод Web-службы). Сохранение информации о сеансе может обеспечить более "интуитивную" Web-службу. В приводимом далее примере создается Web-служба для выполнения расчетов в карточной игре "блэк-джек" (листинг 18.4). Затем данная Web-служба используется для создания "раздающего" в игре блэк-джек. Раздающий обрабатывает все нюансы отдельно взятой колоды карт. Информация сохраняется как часть сеанса, поэтому одна колода не сме- шивается с другой, используемой, например, другим клиентским приложением. В примере применяются сле- дующие правила игры в блэк-джек. Раздающему и игроку сдаются по две карты. Карты игрока — "открытые". У раздающего открыта только одна карта. Игрок начинает брать по одной карте из колоды, которые сдаются открытыми, и решает, когда остановиться. Если сумма очков взятых игроком карт превышает 21, то игра заканчивается его проигрышем. Когда игроку достаточно взятых карт, он "останавливается" (перестает брать карты), после чего вскрывается закрытая карта раздающего. Если сумма очков карт раздающего меньше 17, то он должен взять еще одну карту, либо остановиться. Раздающий обязан брать карты до тех пор, пока сумма их очков не будет больше или равной 17. При "переборе" раздающего (набрано больше 21 очка) выигрывает игрок. В противном слу- чае выигрывает тот, у кого сумма очков максимально приближена к 21. Если у обоих равное количество оч- ков, то это означает "ничью". Если сумма очков первых двух карт игрока равна 21, то он выигрывает авто- матически. Такой выигрыш и называется блэк-джеком. Создаваемая Web-служба предоставляет методы сдачи карт и подсчета очков карт, сданных каждому игроку. Каждая карта обозначена строкой в форме "face suit", где face— цифра, обозначающая достоинство карты, а suit— цифра, обозначающая масть карты. После создания Web-службы формируется Windows-приложение, в котором эти методы используются для моделирования игры "блэк-джек". // Листинг 18.4: BlackjackService.asmx.cs // Web-служба Blackjack для манипуляций колодой карт 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 using System; using System.Collections; using System.ComponentModel; using System.Data; using System.Diagnostics; using System.Web; using System.Web.Services; namespace BlackjackWebService { [WebService( Namespace = "http://www.deitel.com/cspfepl/chl8/, Description = "A Web service that provides methods " + "to manipulate a deck of cards.")] public class VlackjackService : System.Web.Services.WebService { // код, сгенерированный Visual Studio .NET // сдать новую карту [WebMethod[EnableSession = true, Description = "Deal a new card from the deck.")] public string DealCardO { string card = "2 2";
684 Глава 18 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 .69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 11 получение колоды клиента ArrayList deck = (ArrayList) Session["deck"]; card = (string) deck[0]; deck.RemoveAt(0); return card; } // конец метода DealCard [WebMethod(EnableSession = true, Description = "Create and shuffle a deck qf card.")] public void Shuffle() { Random randomobject = new RandomO; ArrayList deck = new ArrayList(); // генерирование всех возможных карт for (int i = 1; i < 14; i++) { for (int j = j; j < 4; j++) { deck.Add(i + " " + j); } ) // произвольно заменить каждую карту другой картой for (int i = 0; i < deck.Count; i++) { int newlndex = randomobject.Next(deck.Count); object temporary « deckti]; deck[i] = deck[newlndex]; deck[newlndex] = temporary; } // добавить данную колоду в состояние сеанса пользователя Session["deck"] = deck; // подсчет количества очков у играющего [WebMethod (Description = "Compute а ” + "numerical value for the current hand."!] public int Countcards(string dealt) { // разбиение строк, содержащих карты char[] tab = { '\t' }; string!] cards = dealt.Split(tab); int total = 0, face, aceCount = 0; foreach (string drawn in cards) { // получение значения карты face = Int32.Parse(drawn.Substring( 0, drawn.IndexOf(" ”))); switch (face) { // при тузе увеличить количество тузов у игрока case 1: aceCount++; break; // если валет, дама, король, добавить 10 к общему количеству очков case 11: case 12: case 13: total += 10; break;
ASP.NET и Web-службы 685 95 // в противном случае добавить значение карты 96 default: 97 total += face; 98 break; 99 100 ) // конец switch 101 102 } // конец foreach 103 104 // при наличии тузов расчет оптимального общего к-ва очков 105 if (aceCount > 0) 106 { 107 // при возможности считать туза за 11 очков, 107а // а остальные карты — 1, то так и делать; 108 // в противном случае считать тузы по 1 очку за каждыйв 109 if (total + 11 + aceCount — 1 <= 21) 110 total += 11 + aceCount — 1; 111 else » 112 total += aceCount; 113 } 114 115 return total; 116 117 } // конец метода CountCards 118 119 } // конец класса BlackjackService 120 121 } // конец пространства имен BlackjackWebService В строках 24—36 в качестве WebMethod определяется метод Dealcard со значением true свойства EnableSession. Значение true необходимо для поддержки информации о сеансе. Этот простой шаг обеспечива- ет Web-службе важное преимущество. Теперь служба может использовать объект HttpSessionState (называется Session) для поддержания колоды карт для каждого клиентского приложения, использующего данную Web- службу (строка 31). Объект Session можно использовать для сохранения объектов для конкретного клиента ме- жду вызовами метода. Более подробно состояние сеанса рассматривалось в главе 17. Как вскоре станет понятно, метод Deaicard извлекает карту из колоды и возвращает ее клиенту. Если бы не ис- пользовалась переменная сеанса, тогда колоду карт пришлось бы передавать из службы в приложение и обратно при каждом вызове метода. Применение контроля состояния сеанса не только упрощает вызов метода (теперь он не требует аргументов), но и исключает непроизводительные издержки, которые бы имели место при много- кратной передаче информации; теперь Web-служба работает более оперативно. В текущей реализации имеются только методы, использующие переменные сеанса. Однако Web-служба не мо- жет определить, к какому пользователю какие переменные сеанса относятся. Это — достаточно важный мо- мент: если Web-служба не может уникально идентифицировать пользователя, то она не может корректно осу- ществить контроль сеанса. Если один клиент дважды вызовет метод Dealcard, то игра будет вестись на двух разных колодах (как в случае, если метод Dealcard вызовут два разных пользователя). Для идентификации раз- ных пользователей Web-служба создает для каждого пользователя cookies. К сожалению, Web-служба не может определить, активизированы cookies на машине клиента или нет. Если клиентское приложение предполагает использование данной Web-службы, то клиент должен принять cookies в объект cookiecontainer. Этот аспект будет рассмотрен немного ниже при обсуждении клиентского приложения, использующего Web-службу Blackjack. Метод Dealcard (строки 24—36) получает колоду текущего пользователя как ArrayList из объекта Session Web-службы (строка 31). Класс ArrayList можно рассматривать как динамический массив (т. е. его размер спо- собен меняться во время выполнения программы). Более подробно класс ArrayList рассматривается в главе 20. Метод Add данного класса помещает object в класс ArrayList. Затем метод Dealcard снимает из колоды верх- нюю карту (строка 33) и возвращает ее значение как string (строка 34). Метод Shuffle (строки 38—64) генерирует класс ArrayList, обозначающий колоду карт, тасует ее и сохраняет перетасованные карты в объекте Session клиента. В строки 47—51 включены циклы for для генерирования строк в форме "face suit" для обозначения каждой возможной карты в колоде. В строках 54—50 заново соз- данная колода тасуется путем замены каждой карты другой картой колоды В строке 63 класс ArrayList добав- ляется в объект Session для поддержания колоды между вызовами методов.
686 Глава 18 С помощью метода Countcards (строки 67—117) подсчитывается количество очков у каждого игрока с целью получения наибольшего значения, не превышающего 21. Подсчет требует особого внимания, потому что значе- ние туза может считаться как 1 или 11, а все остальные фигуры — 10. Строка dealt помечается по каждой ее карте вызовом метода Split и передачей ему массива, содержащего символ табуляции. Цикл foreach (строки 76—102) подсчитывает значение каждой карты. В строках 79—81 из- влекается первое число— лицевая сторона карты,— и это значение вводится в оператор switch в строке 83. Если значение карты — L (туз), тогда программа увеличивает переменную aceCount. По причине того, что туз может иметь два значения, для обработки тузов требуется дополнительная логика. Если значение карты равно 13, 12 или 11 (король, дама или валет), тогда к общей сумме программа прибавляет 10. При другой карте про- грамма увеличивает общее количество очков на значение этой карты. В строках 105—113 тузы считаются после всех других карт. Если у одного игрока несколько тузов, то только один считается за 11 очков (если бы по 11 очков считалось, например, 2 туза, то уже получилось бы 22, а это — "перебор"). Затем определяется возможность присвоения тузу 11 очков без превышения 21. Если такая ситуация возможна, тогда в строке 110 общее количество очков настраивается соответственно. В противном случае, в строке 112 общее количество очков настраивается исходя из того, что каждый туз — 1 очко. В методе countcards делается попытка максимального увеличения значения имеющихся карт без превышения 21 очка. Представим, например, что раздающий имеет "семерку", и ему приходит туз. Новое общее количество очков может быть 8 или 18. Однако метод Countcards всегда старается максимально увеличить значение карт без превышения 21, поэтому, в данном случае, новое количество очков будет равно 18. Теперь воспользуемся Web-службой Blackjack в Windows-приложении под названием Game (листинг 18.5). В этой программе для обозначения раздающего используется экземпляр пространства имен Black jackwebservice с вызовом методов Dealcard и Countcards. Web-служба отслеживает карты как раздающего,-так и его против- ника (т. е. все сданные карты). Каждый игрок имеет 11 графических областей (PictureBox): максимальное количество карт, которые можно сдать без превышения 21 очка. Эти графические области размещены в классе ArrayList, что позволяет индек- сировать ArrayList для определения, какая область отображает карту. Ранее упоминалось, что для идентификации пользователей клиент должен обеспечивать способ принятия любо- го cookies, созданного Web-службой. В строке 64 в конструкторе создается новый объект cookiecontainer для свойства CookieContainer объекта dealer. Класс CookieContainer (определен в пространстве имен System.Net) выполняет роль области памяти для любого объекта класса HttpCookie. Создание CookieContainer позволяет Web-службе поддерживать состояние сеанса для текущего клиента. Этот класс сохраняет cookie с уникальным идентификатором, который сервер использует для идентификации клиента при последующих запросах послед- него. По умолчанию CookieContainer имеет значение null, и для каждого клиента Web-служба создает новый объект Session. 1 // Листинг 18.5: Blackjack.cs 2 // Игра "Блэк-джек", использующая Web-службу Blackjack 3 4 5 б 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 using System; using System.Drawing; using System.Collections; using System.ComponentModel; using System.Windows.Forms; using System.Data; using System.Net; // игра, использующая Web-службу Blackjack public class Blackjack : System.Windows.Forms.Form { private System.Windows.Forms.PictureBox pictureBoxl; private System.Windows.Forms.PictureBox pictureBox2; private System.Windows.Forms.PictureBox pictureBox3; private System.Windows.Forms.PictureBox pictureBox4; private System.Windows.Forms.PictureBox pictureBox5; private System.Windows.Forms.PictureBox pictureBox6; private System.Windows.Forms.PictureBox pictureBox7;
ASP.NET и Web-службы 687 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 private System.Windows.Forms.PictureBox pictureBox8; private System.Windows.Forms.PictureBox pictureBox9; private System.Windows.Forms.PictureBox pictureBoxlO; private System.Windows.Forms.PictureBox pictureBoxl1; private System.Windows.Forms.PictureBox pictureBoxl2; private System.Windows.Forms.PictureBox pictureBoxl3; private System.Windows.Forms.PictureBox pictureBoxl4; private System.Windows.Forms.PictureBox pictureBoxl5; private System.Windows.Forms.PictureBox pictureBoxl6; private System.Windows.Forms.PictureBox pictureBoxl7; private System.Windows.Forms.PictureBox pictureBoxl8; private System.Windows.Forms.PictureBox pictureBoxl9; private System.Windows.Forms.PictureBox pictureBox20; private System.Windows.Forms.PictureBox pictureBox21; private System.Windows-. Forms. PictureBox pictureBox22 ; private System.Windows.Forms.Button dealButton; private System.Windows.Forms.Button hitButton; private System.Windows.Forms.Button stayButton; private System.ComponentModel.Container components = null; private localhost.Blackjackservice dealer; private string dealersCards, playersCards; private ArrayList cardBoxes; private int playerCard, dealercard; // метки, отображающие статус игры, раздающего и игрока private System.Windows.Forms.Labell dealerLabel; private System.Windows.Forms.Labell playerLabel; private System.Windows.Forms.Labell statusLabel; public enum GameStatus : int { PUSH, LOSE, WIN, BLACKJACK }; public Blackjack() { InitializeComponent(); dealer = new localhost.BlackjackService(); // обеспечение состояния сеанса dealer.CookieContainer = new CookieContainer(); cardBoxes = new ArrayList(); // размещение PictureBoxes в cardBoxes cardBoxes.Add(pictureBoxl); cardBoxes.Add(pictureBox2); cardBoxes.Add(pictureBox3); cardBoxes. Add(pictureBox4); cardBoxes.Add(pictureBox5); cardBoxes.Add(pictureBox6); . cardBoxes.Add (pictureBox7); cardBoxes.Add(pictureBox8); cardBoxes.Add(pictureBox9); cardBoxes.Add(pictureBoxlO); cardBoxes.Add(pictureBoxll); cardBoxes.Add(plctureBoxl2); cardBoxes.Add(pictureBoxl3); cardBoxes.Add(pictureBoxl4); cardBoxes.Add(pictureBoxl5); cardBoxes.Add(pictureBoxl6);
688 Глава 18 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 < 14 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 cardBoxes.Add(pictureBoxl7); cardBoxes.Add(pictureBoxl8); , cardBoxes.Add(pictureBoxl9); cardBoxes.Add(pictureBox20); cardBoxes.Add(pictureBox21); cardBoxes.Add(pictureBox22); } // конец метода Blackjack // код, сгенерированный Visual Studio .NET [STAThread] static void Main() { Application.Run(new Blackjack()); } // конец Main // сдача карт раздающему; общее количество очков — меньше 17; // подсчет очков у каждого игрока //и определение победителя protected void stayButton_Click( object sender, System.EventArgs e) { stayButton.Enabled = false; hitButton.Enabled = false; dealButton.Enabled = true; DealerPlay(); ) 11 обработка "вскрытия" раздающего private void DealerPlay() < // пока количество очков раздающего меньше 17, // раздающий должен брать карты while (dealer>CountCards(dealersCards) < 17) { dealerscards += "\t" + dealer.DealerCard(); DisplayCard(dealercard, ""); dealercard++; MessageBox.Show("Dealer takes a card"); } int dealersTotal = dealer.CountCards(dealerscards); ! int playersTotal = dealer.CountCards(playersCards); 11 если у раздающего ’^перебор", игрок выиграл if (dealersTotal > 21) ( GameOver (GameStatus-. WIN) ; return; ) // если раздающий и игрок не имеют "перебора", // выигрывает большее количество очков; если поровну - ничья if (dealersTotal > playersTotal) GameOver-(GameStatus.LOSE) ; else if (playersTotal > dealersTotal) GameOver(GameStatus.WIN); else * GameOver(GameStatus.PUSH); } // конец метода DealerPlay
ASP.NET и Web-службы 689 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 // сдача очередной карты игроку protected void hitButton_Click( object sender, System.EventArgs e) { // игрок получает очередную карту string card = dealer.DealCard(); playersCards += "\t" + card; DisplayCard(playerCard, card); playerCard++; int total = dealer.CountCards(playersCards); // если у игрока "перебор", машина выигрывает if (total > 21) GameOver(GameStatus.LOSE); 11 если игрок имеет 21 очко, он не может брать карты // играет раздающий if (total == 21) { hitButton.Enabled = false; DealerPlay(); ) } // конец метода hitButton_Click 11 сдача по две карты раздающему и игроку protected void dealButton_Click( object sender, System.EventArgs e) { string card; // сброс изображений карт foreach (PictureBox cardimage in cardBoxes) cardimage.Image = null; // сброс состояния предыдущего кона statusLabel.Text = ""; // тасование карт ' » dealer.Shuffle(); // сдача двух карт игроку playersCards = dealer.DealCard(); DisplayCard(11, playerscards); card = dealer.DealCard(); DisplayCard(12, card); playerscards +₽ "\t" + card; // сдача двух карт раздающему; показать лицо // только первой карты dealersCards = dealer.DealCard(); DisplayCard(0, dealersCards); card = dealer.DealCard(); Di splayCards(1, ""); dealersCards += "\t" + card; stayButton.Enabled = true; hitButton.Enabled = true; dealButton.Enabled = false; int dealersTotal = dealer.CountCards(dealersCards); int playersTotal = dealer.CountCards(playerscards); 44 Зак. 3333
690 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 Глава 18 // если у каждого 21, то ничья if (dealersTotal — playersTotal && dealersTotal = 21) Gameover(Gamestatus.PUSH); // если у игрока - 21, он выигрывает блэк-джеком else if (playersTotal == 21) GameOver(Gamestatus.BLACKJACK); // если у раздающего - 21, он выигрывает else if (dealersTotal == 21) GameOver(Gamestatus.LOSE); dealercard =2; playercard = 13; } // конец метода dealButton_Click // отображение карты, представленной cardValue в // PictureBox как числовая карта public void DisplayCard(int card, string cardValue) { // извлечение соответствующей граф, области из ArrayList PictureBox displayBox = (PictureBox) cardBoxes[card]; // если представляющая карту строка пустая, // установить displayBox на отображение рубашки карты if (cardValue = "") { displayBox.Image = Image.FromFile("Blackjack_images\\cardback.png"); return; } // извлечение значения карты из класса cardValue int faceNumber = Int32.Parse(cardValue.Substring(0, cardValue.IndexOf(" "))); string face = faceNumber.ToStringO ; // извлечение масти карты из класса cardValue string suit = cardValue.Substring( cardValue.IndexOf(" ") + 1); char suitLetter; // отличается ли масть от треф switch (Convert.Toint32(suit)) { // масть - трефы case 0: suitLetter = 1c'; break; // масть - бубны case 1: suitLetter = 'd'; break; // масть — червы case 2: suitLetter = 'h'; break;
ASP.NET и Web-службы 691 277 // либо масть - пики 278 default: 279 suxtLetter = ’s'; 280 break; 281 } 282 283 // установка displayBox на\ отображение нужной "картинки" 284 displayBox. Image = Image.FromFile( 285 "blackjack_images\\" + face + suitletter + ".png"); 286 287 } // конец метода DisplayCard 288 289 // отображение всех карт игрока и сообщения 290 //о состоянии игры 291 public void GameOver(GameStatus winner) 292 { 293 char[] tab = { *\t' }; 294 string[] cards = dealersCards'• Split (tab) ; 295 296 for (int i = 0; i < cards.Length; i++) 2 97 DisplayCard(i, cards[i]); 298 299 // ничья 300 if (winner == GameStatus.PUSH) 301 statusLabel.text = "It's a tie!"; 302 303 // игрок проиграл 304 else if Jwinner — GaneStatus.LOSE) 305 statusLabel.Text = "You Lose Try Again!"; 306 307 // игрок выиграл 308 else if (winner == GameStatus.WIN) 309 statusLabel.Text = "You Win!"; 310 311 // игрок выиграл с блэк-джеком 312 else 313 statusLabel.Text = "Blackjack!"; 314 315 stayButton.Enabled « false; 316 hitButton.Enabled = false; 317 dealButton.Enabled = true; 318 319 } // конец метода GameOver 320 321 } // конец класса Blackjack Метод GameOver (строки 291—319) отображает все карты раздающего (многие из которых повернуты в процессе игры рубашками вверх) и соответствующее сообщение в состоянии PictureBox. Метод GameOver принимает в качестве аргумента элемент перечисления Gamestatus (определено в строках 54 и 55). Перечисление обозначает выигрыш игрока, его проигрыш или ничью; четыре элемента — следующие push (ничья), lose (проигрыш), win (выигрыш) И BLACKJACK (ОЧКО). При нажатии игроком кнопки Deal1 (в обработчике событий в строках 176—229) все графические области (PictureBox) сбрасываются, колода тасуется, и игрок с раздающим получают по две карты (рис. 18.11). Если у обоих— 21 очко, то вызывается метод GameOver, которому передается сообщение о состоянии игры — GameStatus. push. Если у игрока— 21 очко, то вызывается метод GameOver, которому передается сообщение Gamestatus.blackjack. И если 21 очко только у раздающего, то вызываемому методу GameOver передается со- общение О проигрыше игрока: GameStatus.LOSE. Если метод GameOver не вызывается, тогда игрок может брать дополнительные карты нажатием кнопки Hit (в обработчике событий в строках 150—173). Всякий раз при нажатии игроком кнопки Hit ему сдается одна 1 Раздать карты. — Пер.
692 Глава 18 карта, отображаемая в GUI. Если количество очков игрока превышает 21, то игра заканчивается его проигры- шем. Если игрок имеет 21 очко, то он не может больше брать карты. Игроки могут нажимать на кнопку Stay для указания того, что они не хотят рисковать. Для данного события в обработчике (строки 106—113) все три кнопки неактивны, и вызывается метод DealerPlay. Этот метод (стро- ки 116—147) вынуждает раздающего брать карты до тех пор, пока общее количество его очков не составит 17 или больше. Если сумма очков взятых раздающим карт превышает 21, то выигрывает игрок; в противном слу- чае сравниваются очки обоих играющих, и с соответствующим аргументом вызывается метод GameOver. С помощью метода Displaycard (строки 233—287) извлекается изображение соответствующей карты. В качест- ве аргументов данный метод принимает число, обозначающее указатель PictureBox в классе ArrayList, кото- рый должен иметь множество образов и строку, представляющую карту. Пустая строка указывает, что отобра- зить нужно рубашку карты; в противном случае программа извлекает из строки значение и масть и использует эту информацию для нахождения нужного изображения. Оператор switch (строки 260—281) преобразует число, обозначающее масть, в целое число и присваивает нужный символ классу suitLetter (с — трефы, d — бубны, h—>червы и s — пики). Символ suitLetter завершает имя файла изображения карты. Рис. 18.11. Игра "Блэк-джек": а — нажатие кнопки Deal; б — выиграл игрок; в — игрок проиграл 18.6. Использование Web-форм и Web-служб В предыдущих примерах доступ к Web-службам осуществлялся из приложений Windows. Но их легко можно использовать и в Web-приложениях. По причине того, что электронный бизнес становится все более актуаль- ным, программистам часто выгоднее проектировать Web-службы как часть Web-приложений. В листинге 18.6
ASP.NET и Web-службы 693 представлена Web-служба бронирования авиабилетов (рис. 18.12), получающая информацию, касающуюся типа места, которое клиент хочет забронировать, с последующим фактическим его бронированием (при наличии). р* 1 ...ду ’шамиг «rwv1'пар Листинг J8.6. Wab-служба бронирования авиабилетов 1 // Листинг 18.6: Reservation.asmx.cs 2 // Web-служба бронирования авиабилетов 3 4 using System; 5 using System.Data; 6 using System.Diagnostics; 7 using System.Web; 8 using System.Web.Services; 9 using System.Data.OleDb; 10 11 namespace AirlineReservation 12 { 13 // выполнение бронирования места 14 [WebService(Namespace = "http://www.deitel.com/cspfepl/chl8/", 15 Description = "Service that enables a user to " + 16 "reserve a seat on a plane.")] 17 public class Reservation : System.Web.Services.Webservice 18 { 19 private System.Data.OleDb.OleDbCommand 2 0 oleDbSelectCommandl; 21 private System.Data.OleDb.OleDbCommand 22 oleDblnsertCdmmandl; 23 private System.Data.OleDb.OleDbCommand 2 4 oleDbUpdateCommandl; 25 private System.Data.OleDb.OleDbCommand 26 oleDbDeleteCommandl; 27 private System.Data.OleDb.OleDbConnection 28 oleDbConnectionl; 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 private System.Data.OleDb.OleDbDataAdapter oleDbDataAdapterl; // код, сгенерированный Visual Studio .NET 11 проверка базы данных для определения наличия // нужного места [WebMethod (Description = "Method to reserve seat.")] public bool Reserve(string seatType, string classType) { OleDbDataReader dataReader; // попытка подключения к базе данных try { // открытие подключения к базе данных oleDbConnectionl.Open(); 47 // установка и исполнение запроса SQL 48 oleDbDataAdapterl.SelectCommand.CommandText = 49 "SELECT Number FROM Seats WHERE Type = '" + 50 seatType + "' AND Class = ”• + classType + 51 •” AND Taken = '0'"; 52 dataReader = 53 oleDbDataAdapterl.SelectCommand.ExecuteReader(); 54 55 // при наличии результатов место имеется 56 if (dataReader.Read())
694 Глава 18 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 { string seatNumber = dataReader.GetString(0); dataReader.Close(); // обновление первого имеющегося места oleDbDataAdapterl.UpdateCommand. CommandText = "Update Seats Set Taken = ’11 WHERE Number = '" + seatNumber + ; oleDbDataAdapterl.UpdateCommand.ExecuteNonQuery(); return true; } // конец if dataReader.Close(); } catch (OleDbException) // при проблемах подключения { return false; } finally { oleDbConnectionl.Close(); } // место не забронировано return false; } /J конец метода Reserve } // конец класса Reservation } // конец пространства имен AirlineReservation Рис. 18.12. Web-служба бронирования авиабилетов в режиме просмотра Web-служба бронирования билетов содержит один Web-метод — Reserve (строки 36—85), осуществляющий поиск в базе данных имеющихся в наличии мест необходимого клиенту места. Если таковое находится, то метод Reserve обновляет базу данных, выполняет бронирование и возвращает true; в противном случае бронирования не происходит, и метод возвращает false. Метод Reserve принимает два аргумента: строку, обозначающую нужный тип места (выбор: у окна, в середине или у прохода), и строку, обозначающую нужный тип класса (экономический или бизнес-класс). В базе данных имеются четыре столбца: номер места, тип места, тип класса и столбец, содержащий 0 или 1, указывающие на то, занято место или нет. В строках 48—51 содержится команда SQL, извлекающая количество имеющихся мест, соответствующих запрошенным типу и классу. Оператор в строках 52 и 53 выполняет данный запрос. Ес- ли результат запроса не пустой, тогда программа бронирует первое место, возвращаемое запросом. База данных обновляется командой update, и метод Reserve возвращает true, указывая на успешное завершение операции бронирования. Если результат команды select отрицательный, тогда метод Reserve возвращает false, указы- вая, что мест, удовлетворяющих условиям запроса, нет.
ASP.NET и Web-службы 695 Ранее Web-служба отображалась в режиме конструктора (см. рис. 18.6), и пояснялось, что просмотр в этом ре- жиме позволяет программисту добавлять компоненты в Web-службу. В представленной Web-службе брониро- вания авиабилетов (листинг 18.6) использовались различные компоненты данных. На рис. 18.12 эти компоненты представлены в режиме просмотра. Обратите внимание, что компоненты проще переносить в Web-службу из панели инструментов Toolbox, вместо написания соответствующего кода. В листинге 18.7 представлен файл ASPX для Web-формы, в котором пользователи могут выбирать типы мест. Данная страница дает пользователю возможность бронирования места, исходя из их класса и местоположения в ряду мест. Затем в странице используется Web-служба бронирования для выполнения пользовательского запро- са. Если запрос базы данных не выполняется, тогда пользователю предлагается изменить запрос и сделать его повторно. Листинг 18.7. Файл ASPX, принимающий информацию о бронировании .. ..... ... ......................... 1 2 3 4 5 б 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 <%— Листинг 18.7: TicketReservation.aspx —%> <%— Web-форма для выбора пользователями типа места, —%> <%— которое они хотят забронировать —%> <%@ Page language="c#" Codebehind="TicketReservation.aspx.cs" AutoEventWireup="false" Inherits="MakeReservation.TicketReservation" %> <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN" > <HTML> <HEAD> <meta name="GENERATOR" Content="Microsoft Visual Studio 7.0"> <meta name="CODE_LANGUAGE" Content="C#"> <meta name="vs_defaultClientScript" content="JavaScript (ECMAScript)"> <meta name="vs_targetSchema" content="http://schemas.microsoft.com/intellisense/ie5"> </HEAD> cbody MS_POSITIONING="GridLayout"> <form id="MakeReservation" method="post" runat="server"> <asp:DropDownList id="seatList" style="Z-INDEX: 101; LEFT: 16px; POSITION: absolute; TOP: 43px" runat="server" Width="105px" Height="22px"> <asp:ListItem Value="Aisle">Aisle</asp:ListItem> <asp:ListItem Value="Aisle">Middle</asp:ListItem> <asp:ListItem Value="Aisle">Window</asp:ListItem> </asp:DropDownList> <asp:DropDownList id="classList" style="Z-INDEX; 102; LEFT: 145px; POSITION: absolute; TOP: 43px" runat="server" Width="98px" Height="22px"> <asp:Listitem Value="Economy">Aisle</asp:Listltem> <asp:ListItem Value="First">Aisle</asp:ListItem> </asp:DropDownList> <asp:Button id="reserveButton" style="Z-INDEX: 103; LEFT: 21px; POSITION: absolute; TOP 83px" runat="server" Text="Reserve"> </asp:Button> <asp:Label id="Labell" style="Z-INDEX: 104; LEFT: 17px; POSITION: absolute; TOP: 13px”
696 Гпава 18 49 runat="server">Рlease select the type of seat and 50 class you wish to reserve: 51 </asp:Label> 52 1 53 </form> 54 </body> 55 </HTML> Web-страница, представленная в листинге 18.7, определяет два объекта DropDownLiSt и объект Button. Один объект DropDownLiSt отображает все типы мест, из которых пользователи могут делать свой выбор. Во втором перечислены возможности выбора типа класса. Пользователи нажимают кнопку Button с именем reserveButton для подачи запросов после того, как в объекте DropDownLiSt сделан выбор. Файл фонового кода (листинг 18.8) присваивает этой кнопке обработчик события. В строках 30 и 31 создается объект Reservation. При нажатии пользователем кнопки Reserve выполняется об- работчик события reserveButton Click, и страница перезагружается. Обработчик события (строки 48—63) вы- зывает метод Reserve Web-службы и передает ему в качестве аргументов типы выбранного места и класса. Если метод Reserve возвращает true, тогда программа отображает сообщение благодарности пользователю за бро- нирование; в противном случае пользователь уведомляется об отсутствии запрошенного места с предложением повторить запрос позже (рис. 18.13). 1 // Листинг 18.8: TicketReservation.aspx.cs 2 // Бронирование с помощью Web-службы 3 4 using System; 5 using System.Collections; 6 using System.ComponentModel; 7 using System.Data; 8 using System.Drawing; 9 using System.Web; 10 using System.Web.Sessionstate; 11 using System.Web.UI; 12 using System.Web.UI.WebControls; 13 using System.Web.UI.HtmlControls; 14 15 namespace MakeReservation 16 { 17 // посетители могут выбрать тип места для бронирования 18 // с последующим бронированием 19 public class TicketReservation : System.Web.UI.Page 20 { 21 protected System.Web.UI.WebControls.DropDownLiSt 22 seatList; 23 protected System.Web.UI.WebControls.DropDownLiSt 24 classList; 25 26 protected System.Web.UI.WebControls.Button 27 reserveButton; 28 protected System.Web.UI.WebControls.Label Labell; 29 30 private localhost.Reservation agent = 31 new localhost.Reservation(); 32 33 private void Page_Load( 34 object sender, System.EventArgs e) 35 { 36 if (IsPostBack) 37 { 38 seatList.Visible = false; 39 classList.Visible = false;
ASP.NETи Web-службы 697 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 reserveButton.Visible = false; Labell.Visible = false; } } // код, сгенерированный Visual Studio .NET // вызов Web-службы дляпопытки бронирования указанного места public void reserveButton_Click ( object sender, System.EventArgs e) { // метод Web-службы возвращает true - сигнал успешного бронирования if (agent.reserve(seatList.Selectedltem.Text, classList.Selectedltem.Text)) Response.Write("Your reservation has been made." + " Thank you."); // метод Web-службы возвращает false - сигнал сбоя else Response.Write("This seat is not available, " + "please hit the back button on your browser " + "and try again."); } // конец метода reserveButton_Click } // конец класса TicketReservation j* } // конец пространства имен MakeReservation б в Рис. 18.13. Работа приложения бронирования авиабилетов: а — выбор места и класса; б— бронирование выполнено, в — выбор места и класса; г — сообщение о невозможности бронирования выбранного места 18.7. Учебный пример: программа определения температуры В данном учебном примере рассматриваются Web-служба прогнозов погоды в разных городах США и прило- жение Windows, использующее эту Web-службу. В последней применяются сетевые функции для отображения
698 Глава 18 прогнозов; выполняется синтаксический анализ Web-страницы, содержащей нужную информацию с последую- щим извлечением данных прогноза погоды. Сначала в листинге 18.9 представляется Web-служба TemperatureServer. Она считывает Web-страницу и соби- рает информацию о температуре и погодных условиях в нескольких городах США. Примечание___________________________________________________________________ На время издания книги данная программа работала так, как описано Однако при модификации Web-страницы, из которой программа получает данные, последняя может работать иначе (либо совсем не работать). Обновле- ния представлены на сайте www.deitel.com. «Листинг 18.9. Web-служба TeinperatureSex'vej' 1 // Листинг 18.9: TenperatureServer.asmx.cs 2 // Web-служба TemperatureServer; извлекающая из Web-страницы 3 // информацию о погоде 4 5 using System; 6 using System.Collections; 7 using System.ComponentModel; 8 using System.Data; 9 using System.Drawing; 10 using System.Web; 11 using System.Web.SessionState; 12 using System.Web.UI; 13 using System.Web.UI.WebControls; 14 15 namespace MakeReservation 16 { 17 [WebService(Namespace » http://www.deitel.com/cspfepl/chl8/, 18 Description = "A Web service that provides information " + 19 "from the National Weather Service.")] 20 public class TempteratureServer : 21 System.Web.Services.WebService 22 { 23 // код, сгенерированный Visual Studio .NET 24 25 [WebMethod(EnableSession = true, Description = 26 "Method to read information from the weather service.")] 27 public void UpdateWeatherConditions() 28 { 29 // создание WebClient для получения 29a // доступа к Web-странице 30 WebClientmyClient = new WebClient(); 31 ArrayList cityList new ArrayList(); 32 33 // получение Streamreader для ответа, чтобы считать страницу 34 StreamReader input = new StreamReader( 35 myClient.OpenRead( 36 "http://iwin.nws.noaa.gov/iwin/us/ + 37 "traveler.html")); 38 39 string separator = "TAV12"; 40 41 // обнаружение первой горизонтальной черты в Web-странице 42 while (!input.ReadLine().StartsWith( 43 separator)); // действий не предпринимать 44 45 // формат дневного и ночного времени 46 string dayFormat = 47 "CITY WEA HI/LO WEA " + 48 "HI/LO";
ASP.NET и Web-службы 699 49 50 51 52 53 54 54а 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 string nightFormat = "CITY WEA LO/HI WEA " + "LO/HI"; string inputLine = ""; // обнаружение заголовка, с которого // начинается информация о погоде do { inputLine = input.ReadLine(); } while (!inputLine.Equals(dayFormat) && ! inputLine.Equals(nightFormat)); // получение данных о первом городе inputLine = input.ReadLine(); while (inputLine.Length > 28) { // создание объекта CityWeather для города CityWeather weather = new CityWeather( inputLine.Substring(0, 16), inputLine.Substring(16, 7), inputLine.Substring(23, 7)); // добавление в список cityList.Add(weather); // получение данных следующего города inputLine = input.ReadLinef); ) // закрытие подключения к серверу NWS input.Close(); // добавление списка городов в сеанс пользователя Session.Add("cityList", cityList); } // конец метода UpdateWeatherConditions // получение названий всех городов [WebMethocl (EnableSession = true, Description = "Method to retriece a list of cities.")] public string[] Cities() { ArrayList cityList = (ArrayList) Session("cityList"]; stringf] cities= new string[cityList.Count]; // получение названий городов for (int i = 0; i < cityList.Count; i++) { CityWeather weather = (CityWeather) cityList[i]; cities[i] = weather.CityName; } return cities; } // конец метода Cities // получение описаний всех городрв [WebMethod(EnableSession = true, Description = "Method" + " to retrieve weather description for a ” + "list of cities.")] /
700 Глава 18 ill 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 public string.[] Description () { ArrayList cityList = (ArrayList) Session["cityList’’]; string [] description new string[cityList.Count]; // получение описаний погода для всех городов for (int i = 0; i < cityList.Count; i++) { CityWeather weather = (CityWeather) cityList [i]; descriptions[i] • weather.Description; } return descriptions; ) // конец метода Descriptions // получение значений температуры в каждом городе [WebMethod (EnableSession = true, Description = ’’Method " + "to retrieve the temperature for a list of cities.")] public stringl] Temperatures() { ArrayList cityList = (ArrayList) Session["cityList"]; string[] temperatures= new string[cityList.Count]; // получение значений температуры во всех городах for (int i = 0; i < cityList.Count; i++) { CityWeather weather - (CityWeather) cityList[i]; temperatures[i] = weather.Temperature; } return temperatures; } // конец метода Temperatures } // конец класса TemperatureServer 149 ) // конец пространства имен TemperatureWebService Метод UpdateWeatherConditions, собирающий информацию о погоде с Web-страницы, — первый WebMethod, который клиент должен вызвать в Web-службе. Служба также предоставляет Web-методы cities, Descriptions и Temperatures, возвращающие разные типы информации, относящейся к прогнозам погоды. При активизации метода UpdateWeatherConditions (строки 25—85) он подключается к Web-сайту, содержаще- му прогнозы Национальной метеорологической службы (National Weather Service, NWS). В строке 30 создается объект WebClient, используемый потому, что класс WebClient спроектирован для взаимодействия с источником, определенным URL. В данном случае URL для страницы NWS — http://iwin.nws.noaa.gov/iwin/us /traveler.html. В строках 34--—37 вызывается метод OpenRead класса WebClient; этот метод извлекает stream (по- ток) из URL, содержащий информацию о погоде, после чего использует данный поток для создания объекта StreamReader. С помощью объекта StreamReader программа может построчно считывать разметку HTML- страницы. Интересуемый раздел Web-страницы начинается со строки "TAV12". Следовательно, строки 42 и 43 считывают по одной строке разметки HTML до тех пор, пока не попадется строка. По достижении строки "TAV12" цикл do/while (строки 55—59) продолжает считывать страницу построчно до достижения строки заголовка (т. е. строки перед таблицей прогнозов). Данная строка начинается либо с объекта dayFormat, обозначающего днев- ное время, либо с объекта nightFormat, указывающего ночное время. Из-за того, что данная строка может иметь любой из этих форматов, структура проверяет их. В строке 62 считывается следующая строка страницы, яв- ляющаяся первой строкой, содержащей информацию о температуре. С помощью цикла while (строки 64—77) создается новый объект CityWeather для обозначения текущего горо- да. Данный объект проводит синтаксический анализ строки, содержащей текущие данные о погоде с разделени- ем названия города, погодных условий и температуры. Объект CityWeather добавляется в cityList (ArrayList,
ASP.NET и Web-службы 701 содержащий список городов, их описания и текущую температуру воздуха); затем считывается очередная стро- ка страницы и сохраняется в inputLine для следующей итерации. Данный процесс продолжается до тех пор, пока длина строки, считываемой из Web-страницы, не будет меньше или равна 28 Это — сигнал окончания раздела температуры воздуха. В строке 83 объект ArrayList cityList добавляется к объекту Session так, что значения последовательно поддерживаются между вызовами методов. Методом Cities (строки 88—105) создается массив строк, которые могут содержать столько элементов string, сколько элементов имеется в объекте cityList. В строке 92 список городов извлекается из объекта Session. Строки 96—101 итерируются через каждый объект cityweather в cityList и вводят название города в массив, возвращаемый в строке 103. Поведение методов Descriptions (строки 108—126) и Temperatures (строки 129— 145) аналогичное, за исключением того, что они возвращают описания погоды и значения температуры воздуха соответственно. В листинге 18.10 содержится код для класса cityweather. Конструктор принимает три аргумента: название го- рода, описание погодных условий и текущую температуру воздуха. Данный класс предоставляет свойства CityName, Temperature и Description; их значения могут быть получены Web-службой. 1 // Листинг 18.10: CityWeather.cs 2 // Класс, представляющий информацию о погоде в одном городе 3 4 using System; 5 6 namespace TemperatureWebService 7 { 8 public class CityWeather 9 { 10 private string cityName; 11 private string temperature; 12 private string description; 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 public CityWeather( string city, string information, string degrees) { cityName = city; description = information; temperature = degrees; } // название города public string CityName { get { return cityName; } 1 // температура воздуха в городе public string Temperature » { get { return temperature; } } // описание прогноза public string Description { get
702 Глава 16 44 { 45 return description; 46 } 47 } 48 49 } //‘конец класса CityWeather 50 } // конец пространства имен TemperatureWebService Приложение Windows в листинге 18 31 использует Web-службу Temperatureserver для отображения информа- ции о погоде в удобном для пользователя формате. Temperatureclient (листинг 18.11) — это приложение Windows, использующее Web-службу Temperatureserver для наглядного и удобочитаемого отображения информации о погоде. Окно приложения содержит 36 меток (Label), размещенных в два столбца. Каждая метка Label отображает информацию о погоде для отдельно взято- го города (рис. 18.14). J™;™"™” ’4‘Я •’• -т »!»• Mt-w га. -.-w Лист инг 1811. Получение из Web-службы значений темперитуры и информации с оогиде 1 // Листинг 18.11: Client.cs 2 // Класс, отображающий информацию о погоде, 3 // получаемую из Web-службы 4 5 using System; 6 using System.Drawing; 7 using System.Collections; 8 using System.ComponentModel; 9 using System.Windows.Forms; 10 using System.Net; 11 12 namespace Temperatureclient 13 { 14 public class Client : System.Windows.Forms.Form 15 { 16 private System.Windows.Forms.Label labell; 17 private System.Windows.Forms.Label label2; 18 * private System.Windows.Forms.Label label3; 19 private System.Windows.Forms.Label label4; 20 private System.Windows.Forms.Label label5; 21 private System.Windows.Forms.Label label6; 22 private System.Windows.Forms.Label label7; 23 private System.Windows.Forms.Label label8; 24 private System.Windows.Forms.Label label9; 25 private System.Windows.Forms.Label labellO; 26 private System.Windows.Forms.Label labelll; 27 private System.Windows.Forms.Label labell2; 28 private System.Windows.Forms.Label labell3; 29 private System.Windows.Forms.Label labell4; 30 private System.Windows.Forms.Label labell5; 31 private System.Windows.Forms.Label labell6; 32 private System.Windows.Forms.Label labell7; 33 private System.Windows.Forms.Label labell8; 34 private System.Windows.Forms.Label labell9; 35 private System.Windows.Forms.Label label20; 36 private System.Windows.Forms.Label label21; 37 private System.Windows.Forms.Label label22; 38 private System.Windows.Forms.Label label23; 39 private System.Windows.Forms.Label label24; 40 private System.Windows.Forms.Label label25; 41 private System.Windows.Forms.Label label26; 42 private System.Windows.Forms.Label label27; 43 private System.Windows.Forms.Label label28; 44 private System.Windows.Forms.Label label29; 45 private System.Windows.Forms.Label label30; 46 private System.Windows.Forms.Label label31;
ASP.NET и Web-службы 703 41 private System.Windows.Forms.Label label32; 48 private System.Windows.Forms.Label label33; 49 private System.Windows.Forms.Label label34; 50 private System.Windows.Forms.Label label35; 51 private System.Windows.Forms.Label label36; 52 53 private System.ComponentModel.Container components = 54 null; 55 56 public Client() 57 { 58 InitializeComponent(); 59 60 localhost.temperatureserver client = 61 new localhost.temperatureServer(); 62 client.CookieContainer = new CookieContainer(); 63 client.UpdateWeatherConditions(); 64 65 string[] cities = client.Cities(); 66 string[] descriptions = client.Descriptions(); 67 stringl] temperatures = client.Temperatures(); 68 69 label35.Backgroundimage = new Vitmap( 70 "images/header.png"); 71 Iabel36/Backgroundlmage « new Bitmap( 72 "images/header.png"); 73 74 // создание хэш-таблицы и ее заполнение всеми метками 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 Hashtable cityLabels = new Hashtable(i; cityLabels.Add(1, labell); cityLabels.Add(2, label2); cityLabels.Add(3, label3); cityLabels.Add(4, label4); cityLabels.Add(5, label5); cityLabels.Add(6, label6); cityLabels.Add(7, label?); cityLabels.Add(8, label8); cityLabels.Add(9, label9); cityLabels .Add(10, labellO); cityLabels.Add(11, labelll); cityLabels.Add(12, labell2); cityLabels.Add(13, labell3); cityLabels.Add(14, labell4); cityLabels.Add(15, labell5); cityLabels.Add(16, Labell6); cityLabels.Add(17, labell7); cityLabels.Add(18, labell8); cityLabels.Add(19, labell9); cityLabels.Add(20, label20); cityLabels.Add(21, label21); cityLabels.Add(22, label22); cityLabels.Add(23, label23); cityLabels .Add(24, label24) к cityLabels.Add(25, label25); cityLabels.Add(26, label26); cityLabels.Add(27, label27); cityLabels.Add(28, label28); cityLabels.Add(29, label29); cityLabels.Add(30, label30); cityLabels.Add(31, label31); cityLabels.Add(32, label32); cityLabels.Add(33, label33); ci tyLabels.Add(34, labe!34);
704 Гпава 18 Ill 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 139a 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 11 создание хэш-таблицы и заполнение ее // информацией обо всех погодных условиях Hashtable weather - new Hashtable(); weather.Add("SUNNY", "sunny"); weather.Add("PTCLDY", "pcloudy"); weather.Add("CLOUDY", "mcloudy"); weather.Add("MOCLDY", "mcloudy"); weather.Add("TSTRMS", ”rain"); weather.Add("RAIN", "rain"); weather.Add("SNOW", "snow"); weather.Add("VRYHOT", "vryhot") ; weather.Add("FAIR", "fair"); weather .Add ("RNSNOW", "msnow") ; weather.Add("SHWRS", "showers"); weather.Add("WINDY", "windy"); weather.Add("NOINFO", "noinfo"); weather.Add("MISG", "noinfo"); weather.Add("DRZL", "rain"); weather.Add("HAZE", "noinfo"); weather.Add("SMOKE", "mcloudy"); Bitmap background = new Bitmap("images/back.png"); Font font - new Font("Courier New", 8, FontStyle.Bold); // для каждого города for (int i = 0; i < cities.Length; i++) { // использование cityLabels хэш-таблицы 11 для нахождения следующей метки Label currentcity « (Label)cityLabels[i +1]; // установка изображения текущей метки // для отображения погодных условий в городе: найти нужное // название изображения в погодных условиях хэш-таблицы currentCity.Image - new Bitmap("images/" + weather[descriptions[i J.Trim()] + ".png"); // установка фонового изображения, // гарнитуры и цвета переднего плана метки currentcity.Backgroundimage = background; currentCity.Font = font; currentCitу.ForeColor = Color.White; // установка текста метки: название города currentcity.Text = "\r\n" + cities[i] + "” + temperatures[i]; } } // конец конструктора // код, сгенерированный Visual Studio .NET [STAThread] static void Main() { Application.Run(new Client()); ) ) // конец класса Client ) // конец пространства имен TemperatureClient
ASP.NET и Web-службы 705 Рис. 18.14. Приложение, позволяющее получить сведения о температуре и погоде В строках 60—63 конструктора создается объект TemperatureServer, НОВЫЙ объект CookieContainer и обнов- ляются данные о погоде вызовом метода UpdateWeatherConditions. В строках 65—67 вызываются методы Cities, Descriptions и Temperatures класса TemperatureServer ДЛЯ извлечения информации и О погоде в горо- де, и ее описания. Из-за того что в приложении представлены данные о погоде в большом количестве городов, необходимо установить метод организации информации в метках (Label) и удостовериться, что каждому описа- нию погоды сопутствует соответствующее изображение. Для выполнения этих задач в программе предназначен класс Hashtable (подробно рассматривается в главе 20) для сохранения всех меток, описаний погодных условий и названий всех соответствующих им изображений. В хэш-таблице сохраняются пары "ключ/значение", где ключ и значение могут быть объектом любого типа. Метод Add добавляет пары "ключ/значение" в таблицу Hashtable. Этот класс также предоставляет индексатор для возвращения ключевого значения, по которому ин- дексирована хэш-таблица. В строке 75 создается объект Hashtable, а в строках 76—109 метки добавляются в класс Hashtable с использованием в качестве ключей чисел от 1 до 36. Затем в строке 113 создается второй объ- ект Hashtable (weather), содержащий пары погодных условий и связанных с этими погодными условиями изо- бражения. Обратите внимание, что отдельно взятое описание погоды не обязательно соответствует имени файла PNG, содержащего корректное изображение. Например, погодные условия "tstrms" и "rain" используют один файл rain.png. В строках 137—157 каждая метка задается так, что она содержит название города, текущую температуру возду- ха в нем и изображение, соответствующее погодным условиям в этом городе. В строке 140 используется индек- сатор Hashtable для получения следующей метки путем передачи в качестве аргумента текущего значения i плюс 1. Единица прибавляется потому, что индексатор Hashtable начинается с нуля, несмотря на то, что ключи как меток, так и класса Hashtable пронумерованы от 1 до 36. В строках 145—146 изображение метки устанавливается на изображение PNG, соответствующее погодным ус- ловиям города. Программа выполняет эту задачу извлечением имени изображения PNG из объекта weather класса Hashtable. Программа удаляет все пробелы в строковом описании путем вызова метода Trim. В строках 150—156 задается несколько свойств меток для получения визуального эффекта, представленного на выходе. Для каждой метки устанавливается сине-черное фоновое изображение (строка 150). В строках 155 и 156 задает- 45 Зак. 3333
706 Глава 18 ся текст каждой метки так, что для каждого города отображается корректная информация (т. е. название города и температура воздуха в нем). 18.8. Пользовательские типы в Web-службах Web-служба, рассмотренная в предыдущем разделе, возвращает массивы строк (string). Было бы гораздо удоб- нее, если бы объект TemperatureServer мог возвращать массив объектов CityWeather вместо массива строк. К счастью, в Web-службе можно определить и развернуть пользовательские типы. Эти типы можно передавать в методы Web-службы и получать из них. Клиенты Web-службы могут пользоваться пользовательскими типами, потому что созданный для клиента прокси-класс содержит определения этих типов. Впрочем, при работе с пользовательскими типами в Web-службах стоит помнить об определенных тонкостях: авторы обратят на них внимания читателя при разборе следующего примера. В качестве учебного примера в данном разделе представлена программа обучения математике. Web-служба ге- нерирует произвольные уравнения типа Equation. Клиент вводит информацию о типе необходимого пользова- телю математического примера (сложение, вычитание или умножение), а также уровень знаний пользователя (I создает уравнения с одноразрядными числами, 2 — более сложные уравнения с двухразрядными числами, а 3 — самые сложные уравнения с трехразрядными числами). Затем создается уравнение, состоящее из произ- вольных чисел, содержащих нужное количество цифр (разрядов). Клиент получает Equation и использует фор- му Windows для представления пользователю примерных вопросов. Ранее отмечалось, что все типы данных, передаваемые в Web-службы и получаемые из них, должны поддержи- ваться протоколом SOAP. А как SOAP может поддерживать еще не созданный тип? В главе 14 обсуждалось преобразование типов данных в последовательную форму (сериализация), позволяющее выполнить их запись в файлы. Подобным же образом в последовательную форму преобразуются пользовательские типы, передаваемые в Web-службу или извлекаемые из нее, что дает возможность их передачи в формат XML. Данный процесс на- зывается сериализацией XML (XML-serialization). При определении объектов для возврата из методов Web-службы нужно учитывать несколько тонкостей. На- пример, любой объект, возвращаемый методом Web-службы, должен иметь конструктор по умолчанию. Не- смотря на то, что создать можно любой объект с помощью конструктора по умолчанию типа public (даже если он не определен явно), класс, возвращаемый из Web-службы, должен иметь явно определенный конструктор, даже если его тело пустое. Распространенная ошибка программирования______________________________________________ Результатом отсутствия явного определения конструктора public для типа, используемого в Web-службе, явля- ется ошибка выполнения. К пользовательским типам в Web-службах также предъявляется несколько дополнительных требований. Любые переменные пользовательского типа, доступ к которым осуществляется с клиентской стороны, должны быть объявлены как public. Также необходимо определить процедуры доступа get и set любых свойств, доступ к которым планируется во время выполнения программы. Web-служба должна иметь способ извлечения этих свойств и манипуляций ими, потому что объекты пользовательского типа будут преобразованы в XML (при се- риализации), а затем — обратно в объекты (при отмене сериализации). Ёо время сериализации должно считы- ваться значение свойства (через процедуру get); во время отмены сериализации должно задаваться значение свойства нового объекта (через средство set). При наличии только одного из этих двух средств доступа клиент- ское приложение не получит доступа к свойству. Распространенная ошибка программирования______________________________________________ Результатом определения только процедуры доступа get или set свойства пользовательского типа, применяе- мого в Web-службе, становится свойство, недоступное для клиента. Распространенная ошибка программирования______________________________________________ Клиенты Web-службы могут получить доступ только к элементам public этой службы. Для доступа к данным private программисту необходимо обеспечить свойства public. В листинге 18.12 представлен класс Equation. Вызываемый конструктор (строки 18—37) принимает три аргу- мента: два целых числа, обозначающих левый и правый операнды, и строку, соответствующую алгебраической операции для выполнения. Определяется конструктор по умолчанию (строки 13—15), вызывающий другой кон- структор (строки 18—37) и передающий определенные значения по умолчанию. Данный конструктор задает поля left, right и operation, после чего рассчитывает результат Конструктор по умолчанию не используется, но он должен быть определен в программе.
ASP.NET и Web-службы 707 Класс Equation определяет свойства LeftHandSide, Right hands ide, Left, Right, Operation и Result. Программе нет необходимости изменять значения каких-либо из этих свойств, однако необходимо обеспечить реализацию средства доступа set. Свойство LeftHandSa.de возвращает строку, обозначающую все, что расположено слева от знака =, а свойство RightHandSide возвращает строку, обозначающую все, что расположено справа от знака =. Свойство Left возвращает int в левую часть оператора (называемого левым операндом), свойство Right воз- вращает int в правую часть оператора (называемого правым операндом). Свойство Result возвращает решение уравнения, а свойство Operation — оператор. На самом деле, программе не требуется свойство RightHandSide, но оно включено в пример на случай, если оно потребуется другим клиентам. В листинге 18.13 представлена Web-служба Generator, создающая заказные уравнения Equations со случайными коэффициентами. 1 // Листинг 18.12: Equation.cs 2 // Класс Equation, содержащий 3 // информацию об уравнении 4 5 using System; 6 7 public class Equation 8 { 9 private int left, right, result; 10 private string operation; IT 12 // необходимый конструктор по умолчанию 13 public Equation() : this(0, 0, 14 { 15 } 16 17 // конструктор для класса Equation 18 public Equation(int leftvalue, int rightvalue, 19 string operationType) 20 { 21 Left = leftvalue; 22 Right = rightvalue; 23 Operation = operationType; 24 25 switch (opeartionType) 26 { 27 case 28 Result = Left + Right; 29 break; 30 case 31 Result = Left — Right; 32 break; 33 case 34 Result = Left * Right; 35 break; 36 } 37 } 38 39 public override string ToString() 40 { 41 return Left.ToStringO + "" + Operation + "" + 42 Right. ToSt ring () + " = " + Result.Tostring(); 43 ) 44 45 // свойство, возвращающее строку, обозначающую 46 // левую сторону 47 public string LeftHandSide 48 { 49 get
708 Глава 18 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 ( return Left.Tostring() + "" + Operation + "" + Right.Tostring(); } set { } } // свойство, возвращающее строку, обозначающую II правую сторону public string RightHandSide { get { return Result.ToString(); } set { 1 } // левый операнд получает и задает свойство public int Left { get 1 return left; ) set { left = value; } } // правый операнд получает и задает свойство public int Right { get __ { return right; 1 set { right = value; } } // получение и задание свойства результата после применения // операции к левому и правому операндам public int Result { get { return result; } set {
ASP.NET и Web-службы 709 113 result = value; 114 } 115 ) 116 117 // получение и установка свойства для операции 118 public string Operation 119 { 120 get 121 { 122 return operation; 123 | 124 125 set 126 { 127 operation = value; 128 ' } 129 } 130 131 } // конец класса Equation 1 // Листинг 18.13: Generator.asmx.es 2 // Web-служба, генерирующая уравнения со случайными коэффициентами 3 //на основе заданных операций и уровня сложности 4 5 using System; 6 using System.Collections; 7 using System.ComponentModel; 8 using System.Data; 9 using System.Diagnostics; 10 using System.Web; 11 using System.We.Services; 12 13 namespace EquationGenerator 14 { 15 [WebService(Namespace = http://www.deitel.com/cspfepl/chl8, 16 Description = "A Web service that generates questions ” + 17 "based on the specified mathematical operation and " + 18 "level of difficulty chosen.")] 19 public class Generator : System.Web.Services.WebService 20 { 21 22 // код, сгенерированный Visual Studio .NET 23 24 [WebMethod (Description = 25 "Method that generates a random equation.")] 26 public Equation GenerateEquation(string operation, 27 int level) 28 { 29 // поиск максимального и минимального чисел 30 int maximum = (int) Math.Pow(10, level), 31 minimum = (int) Math.Pow(10, level — 1); 32 33 Random random = new Random(); 34 35 // создание уравнения, состоящего из двух случайных чисел 36 // между минимальным и максимальным параметрами 37 Equation equation = new Equation( 38 random. Next (minimum, maximum), 39 random. Next (minimum, maximum), operation); 40
710 Глава 18 41 return equation; 42 43 } // конец метода GenerateEquation 44 45 } // конец класса Generator 46 47 } // конец пространства имен EquationGenerator Web-служба Generator содержит только один метод — Generat eEquation. В качестве аргументов данный метод принимает строку (string), представляющую математическое действие, и целое число (integer), представляю- щее желаемый уровень сложности уравнения. На рис. 18.15 показан результат выполнения 'тестового вызова данной Web-службы. Обратите внимание, что значение, возвращаемое методом Web-службы, размечено как XML (рис. 18.16). Впрочем, данный пример отличается от предыдущих тем, что XML задает значения для всех свойств public и полей возвращаемого объекта. Возвращаемый объект переведен в XML. Прокси-класс прини- мает возвращаемое значение и преобразовывает его в объект (содержащий данные public первоначального объекта), передаваемый обратно клиенту. Рис. 18.15. Ввод данных в форму 3http: localhost Equationbenerator'Gerneralor.asnw < АДОък l^http://localx>st/EquabonGenerator/Generator.asmx/Generate£quation?operation-^ievel-^] <^Go Links * y.ki.-Jr.i,, imrtiwB»fMmwiiamTihirniinira«MBbat.Mimdniwwm.irii.vwuHM;ai&e.tn1 тгг-•• i “иг; <г-в11-иш1в«1В1а..т.;ю;»м,м1гшмм.»ти.----^ i vt ... ,й„л--.-.ва.й. <?xml verslon="1.0" encodlng="utf-8‘' ?> - <Equation xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns: xsl="http://www. w3.org/2001/XMLSchema instance" xrrilns="http://www.deitel.com/csphtpl/ch21"> <LeftHand£ ide>44 - 28</LeftHandSide> <RightHandS.de> 16</RightHandSlde> <Left>44</Left> <Ri ght > 28 </Right > <Result> 16</Result> <Operatlon>-</Operation> </Equallon> ц Рис. 18.16. Возвращение объекта из метода Web-службы В строках 30 и 31 задаются нижний и верхний пределы для случайных чисел, генерируемых данным методом. Для их задания программа сначала вызывает метод Pow типа static класса Math; данный метод возводит первый аргумент в степень второго аргумента Целое число maximum представляет верхний предел для случайно сгене- рированного числа. Программа возводит 10 в степень заданного аргумента level, после чего передает это зна-
ASP.NET и Web-службы 711 чение в качестве верхнего предела. Например, если аргумент level равен 1, то maximum— 10; если level — 2, то maximum— 100, и т. д Значение переменной minimum определяется возведением 10 в степень на единицу меньшую, чем level. При этом рассчитывается наименьшее число с количеством разрядов level. Если level — 2, то minimum— 10; если level — 3, то minimum— 100, И Т. д. В строках 37—39 создается новый объект Equation. Программа вызывает метод Next класса Random, который возвращает целое число, превышающее или равное заданному нижнему пределу, но меньшее, нежели заданный верхний предел. В данном примере Random генерирует значение левого операнда, превышающее или равное minimum, но меньшее, чем maximum (т. е. число с количеством разрядов level). Правый операнд — это другое случайное число с такими же характеристиками. Операция, передаваемая в конструктор Equation, является строковой операцией, полученной методом GenerateEquation. Возвращается новый объект Equation. В листинге 18.14 приведено обучающее математическое приложение, использующее Web-службу Generator. Программа вызывает метод GeneratorEquation класса Generator для создания объекта Equation. Затем про- грамма отображает левую сторону уравнения Equation и ожидает данных, которые должен ввести пользователь. В этом примере приложение имеет доступ изнутри пространства localhost как к классу Generator, так и к клас- су Equation: при генерировании прокси-класса оба класса размещены в данном пространстве имен. Обучающая программа отображает вопрос и ожидает ввода. Настройка уровня сложности по умолчанию— 1, однако пользователь в любой момент может ее изменить выбором уровня в нижнем ряду переключателей RadioButton. При щелчке кнопкой мыши на любой опции уровня активизируется обработчик события levelRadioButton Click (строки ПО—120), задающий целое число level для выбранного пользователем уров- ня. Несмотря на то, что настройка по умолчанию для типа вопроса — Addition, пользователь также может ее изменить выбором любого из переключателей в верхнем ряду. При этом активизируется обработчик события operationRadioButton_ciick (строки 91—107), задающий строковую операцию так, что она содержит символ, соответствующий пользовательскому выбору. 1 // Листинг 18.14: Tutor.cs 2 // Обучающая математическая программа 3 4 using System; 5 using System.Drawing; 6 using System.Collections; 7 using System.ComponentModel; 8 using System.Windows.Forms; 9 10 namespace EquationGeneratorClient 11 < 12 public class Tutor : System.Windows.Forms.Form 13 { 14 private System.Windows.Forms.Panel panel1; 15 private System.Windows.Forms.Panel panel2; 16 17 private System.Windows.Forms.Label questionLabel; 18 private System.Windows.Forms.TextBox answerTextBox; 19 private System.Windows.Forms.Button okButton; 20 private System.Windows.Forms.Button generateButton; 21 22 private System.Windows.Forms.RadioButton oneRadioButton; 23 private System.Windows.Forms.RadioButton twoRadioButton; 24 private System.Windows.Forms.RadioButton 25 threeRadi©Button; 26 private System.Windows.Forms.RadioButton addRadioButton; 27 private System.Windows.Forms.RadioButton 28 subtractRadioButton; 2 9 private System.Windows.Forms.RadioButton 30 multiplyRadioButton; 31 32 private System.ComponentModel.Container components = 33 null; 34 private int level = 1; 35
712 Глава 18 36 37 38 39 40 41 42 43 44 45 46 47 48 49 5Ь 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 private localhost.Equation equation; private localhost.Generator generator = new localhost.Generator(); private string operation = "+"; // код, сгенерированный Visual Studio .NET [STAThread] static void Main() { Application.Run(new Tutor()); } // генерирование нового вопроса при щелчке кнопкой мыши protected void generateButton_Click(object sender, System. EventArgs e) { // генерирование уравнения с помощью текущей операции // и уровня equation = generator.GenerateEquation(operation, level); // отображение левой части уравнения questionLabel.text « equation.LeftHandSide; okButton.Enabled = true; answerTextBox.Enabled = true; } // конец метода generateButton_Click // проверка ответа пользователя protected void okButtorrClick(object sender, System EventArgs e) { // определение правильного результата из // объекта Equation int answer = equation.Result; // получение ответа пользователя int meAnswer = Int32.Parse(answertextBox.Text); // проверка правильности ответа пользователя if (answer == myAnswer) ( questionLabel.Text = ""; answerLabel.Text> = ""; okButton.Enabled = false; MessageBox.Show("Correct! Good job!"); } else MessageBox.Show("Incorrect. Try again."); } // конец метода okButton_Click // задать выбранную операцию protected void operationRadioButtons_Click(object sender, EventArgs e) { RadioButton item = (RadioButton) sender; // задание соответствующего символа операции if (item = addRadioButton) operation = "+";
ASP.NET и Web-службы 713 99 else if (item == subtractRadioButton) 100 operation = 10i else 102 operation = 103 104 generateButton.Text = "Generate " + item.Text + 105 " Example"; 106 107 } // конец метода OperationRadioButtons_Click 108 109 // установка текущего уровня 110 protected void levelRadioButtons_Click(object sender, 111 EventArgs e) 112 { 113 if (sender — oneRadioButton) 114 level = 1; 115 else if (sender == twoRadioButton) 116 level = 2; 117 else 118 level = 3; 119 120 } // конец метода levelRadioButtons_Click 121 122 } // конец класса Tutor 123 124 } // конец пространства имен EquationGeneratorClient Обработчик события generateButton_Click (строки 50—64) активизирует метод generateEquation класса Generator. Левая часть уравнения отображается в questionLabel (строка 59), и активизируется кнопка okButton: пользователь может ввести ответ. При нажатии пользователем кнопки ОК метод okButton_ciick (строки 67— 88) проверяет ответ на предмет его правильности (рис. 18.17). Рис. 18.17. Работа математического приложения: а — программа вывела пример сложения; б — пользователь ввел ответ; в — сообщение о неверном ответе пользователя; г — пользователь ввел новый ответ; д — сообщение о верном результате; е — сгенерировано новое математическое выражение В настоящей и предыдущей главах читатели познакомились с созданием Web-приложений и Web-служб, даю- щих пользователям возможность запроса и получения данных через Интернет. В следующей главе рассматри- ваются подробности низшего уровня: передача данных из одного местоположения в другое (этот процесс назы- вается сетевой организацией). В число тем, рассматриваемых в следующей главе, входит реализация серверов и клиентов, а также передача данных по сокетам.
714 Глава 18 18.9. Резюме Web-службой называется программное приложение, сохраненное на удаленной машине, доступ к которому осуществляется через удаленный вызов процедуры. Вызов методов Web-службы реализуется с помощью SOAP — протокола на базе XML, описывающего разметку запросов и ответов на них так, что они могут пере- даваться, например, по протоколу HTTP. Эти методы размечаются атрибутом WebMethod и часто называются методами Web-службы. По умолчанию запросы Web-служб и ответы на них передаются как сообщения SOAP. Пока клиент способен создавать и расшифровывать сообщения SOAP, он может пользоваться Web-службой, независимо от языка программирования, на котором она написана. Web-служба в .NET Framework состоит из двух частей: файла ASMX и файла фонового кода. Файл ASMX мож- но просмотреть в любом Web-браузере: в нем отображена информация о Web-службе. Файл фонового кода со- держит определения методов Web-службы. Описание службы — это документ XML, соответствующий языку описания Web-службы (WSDL). WSDL — это словарь XML, описывающий поведение Web-служб. Описание службы может использоваться клиентской про- граммой для подтверждения корректности обращения к методу во время компиляции. Протокол SOAP не зависит от платформы и использует XML для описания удаленных обращений к процедурам по HTTP. Запросы методов Web-службы и ответы на них объединены в пакеты в сообщениях SOAP — XML- сообщениях, содержащих всю информацию, необходимую для обработки содержимого. Протокол SOAP дает Web-службе возможность использования различных типов данных, включая определенные пользователем. При активизации программой метода Web-службы запрос и вся относящаяся к нему информация объединяется в сообщении SOAP и передается по назначению. Когда Web-служба получает сообщение SOAP, она обрабатыва- ет его содержимое, указывающее метод, который клиент хочет использовать, и аргументы, передаваемые кли- ентом в этот метод. После этого метод исполняется с отправкой клиенту ответа в виде другого сообщения SOAP. Программное приложение, использующее Web-службу, состоит из двух частей: прокси-класса для Web-службы и клиентского приложения, осуществляющего доступ к Web-службе через прокси. Прокси-класс управляет про- цессом передачи аргументов от клиента в сообщение SOAP, отправляемое в Web-службу. Подобным же обра- зом прокси управляет передачей информации в SOAP-ответ клиенту. Прокси-класс создается из файла WSDL Web-службы, обеспечивающего вызов клиентом методов Web-службы по Интернету. Всякий раз при вызове метода Web-службы в клиентском приложении метод вызывается в прокси-классе. Этот метод получает имя и аргументы, переданные клиентом, и форматирует их так, что их можно отправлять как запрос в виде сообщения SOAP. Класс webservice предоставляет элементы, определяющие информацию о пользователе, программном прило- жении и других аспектах, относящихся к Web-службе. Последняя не обязательно наследует от класса WebService. Программист задает класс как Web-службу, помечая его атрибутом WebService. UDDI (Universal Description, Discovery and Integration, универсальное описание, обнаружение и интеграция) — это проект разработки набора спецификаций, определяющих обнаружение Web-службы клиентами. Файл DISCO (discovery) указывает все Web-службы, имеющиеся в текущем каталоге. Существуют два типа файлов обнаружения: файлы динамического обнаружения (с расширением vsdisco) и файлы статического обнаружения (с расширением disco). При создании Web-ссылки файл статического обнаружения размещается в проекте кли- ента. Этот файл содержит местоположения файлов ASMX и WSDL. В файлы динамического обнаружения вхо- дит список Web-служб, создаваемый при поиске клиентом той или иной Web-службы. Для сохранения информации о сеансе свойство EnableSession атрибута WebMethod должно иметь значение true. При сохранении информации о сеансе Web-служба должна иметь возможность идентификации пользователей между вызовами методов. Данный подход реализуется с помощью cookies, хранящихся в объекте CookieContainer. Программист может определять типы и использовать их в Web-службе. Типы можно передавать в методы Web- службы и извлекать из методов. Определенные пользователем типы можно передавать методы Web-службы и извлекать, потому что они определены в прокси-классе, созданном для клиента. Пользовательские типы, от- правляемые в Web-службу и извлекаемые из нее, преобразуются в форму XML. При возвращении объекта из Web-службы все свойства public и поля размечаются в XML-документе. Впоследствии эту информацию можно передавать назад в объект на стороне клиента.
ГЛАВА 19 (ш Организация сетей: сокеты на основе потоков и дейтаграммы Если наличие электричества в любой части сети можно сделать видимым, то я не понимаю, почему интеллект нельзя передавать вместе с электричеством. Сэмюэл Ф. Б. Морзе Г-н Ватсон, идите сюда, вы мне нужны. Александр Грэм Белл То, что раньше было сетями железных дорог, шоссе и каналов,... теперь пре- вратилось в сети телекоммуникаций, информации й компьютеризации. Бруно Крейски Совершенней системы передачи тишины, чем перерыв на кофе, не существует. Эрл Уилсон Темы данной главы: □ реализация сетевых программных приложений С#, использующих сокеты и дейтаграммы; □ реализация взаимодействия клиентов и серверов С#; □ реализация сетевых совместных приложений; □ создание многопоточного сервера. 19.1. Введение Появление Интернета и Всемирной паутины (WWW) поистине взбудоражило деловое и компьютерное сообще- ства. Интернет связывает "информационный мир" воедино; Web упрощает использование Интернета с одно- временным обеспечением определенных мультимедийных возможностей. Сегодня практически все компании рассматривают Интернет и Web как основные движущие факторы стратегий развития своих информационных систем. C# и .NET Framework предлагают множество интегрированных возможностей сетевой организации, упрощающих разработку программных приложений на базе Интернета и Web. Язык C# не только способен ус- танавливать параллелизм посредством организации многопоточной обработки информации, но и осуществлять ее поиск и взаимодействовать с программами, работающими на других компьютерах в любой точке мира. В главах 17 и 18 мы приступили к рассмотрению возможностей организации сетевой и распределенной обра- ботки информации. В этих главах обсуждались Web-формы и Web-службы — две высокоуровневые сетевые технологии, обеспечивающие создание на языке C# распределенных программных приложений. В настоящей главе основное внимание уделено сетевым технологиям, поддерживающим возможности ASP.NET С#, которые могут применяться при построении распределенных приложений. Обсуждение сетевой организации затрагивает обе части взаимодействия "клиент-сервер". Клиент делает за- прос на выполнение определенной операции; сервер выполняет эту операцию и выдает клиенту ответ. Обычно реализация такой модели "запрос — ответ" осуществляется между Web-браузерами и Web-серверами. Когда пользователи выбирают сайт для просмотра через браузер (клиентское приложение), браузер выполняет запрос к соответствующему Web-серверу (серверное приложение). Сервер, как правило, отвечает клиенту отправкой нужных страниц в формате HTML. Сетевые возможности языка C# сгруппированы в нескольких пространствах имен. Фундаментальные функции сетевой организации определены классами и интерфейсами пространства имен System.Net.Sockets. Через дан- ное пространство имен C# обеспечивает соединения на базе сокетов, что дает разработчикам возможность про-
716 Глава 19 сматривать организацию сети в виде обычного файла ввода/вывода. Это означает, что программа может осуще- ствлять считывание информации из сокета (сетевое соединение) и запись в сокет так же легко, как осуществля- ется считывание информации из обычного файла и запись в него. Использование сокетов — основополагающий способ сетевой организации в .NET Framework. Термин "сокет" произошел от Berkeley Sockets Interface (интер- фейс сокетов Беркли), созданного в 1978 году для целей сетевого программирования в UNIX и популяризован- ных программистами С и C++. Классы и интерфейсы пространства имен System.Net .Sockets также предлагают соединения на базе пакетов, по которым передаются отдельные пакеты информации; этот*— обычный способ передачи аудио- и видеомате- риалов по Интернету. В настоящей главе рассматривается создание сокетов, манипуляции ими, а также соеди- нения через пакеты данных. Соединения на базе сокетов в C# задействуют потоковые сокеты. С их помощью один процесс (выполняющая- ся программа) устанавливает соединение с другим процессом. При наличии соединения обмен данными между процессами осуществляется в виде непрерывных потоков. По этой причине говорится, что потоковые сокеты обеспечивают службу маршрутизации. Популярный протокол TCP (Transmission Control Protocol, протокол управления передачей) упрощает передачу информации по потоковым сокетам. С другой стороны, соединения на базе пакетов в C# используют дейтаграммные сокеты, по которым переда- ются отдельные пакеты информации. В отличие от TCP, протокол, используемый для активизации дейта- граммных сокетов — UDP (User Datagram Protocol, пользовательский протокол данных) — является службой, независящей от соединения (connectionless), не гарантирующей передачу пакетов в каком-либо заданном поряд- ке. Пакеты могут теряться, дублироваться или поступать без учета последовательности отправки. Для решения таких проблем приложения, использующие UDP, часто требуют значительной программной доработки. Лучше всего UDP подходит для сетевых приложений, не требующих проверки ошибок и надежности TCP. Например, пользователи сетевых игр применяют UDP, потому что в играх скорость передачи данных намного важнее, чем безупречная точность. Для подавляющего большинства программистов, работающих с С#, потоковые сокеты и протокол TCP будут наиболее предпочтительными способами коммуникации. Совет по повышению производительности____________________________________________________ Службы, независящие от соединения, как правило, демонстрируют хорошую производительность, но меньшую надежность, нежели службы маршрутизации. Совет по повышению портативности_________________________________________________________ Протокол TCP и связанный с ним набор протоколов обеспечивают взаимодействие широкого спектра разнород- ных компьютерных систем (т. е. систем с разными процессорами и разными операционными системами). 19.2. Создание простого сервера с помощью потоковых сокетов Обычно при использовании TCP и потоковых сокетов сервер "ждет" от клиента запроса на соединение. Сервер- ная программа часто содержит управляющую структуру или блок кода, исполняющийся непрерывно до тех пор, пока сервер не получит запрос. При получении запроса сервер устанавливает соединение с клиентом, после чего использует данное соединение для обработки последующих запросов от этого клиента и отправки ему данных. ^Организация простого серверного приложения с TCP и потоковыми сокетами в C# требует выполнения пяти шагов. Первый шаг— создание объекта класса TcpListener, принадлежащего пространству имен System.Net.Sockets. Данный класс представляет потоковый сокет TCP, посредством которого сервер "прослу- шивает" запросы. Вызов конструктора TcpListener, например, TcpListener server = new TcpListener(port); связывает (присваивает) сервер с заданным номером порта. Номер порта — это числовой идентификатор, ис- пользуемый процессом для самоидентификации на конкретном сетевом адресе, также называемом адресом IP (Internet Protocol Address, IP address). С помощью IP-адресов компьютеры идентифицируются в Интернете. На самом деле, названия Web-сайтов, например www.deitel.com, являются псевдонимами IP-адресов. Любой про- цесс, выполняющий сетевую обработку информации, самоидентифицируется через пару "IP-адрес/номер пор- та". Поэтому не может возникнуть ситуации, когда два процесса будут иметь одинаковый номер порта на от- дельно взятом IP-адресе. Как правило, в явном связывании сокета с портом (с помощью метода Bind класса Socket) необходимости нет, потому что класс TcpListener и другие рассматриваемые в главе классы скрывают это связывание (т. е. связывают сокеты с портами неявно), а также выполняют другие операции инициализации сокетов.
Организация сетей: сокеты на основе потоков и дейтаграммы 717 Замечание по технологии программирования______________________________________________ Номера портов могут иметь значения от 0 до 65 535. Многие операционные системы резервируют номера портов до 1024 для системных служб (например для электронной почты и Web-серверов). Для использования зарезер- вированных номеров портов программные приложения должны получить особые полномочия. Как правило, в серверном приложении портам соединения не следует задавать номера меньше 1024, потому что они могут быть зарезервированы некоторыми операционными системами. Распространенная ошибка программирования______________________________________________ Попытка связывания уже присвоенного порта на отдельно взятом IP-адресе является логической ошибкой. Для получения запросов класс TcpListener сначала должен их прослушать. Вторым шагом процесса соедине- ния является вызов метода start класса TcpListener, активизирующего начало прослушивания объектом TcpListener запросов на соединение. Третий шаг устанавливает соединение между серверным и клиентским приложениями. Сервер выполняет прослушивание на предмет запросов, т. е. исполнение серверного приложе- ния ожидает попытки соединения со стороны клиента. Сервер создает подключение к клиенту при получении запроса. Объект класса System.Net.Sockets.Socket управляет каждым соединением с клиентом. Метод Acceptsocket класса TcpListener ожидает запроса на соединение и создает его при получении такого запроса. При наличии соединения данный метод возвращает объект socket, как в выражении Socket connection = server.Acceptsocket(); При получении сервером запроса метод Acceptsocket вызывает метод Accept базового объекта Socket класса TcpListener для установления соединения. Это— пример сокрытия в C# сложности сетевой организации от программиста. Последний может написать предшествующее выражение в серверной программе, после чего классы пространства имен System.Net.Sockets будут обрабатывать подробности приема запросов и установле- ния соединений. Четвертый шаг — это фаза обработки, когда сервер и клиент взаимодействуют посредством методов Receive и Send объекта Socket. Обратите внимание, что эти методы, а также TCP и потоковые сокеты можно использовать только при установленном соединении клиента и сервера. В противоположность этому UDP и дейтаграммные сокеты можно использовать при отсутствии соединения посредством методов SentTo и ReceiveFrom класса Socket. Пятый шаг — фаза прерывания соединения. По окончании взаимодействия сервера с клиентом сервер исполь- зует метод close объекта Socket для прекращения соединения. После этого большинство серверов возвращает- ся к шагу 2 (т. е. к ожиданию запроса на соединение другого клиента). Одной из проблем, связанных с описываемой в данном разделе серверной схемой, является то, что в шаге 4 все прочие запросы блокируются при обработке клиентского запроса: другой клиент не сможет подключиться к серверу во время исполнения кода, определяющего фазу обработки. Самым распространенным способом реше- ния данной проблемы является использование многопоточных серверов, размещающих код фазы обработки в отдельном потоке. При получении сервером запроса на соединение он "размножается", т. е. создает подпро- цесс Thread для обработки этого соединения, освобождая класс TcpListener (или Socket) для получения других запросов. Замечание по технологии программирования______________________________________________ С помощью многопоточных возможностей C# можно создавать серверы, управляющие одновременными соеди- нениями нескольких клиентов. Данная многопоточная серверная архитектура — именно то, что используется в сетевых серверах UNIX и Windows. Замечание по технологии программирования______________________________________________ Для создания потока, управляющего сетевым вводом/выводом по ссылке на объект socket, возвращаемый ме- тодом Acceptsocket, можно реализовать многопоточный сервер. То же самое относится к поддержанию накопи- теля потоков, управляющих сетевым вводом/выводом через вновь созданные сокеты. Совет по повышению производительности_________________________________________________ В высокопроизводительных системах с большим объемом памяти многопоточный сервер можно реализовать для создания накопителя потоков, которые можно оперативно присваивать для управления сетевым вво- дом/выводом через каждый множественный сокет. Поэтому при соединении с клиентом сервер не испытывает непроизводительных издержек, необходимых для создания потока. 19.3. Создание простого клиента с помощью потоковых сокетов Клиенты на базе TCP и потоковых сокетов создаются в процессе выполнения четырех шагов. В первом шаге создается объект класса Tepciient (принадлежащий к пространству имен system.Net.Sockets) для соединения
718 Глава 19 с сервером. Данное соединение устанавливается посредством метода Connect класса TcpClient. Одна перегру- женная версия данного метода принимает два аргумента: IP-адрес сервера и номер порта, как в следующем вы- ражении: TcpClient client = new TcpClient(); Client.Connect(serverAddress, serverPort); Здесь объект serverPort имеет тип int и обозначает номер порта сервера; объект serverAddress может быть как экземпляром iPAddress (инкапсулирующим IP-адрес сервера), так и строкой, указывающей имя хоста сер- вера. Либо программист может передать ссылку на объект класса iPEndPoint, представляющий числовую пару "IP-адрес/номер порта", в другую перегрузку метода Connect. Для установления соединения метод Connect класса TcpClient вызывает метод Connect класса Socket. При успешном соединении метод TcpClient.Connect возвращает положительное целое число; в противном случае возвращается 0. В шаге 2 для получения объекта NetworkStream класс TcpClient использует метод Getstream для записи данных на сервер и их считывания с него. Методы WriteByte и Write объекта Networkstream можно использовать для вывода отдельных байтов или наборов байтов на сервере соответственно; аналогичным образом методы ReadByte и Read объекта Networkstream используются для ввода отдельных байтов или наборов байтов с сервера соответственно. Третий шаг— фаза обработки, когда осуществляется взаимодействие клиента с сервером. На данном этапе кли- ент применяет методы Read, ReadByte, Write и WriteByte класса Networkstream для выполнения соответствую- щих операций. С помощью процессов, сходных с используемым сервером, для предотвращения блокирования связи с другими серверами при обработке данных одного соединения клиент может использовать потоки. По завершении передачи четвертый шаг требует от клиента прекращения соединения вызовом метода close объекта Networkstream. При этом закрывается базовый Socket (если Networkstream имеет ссылку на этот сокет). Затем клиент вызывает метод close класса TcpClient для прекращения соединения TCP. Как уже отмечалось, на данном этапе использованием метода connect может быть установлено новое соединение. 19.4. Взаимодействие "клиент-сервер" посредством потоковых сокетов В листингах 19.1 и 19.2 для построения простой программы чата (интерактивная переписка) клиента с серве- ром используются классы и методики, рассмотренные в двух предыдущих разделах. Сервер ожидает запроса клиента на соединение. После соединения клиентского приложения с серверным последнее отправляет клиенту массив байтов, извещающий его об успешном соединении. Затем программа-клиент выдает пользователю со- общение об установлении соединения. Клиентское и серверное приложения содержат текстовые поля (TextBox), в которых пользователь вводит сооб- щения и отправляет их в другое приложение. Когда клиент или сервер отправляют сообщение "terminate", со- единение между ними прерывается. После этого сервер ожидает запроса на соединение другого клиента. В листингах 19.1 и 19.2 представлен код для классов server и client соответственно. На рис. 19.1 представлены графические изображения окон, отображающих процесс взаимодействия клиента с сервером. 1 // Листинг 19.1: Server.cs 2 // Настройка серверного приложения, принимающего запрос на 3 // соединение, с отправкой строки клиенту и закрытие соединения 4 5 6 7 8 9 10 11 12 13 14 15 16 using System; using System.Drawing; us ing System.Collections; using System.ComponentModel; using System.Windows.Forms; using System.Threading; using System.Net.Sockets; using System.IO; 11 сервер, ожидающий соединения с клиентами (по одному) и // обеспечивающий "разговор" клиента с сервером public class Server : System.Windows.Forms.Form
Организация сетей: сокеты на основе потоков и дейтаграммы 719 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 private System.Windows.Forms.TextBox inputTextBox; private System.Windows.Forms.TextBox displayTextBox; private Socket connection; private Thread readThread; private System.ComponentModel.Container components = null; private Networkstream socketstream; private Binarywriter writer; private BinaryReader reader; 11 конструктор по умолчанию public Server() { InitializeComponent(); // создание нового потока от сервера readThread = new Thread(new Threadstart(RunSercer)); readThread.Start(); } II код, сгенерированный Visual Studio .NET [STAThread] static void Main() { Application.Run(new Server()); } protected void Server_Closing( object sender, CancelEventArgs e) { System.Environment.Exit(System.Environment.ExitCode); } // отправка клиенту текста, набранного на сервере protected void inputTextBox_KeyDown( object sender, KeyEventArgs e) { // отправка текста клиенту try { if (e.KeyCode == Keys.Enter && connection != null) { writ er. Write ("SERVER»> " + inputTextBox. Text); displayTextBox.Text += "\r\nSERVER»> " + inputTextBox.Text; // если пользователь на сервере просигнализировал // клиенту конец соединения if (inputTextBox.Text == "TERMINATE") connection.Close(); inputTextBox.Clear(); 1 ) catch (SocketException) { displayTextBox.Text += "\nError writing object"; } } // inputTextBox_KeyDown
720 Глава 19 80 80а 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 II позволяет программе-клиенту подключиться к серверу //и отображает отправляемый ею текст public void RunServer() { TcpListener listener; int counter = 1; // ожидает клиентского соединения и отображает текст, // отправляемый программой-клиентом try { // Шаг 1: созданйе TcpListener listener = new TcpListener(5000); // Шаг 2: TcpListener ожидает запрос на соединение listener.Start(); // Шаг 3: установка соединения по запросу клиента while (true) { displayTextBox.Text = "Waiting for connection\r\n"; // прием входящего соединения connection = listener.AcceptSqcket(); I/ создание объекта NetworkStream', относящегося к сокету socketStream = new Networkstream(connection); // создание объектов для передачи данных по потоку writer = new Binarywriter(socketStream); reader = new BinaryReader(socketstream); displayTextBox.Text += "Connection " + counter + "received.\r\n"; // информирует клиента об успешном соединении writer.Writer("SERVER»> Connection successful"); inputTextBox.readonly = false; string theReply = // Шаг 4: считывание строки данных от клиента do { try { // считывает строку, отправленную на сервер theReply = reader.readstring(); // отображение сообщения displayTextBox.Text += "\r\n" + theReply; ) // обработка исключения при сшиоке считывания данных catch (Exception) { break; } } while (theReply != "CLIENT»> TERMINATE" && connection.Connected);
Организация сетей: сокеты на основе потоков и дейтаграммы 721 141 142 143 144 145 146 147 148 149 150 displayTextBox.Text += "\r\nUser terminated connection”; // Шаг 5: закрытие соединения inputTextBox.readonly = true; writer.Close(); reader.Close(); socketstream.Close(); Connection.Close(); 151 ++ counter; 152 } 153 } // конец try 154 155 catch (Exception error) 156 { 157 MessageBox.Show(erropr.ToString()); 158 } 159 160 } // конец метода RunServer 161 162 ) // конец класса Server i Листинг 19.2. Клиентская часть взаимодействия "клиент-сервер" посредством потокового сокета -------_.......................................................................... ......; 1 // Листинг 19.2: Client.cs 2 // Настройка клиентского приложения, считывающего и отображающего 3 // информацию, получаемую с сервера 4 5 using System; 6 using System.Drawing; 7 using System.Collections; 8 using System.ComponentModel; 9 using System.Windows.Forms; 10 using System.Threading; 11 using System.Net.Sockets; 12 using System.IO; 13 14 // соединение с сервером чата 15 public class Client : System.Windows.Forms.Form 16 { 17 private System.Windows.Forms.TextBox inputTextBox; 18 private System.Windows.Forms.TextBox displayTextBox; 19 20 private Networkstream output; 21 private Binarywriter writer; 22 private BinaryReader reader; 23 24 private string message = 25 26 private Thread readThread; 27 28 private System.ComponentModel.Container components = null; 29 30 // конструктор по умолчанию 31 public Client() 32 { 33 InitializeComponent(); 34 35 readThread = new Threa(new Threadstart(RunCllent)); 3 6 readThread.Start(); 37 } 38 46 Зак. 3333
722 Глава 19 39 // код, сгенерированный Visual Studio .NET 40 41 [STAThread] 42 static void Main() 43 { 44 Application.Run(new Client()>; 45 } 46 47 protected void Client-Closing( 48 object sender, CancelEventArgs e) 49 { 50 System. Environment. Exit (System. Environment. Exitcode); 51 } 52 53 11 отправляет введенный пользователем текст на сервер 54 protected void inputTextBox_KeyDown ( 55 object sender, KeyEventArgs e) 56 { 57 try 58 { 59 if (e.KeyCode == Keys.Enter) 60 { 61 writer.Write.("CLIENT»> " + inputTextBox.Text); 62 63 displayTextBox.Text += 64 ”\r\nCLIENT»> ’’ + input Text Box. Text; 65 66 inputTextBox.Clear(); 67 ) 68 } 69 catch (SdcketException ioe) 70 { 71 displayTextBox.Text += "\nError writing object"; 72 } 73 74 } // конец метода inputTextBox_KeyDown 75 76 // соединение с сервером и отображение текста, 76а // сгенерированного сервером • 77 public void RunClientO 78 { 79 TcpClient client; 80 81 // создание объекта TcpClient для отправки данных на сервер 82 try 83 { 84 displayTextBox.Text +«. "Attempting connection\r\n"; 85 86 // Шаг 1: Создание объекта TcpClient и соединение с сервером 87 client = new TcpClient(); 88 client.Connect("localhost", 5000); 89 90 // Шаг 2: получение класса IJetworkStream, 90a // относящегося к TcpClient 91 output = client. GetStreamO; 92 93 // создание объектов для записи и считывания в потоке 94 writer = new Binarywriter(output); 95 reader = new BinaryReader(output); 96 97 displayTextBox.Text += "\r\nGot I/O streams\r\n"; 98 99 inputTextBox.readonly = false; 100
Организация сетей: сокеты на основе потоков и дейтаграммы 723 101 // цикл до сигнала сервера о прекращении 102 do 103 { 104 105 // Шаг 3: фаза обработки 106 try 107 { 108 // считывание сообщения с сервера 109 message = reader.readstring(); 110 displayTextBox.Text += "\r\n" + message; 111 ) 112 113 // обработка исключения при ошибке во время 113а // считывания данных сервера 114 catch (Exception) 115 { 11^ System.Environment.Exit( 117 System.Environment.ExitCode); 118 } 119 } while (message != "SERVER»> TERMINATE"); 120 121 displayTextBox.Text += "\r\nClosing connection.\r\n"; 122 123 // Шаг 4: закрытие соединения 124 writer.Close(); ; 125 reader.Close(); 126 output.Close(); 127 client.Close(); 128 Application.Exit(); 129 } 130 131 // обработка исключения при ошибке при установлении соединения 132 catch (Exception error) 133 { 134 MessageBox.Show(error.ToString()); 135 .) 136 \ 137 } // конец метода RunClient 138 139 } // конец класса Client Начнем анализ данного примера с обсуждения класса Server (см. листинг 19.1). В конструкторе в строке 34 создается объект Thread, который будет принимать от клиентов запросы на соединение. Объект Thread начина- ется в строке 35, где активизируется метод RunServer (строки 81—160). Метод RunServer инициализирует сер- вер для получения запросов на соединение и их обработки. В строке 91 создается класс TcpListener для про- слушивания клиентского запроса на соединение на порту 5000 (шаг 1). После этого в строке 94 вызывается ме- тод Start объекта TcpListener, и последний начинает ожидание запросов (шаг 2). В строках 97—152 объявляется бесконечный цикл while, устанавливающий запрошенные клиентами соедине- ния (шагЗ). В строке 102 вызывается метод AcceptSocket объекта TcpListener, возвращающий при удачном соединении socket. Поток, в котором вызван метод AcceptSocket, прекращает исполнение до установления со- единения. Управлять соединением будет объект socket. В строке 105 данный объект Socket передается в каче- стве аргумента в конструктор объекта Networkstream. Класс Networkstream обеспечивает доступ к потокам сети: в данном примере объект Networkstream обеспечивает доступ к соединению Socket. В строках 108 и 109 созда- ются экземпляры классов Binarywriter и BinaryReader для записи и считывания данных. Объект Networkstream передается в качестве артумента в каждый конструктор; класс Binarywriter может записывать байты в Networkstream, а класс BinaryReader может считывать байты из класса Networkstream. В строках 111 и 112 в текстовое поле TextBox вводится текст, указывающий на прием соединения. Метод write класса Binarywriter имеет много перегруженных версий, позволяющих методу записывать в по- ток различные типы данных. (Читатель может вспомнить об использовании перегруженных методов в главе 14 для записи данных в файлы.) В строке 115 используется метод write для отправки клиенту строки, уведомляю-
724 Глава 19 щей пользователя об успешном соединении. В строках 121—139 объявляется цикл do/while, выполняющийся до тех пор, пока сервер не получит сообщение о прекращении соединения (т. е. ”client>»terminate")- В стро- ке 126 используется метод Readstring класса BinaryReader для считывания строки из потока (шаг 4) (Этот ме- тод также описывался в главе 14 при считывании из файлов строк имени и фамилии, хранящихся в записи.) Ме- тод Readstring блокируется до тех пор, пока строка не будет считана. Для предотвращения блокирования всего сервера используется отдельный поток Thread для управления передачей информации. Цикл while повторяется, пока имеется информация для считывания; результатом этого становится блокирование ввода/вывода и частое "зависание" программы Если же запустить данную часть программы в отдельном потоке Thread, то пользова- тель может взаимодействовать с формой Windows и отправлять сообщения, пока программа будет ожидать вхо- дящих сообщений. б ж Рис. 19.1. Работа чата: а — клиент получает сообщение об успешном установлении соединения; б — на сервере сообщение, что установлено одно соединение; в — клиент отправил сообщение; г — сервер сообщение получил; д — клиент получил сообщение от сервера; е — отправленное сервером сообщение; ж — прерывание соединения; з — ожидание нового соединения По завершении переписки в строках 146—149 классы Binarywriter, BinaryReader, NetworkStream и Socket за- крываются (шаг 5) активизацией соответствующих им методов Close. После этого сервер ожидает запроса на соединение другого клиента возвращением к циклу while (строка 97).
Организация сетей: сокеты на основе потоков и дейтаграммы 725 Когда пользователь серверного приложения вводит строку в текстовое поле TextBox и нажимает клавишу <Enter>, обработчик события inputTextBox_KeyDown (строки 53—78) считывает строку и отправляет ее посред- ством метода write класса Binarywriter. Если пользователь прекращает работу серверного приложения, тогда в строке 69 вызывается метод Close объекта Socket для закрытия сеанса связи. В строках 46—50 определяется обработчик Server closing для события closing. Данное событие закрывает приложение и использует метод System.Environment.Exit С параметром System.Environment.Exitcode ДЛЯ пре- рывания всех потоков. Метод Exit класса Environment закрывает все потоки, относящиеся к приложению. В листинге 19.2 представлен код для объекта client. Подобно объекту Server, объект client создает поток Thread (строки 35 и 36) в его конструкторе для обработки всех входящих сообщений. Метод Runclient объекта Client (строки 77—137) соединяется с объектом Server, получает с него данные и отправляет их на Server (при нажатии пользователем клавиши <Enter>). В строках 87 и 88 создается объект TcpCiient, затем вызывается его метод Connect для установления соединения (шаг 1). Первым аргументом для метода Connect в данном случае является имя сервера— "localhost", означающее, что серверное приложение размещено на той же машине, что и клиент, localhost также называется IP-адресом обратной связи и эквивалентно IP-адресу 127.0.0.1. Это значение направляет передачу данных назад на IP-адрес отправителя. Примечание_____________________________________________________________________________ В рассматриваемом примере авторы демонстрируют взаимодействие "клиент-сервер" установлением связи ме- жду программами, размещенными на одном компьютере (localhost). Как правило, данный аргумент содержит адрес другого компьютера в Интернете. Вторым аргументом для метода Connect будет номер порта сервера. Этот номер должен совпадать с номером порта, на который сервер ожидает соединение с клиентом. Объект Client использует класс Networkstream для передачи данных на сервер и для их получения с сервера. Клиент получает класс Networkstream в строке 91 обращением к методу Getstream класса TcpCiient (шаг 2). Цикл do/while в строках 102—109 повторяется до тех пор, пока клиент не получит сообщения о прекращении соединения ("SERVER»> TERMINATE"). В строке 109 используется метод Readstring класса BinaryReader для получения с сервера текстового сообщения (шаг 3). В строке 110 отображаются сообщения, а в строках 124— 137 объекты Binarywriter, BinaryReader, Networkstream и TcpCiient закрываются (шаг 4). Когда пользователь клиентского приложения вводит строку в текстовое поле и нажимает клавишу <Enter>, об- работчик события inputTextBox KeyDown (строки 54—74) считывает эту строку из текстового поля и отправляет ее с помощью метода Write класса Binarywriter. Обратите внимание, что в данном случае объект Server полу- чает запрос на соединение, обрабатывает его, закрывает и ожидает следующего. В реальном приложении сервер получил бы запрос на соединение, установил его для обработки в виде отдельного потока и ожидал бы новых соединений. Отдельные потоки, обрабатывающие существующие соединения, могут продолжать свою работу, в то время как объект server ожидает новых запросов на соединение. 19.5. Взаимодействие системы “клиент-сервер" с дейтаграммами независимо от наличия соединения До сих пор рассматривалась передача на основе соединений и потоков. В данном разделе обсуждается передача данных независимо от соединения, с помощью дейтаграмм. Передача информации на основе соединения напоминает использование системы телефонной связи, когда поль- зователь набирает номер и соединяется с телефоном абонента. Система поддерживает соединение в течение телефонного вызова, независимо от того, разговаривают абоненты или нет. В противоположность этому передача без установки соединения — посредством дейтаграмм — больше напо- минает метод пересылки и доставки почты. При передаче без установления соединения информация объединя- ется в пакеты, называемые дейтаграммами, которые можно сравнить с отправленными письмами. Если боль- шое сообщение не помещается в один конверт, то оно разбивается на части и помещается в отдельные последо- вательно пронумерованные конверты. Все письма отправляются одновременно. Они могут доходить до адресата в указанном порядке, без соблюдения порядка, либо не доходить вообще. Перед интерпретацией со- держимого сообщения адресат упорядочивает его части. Если сообщение не очень большое и может размес- титься в одном конверте, тогда задача упорядочивания снимается, но при этом остается вариант, что сообщение никогда не дойдет до адресата (В отличие от отправленной почты, на принимающие компьютеры могут посту- пать дубликаты дейтаграмм.) Для передачи информации независимо от соединения в C# предусмотрен класс UdpClient. Подобно классам TcpListener и TcpCiient, класс UdpClient использует методы класса Socket. Ме- тоды Send и Receive класса UdpClient предназначены для передачи данных с помощью метода SendTo класса Socket и для считывания данных с помощью метода ReceiveFrom класса Socket соответственно.
726 Глава 19 Программы в листингах 19.3 и 19.4 используют дейтаграммы для передачи пакетов информации между клиент- ским и серверным приложениями (рис. 19.2 и 19.3). В приложении Client пользователь вводит сообщение в текстовое поле и нажимает клавишу <Enter>. Клиент преобразует сообщение в массив byte и отправляет его в серверное приложение. Сервер получает пакет, отображает его информацию и отражает (возвращает) пакет клиенту. При получении пакета клиент отображает содержащуюся в нем информацию. В данном примере реа- лизации классов Client и server сходны. Листинг 19.3. Серверная часть взаимодействия "клиент-сервер" независимо от соединения 1 > .i . 1 // Листинг 19.3: Server.cs 2 // Настройка серверного приложения, принимающего пакеты информации 3 //от клиента и передающего пакеты информации клиенту 4 5 using System; 6 using System.Drawing; 7 using System.Collections; 8 using System.ComponentMode1; 9 using System.Windows.Forms; 10 using SystemTbata; 11 using System.Net; -12 using System.Net.Sockets; 13 using System.Threading; 14 15 // создание сервера UDP 16 public class Server : System.Windows.Forms.Form 17 { 18 private System.Windows.Forms.TextBox displayTextBox; 19 private UdpClient client; 20 private IPEndPoint receivePoint; 21 private System.ComponentModel.Container components = null; 22 23 // конструктор без аргументов 24 public Server() 25 { 26 InitializeComponent(); 27 28 client = new UdpClient(5000); 29 receivePoint = new IPEndPoint(new IPAddress(O), 0); 30 Thread readThread = new Thread{ 31 new Threadstart(WaitForPackets)); 32 33 readThread.Start(); 34 } 35 36 // код, сгенерированный Visual Studio .NET 37 ' 38 [STAThread] 39 static void Main() 40 { 41 Application.Run(new Server()); 42 } 43 44 // закрытие сервера 45 protected void Server_Closing( 46 object sender, CancelEventArgs e) 47 . { 4 8 System.Environment.Exit(System.Environment.ExitCode); 49 } 50 4 51 // ожидание поступления пакета 52 public void WaitForPackets()
Организация сетей сокеты на основе потоков и дейтаграммы 727 53 { 54 while (true) 55 { 56 // получение массива байтов от клиента 57 byte[] data = client.Receive(ref receivePoint); 58 59 // вывод пакетных данных в TextBox 60 displayTextBox.Text += "\r\nPacket received:" + 61 "\r\nLength: " + data.Length + "\r\nContaining: " + 62 System.Text.Encoding.ASCII.GetString(data); 63 64 displayTextBox.Text += 65 "\r\n\r\nEcho data back to client..."; 66 67 // отражение информации пакета назад клиенту 68 client.Send(data, data.Length, receivePoint); 69 displayTextBox.Text += "\r\nPacket sent\r\n"; 70 } 71 72 } // конец метода WaitForPackets 73 74 } // конец класса Server к 5Server Sistas CortarinsFHogranming пСй n funl ? P' e Echo date back to dent. k4-' ..“sr^3 ’ ; Рис. 19.2. Работа серверной части приложения Рис. 19.3. Работа клиентской части приложения: а — окно до отправления пакета сервером; б — окно после получения пакета с сервера и отправки его обратно на сервер "“’г ---------—*----—---------_ Листинг 19.4. Клиентская часть взаимодействия "клиент-сервер" независимо от соединения 1 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 // Листинг 19.4: Client.cs // Настройка клиентского приложения // на отправляющего пакеты сервер и считывающего пакеты с серверного приложения using using using using using using using using using System; System.Drawing; System.Collect ions; System.ComponentModel; Systern.Windows.Forms; System.Data; System.Net. System.Net.Sockets; System.Threading; // запуск клиента UDP public class Client : System.Windows.Forms.Form
728 Глава 19 Y1 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 зз 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 . 70 71 72 73 74 75 76 77 78 private System.Windows.Forms.TextBox inputTextBox; private System.Windows.Forms.TextBox displayTextBox; private UdpClient client; private IPEndPoint receivePoint; private System.ComponentModel.Container components = null; // конструктор без аргументов public Client() { InitializeComponent(); receivePoint = new IPEndPoint(new IPAddress(0), 0); client = new UdpClient(5001); Thread thread = new Thread(new Threadstart(WaitForPackets)); thread.Start(); } If код, сгенерированный Visual Studio..NET (STAThread] static void Main() { Application.Run(new Client()); } // закрытие клиентского приложения protected void Client_Closing( object sender> CancelEventArgs e) ( System.Environment.Exit(System.Environment.Exitcode); } // отправка пакета protected void inputTextBox_KeyDown( object sender, KeyEventArgs e) ( if (e.KeyCode = Keys.Enter) { // создание пакета (дейтаграммы) в виде строки string packet • inputTextBox.Text; displayTextBox.Text += "\r\nSending packet containing: " + packet; // преобразование пакета в массив байтов byte[] data = System.text.Encoding.ASCII.GetBytes(packet); // отправка пакетов в серверное приложение на порт 5000 client.Send(data, data.Length, “localhost", 5000); displayTextBox.Text += "\r\nPacket sent\r\n"; inputTextBox.Clear(); ) } // конец метода inputTextBox_KeyDown // ожидание поступления пакетов public void WaitForPackets() { while (true)
Организация сетей: сокеты на основе потоков и дейтаграммы 729 79 { 80 // получение массива байтов от сервера 81 byte[] data = client.receive(ref receivePoint); 82 83 // вывод пакета данных в текстовое поле 84 displayTextBox.Text += "\r\nPacket received: " + 85 "\r\nLength: " + data.Length + ”\r\nContaining: " + 86 System.Text.Encoding.ASCII.GetString(data) + 87 "\r\n"; 88 } 89 90 } // конец метода WaitForPackets 91 92 } // конец класса Client Код, представленный в листинге 19.3, определяет класс Server для приложения. В строке 28 конструктора для класса Server создает экземпляр класса UdpClient, принимающий данные на порте 5000. При этом инициализи- руется базовый socket для соединений. В строке 29 создается экземпляр класса IPEndPoint для сохранения IP- адреса и номера порта клиента (клиентов), осуществляющего передачу данных в класс Server. Первый аргумент конструктора для класса IPEndPoint— объект iPAddress; второй аргумент конструктора для класса IPEndPoint — номер порта конечной точки. Оба эти значения равны 0, потому что необходимо создать пустой объект IPEndPoint. IP-адреса и номера портов клиентов копируются в класс IPEndPoint при получении дейта- грамм от клиентов. Метод WaitForPackets класса Server (строки 52—72) выполняет бесконечный цикл, ожидая поступления дан- ных в класс Server. При поступлении информации метод Receive класса UdpClient (строка 57) получает от кли- ента массив byte. Метод Receive включен в объект IPEndPoint, созданный в конструкторе; это обеспечивает данный метод ссылкой на класс IPEndPoint, в который программа копирует IP-адрес и номер порта клиента. Программа скомпилируется и будет выполняться без исключений, даже если ссылка на объект IPEndPoint име- ет значение null, потому что метод Receive инициализирует IPEndPoint, если его значение — null. О хорошем стиле программирования_________________________________•____________________________ Рекомендуется инициализировать все ссылки на объекты (на значение, отличное от null). Этим код защищает- ся от методов, не проверяющих свои параметры на предмет наличия нулевых (null) ссылок. В строках 60—65 отображение класса Server обновляется для включения информации о пакете и его содержи- мого. В строке 68 данные отражаются назад к клиенту с помощью метода Send объекта UdpClient. Данная вер- сия send принимает три аргумента: массив байтов для отправки, int, представляющий длину массива, и объект IPEndPoint, в который будут отправлены данные. Массив data, возвращаемый методом Receive, используется в качестве данных, длина массива byte— в качестве длины, а объект IPEndPoint, передаваемый в метод Receive, — в качестве места назначения данных. IP-адрес и номер порта клиента, отправившего данные в класс Server, сохраняются в объекте receivePoint, поэтому простая передача receivePoint в метод Send позволяет классу Server ответить на запрос клиента. Класс client (см. листинг 19.4) работает почти так же, как и класс Server, за исключением того, что объект Client отправляет пакеты только при вводе пользователем сообщения в текстовое поле TextBox и нажатии кла- виши <Enter>. Когда эти операции имеют место, вызывается обработчик события inputTextBox KeyDown (стро- ки 54—73). В строках 65 и 66 преобразуется строка, введенная пользователем в текстовое поле TextBox в мас- сиве byte. В строке 69 вызывается метод Send объекта UDPClient для отправки массива byte в серверное при- ложение Server, размещенное на localhost (на той же машине). Указывается номер порта — 5000, известный как номер приложения Server. В строке 32 создается объект UdpClient для получения пакетов на порт 5001; порт 5001 выбран потому, что Server уже занял порт 5000. В методе WaitForPackets класса Client (строки 76—90) используется бесконечный цикл, ожидающий поступления пакетов данных. Метод Receive объекта UdpClient блокируется до получения пакетов информации (строка 81). Блокирование, осуществляемое методом Receive, не влияет на выполнение классом client других служб (например, обработки введенных пользователем данных), потому что методом WaitForPackets управляет отдельный поток. При поступлении пакета данных в строке 84—87 в текстовом поле отображается его содержимое. Пользователь может ввести информацию в поле TextBox класса client и в любой момент нажать клавишу <Enter>, даже в момент получения пакета. Обработчик события для объекта TextBox обрабатывает это событие и отправляет информацию на сервер. '
730 Глава 19 19.6. Игра "Крестики-нолики" клиента с сервером через многопоточный сервер В данном разделе приведен коронный пример сетевой организации: популярная игра "Крестики-нолики", разра- ботанная с помощью потоковых сокетов и методик взаимодействия "клиент-сервер". Программа состоит из приложения Server (листинг 19.5) и двух приложений client (см. листинг 19.6). Программа server позволяет программе client подключиться к серверу и играть в "крестикй-нолики". Выходные данные представлены в листинге 19.6. При соединении клиента с сервером в строках 72—83 листинга 19.5 создается экземпляр класса Flayer для обработки клиента в отдельном потоке управления. Это позволяет серверу управлять запросами обо- их клиентов. Сервер присваивает значение "X" первому осуществившему соединение клиенту (игрок х делает первый ход), а затем присваивает значение "О" второму клиенту. По ходу игры сервер поддерживает информа- цию независимо от состояния игрового поля так, что подтверждается правомерность запрошенных игроками ходов. Однако ни сервер, ни клиент не могут определить выигрыш того или иного игрока: в данной программе метод GameOver (строки 143—147) всегда возвращает значение false. Каждое приложение client поддерживает свою версию интерфейса игрового поля "крестиков-ноликов". Игрок может ставить кресты или нули только на пустые клетки поля. Класс Square (см. листинг 19.7) используется для определения клеток игрового поля. С ... ..ТГ7»'"....................................... ^..3...-^...^^^-------------------------------------------- ----------- Листинг 19.5. Серверная часть взаимодействия "клиенг-сеовер” лрь игре "Kpet тики-нолики' < •.***. 4. . . . > at . ..............и» XI. .......... mJw .- 44.44.44 » > « Л, »«П«чМ*4» 4»Л 1 // Листинг 19.5: Server.cs 2 // Класс поддерживает игру "Крестики-нолики" для 3 // двух клиентских приложений 4 5 using System; б using System.Drawing; 7 using System.Collections; 8 using System.ComponentModel; 9 using System.Windows.Forms; 10 using System.Data; 11 using System.Net.Sockets; 12 using System.Threading;' 13 using System.IO; 14 15 // ожидание соединений от двух клиентов и обеспечение их 16 // игры "Крестики-нолики” друг против друга 17 public class Server : System.Windows.Forms.Form 18 { 19 private System.Windows.Forms.TextBox displayTextBox; 20 21 private byte[] board; 22 23 private Player[] players; 24 private Thread!] playerThreads; 25 26 private TcpListener listener; 27 private int currentPlayer; 28 private Thread getPlayers; 2? 30 private System.ComponentModel.Container components = null; 31 32 internal bool disconnected = false; 33 34 // конструктор по умолчанию 35 public Server() 36 { 37 InitializeComponent(); 38 39 . board = new byte [9];. 40 41 players = new Player[2]; 42 playerThread = new Thread[2]; 43 currentPlayer =0; 44
Организация сетей: сокеты на основе потоков и дейтаграммы 731 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 // прием соединений на другом потоке getPlayers = new Thread(new Threadstart(Setup)); getPlayers.Start(); } 11 код, сгенерированный Visual Studio .NET [STAThread] static void Main(> { Application.Run(new Server()); protected void Server_Closing( object sender, CancelEventArgs e) { disconnected = true; 1 // прием соединений от двух игроков public void Setup() { // настройка класса Socket listener = new TcpListener(5000); listener.Start(); // прием первого игрока и запуск для него потока players[0] = new Player(listener.AcceptSocket(), this, 0); playerThreads[0] = new Thread( new Threadstart(players[0].Run)); playerThreads[0].Start(); // прием второго игрока и запуск для него потока players[1] = new Player(listener.AcceptSocket(), this, 1); playerThreads[1] = new Thread(new Threadstart(players[1].Run)); playerThreads[1].Start(); // извещение первого игрока о подключении // второго lock (players[0]) { players[0].threadSuspended = false; Monitor.Pulse(players[0]); } ) // конец метода Setup // добавление аргумента тексту в displayTextBox public void Display(string message) { displayTextBox.Text += message + "\r\n"; ' ) 11 определение допустимости хода public bool ValidMove(int location, int player) { // запрещение ходов другому потоку lock (this) { // текущий игрок ожидает хода противника while (player != currentPlayer) Monitor.Wait(this);
732 Глава 19 110 111 112 113 114 115 116 117 П8 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 S.45 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 // если нужная клетка не занята if (!IsOccupied(location)) { // отобразить в поле знак текущего игрока board[location] = (byte) (currentPlayer — 0 ? X’ : ’O’); // смена текущего игрока currentPlayer = (currentPlayer + 1) % 2; // уведомить другого игрока о ходе players[currentPlayer].OtherPlayerMoved(location); 11 предупредить другого игрока о необходимости сделать ход Monitor.Pulse(this); return true; } else return false; } } // конец метода ValidMove // определение занятости указанной клетки public bool IsOccupied(int location) { if (board[location] == 'X' I I board[location] == 'O') return true; else return false; } // определение окончания игры public bool GameOver() ( // разместить код для определения победителя return false; ) ) // конец класса Server public class Player { internal Socket connection; private Networkstream socketstream; private Server server; private Binarywriter writer; private BinaryReader reader; private int number; private char mark; internal bool threadSuspended = true; // конструктор, требующий объектов Socket, Server и int //в качестве аргументов public Player(Socket socket, Server servervalue, int newNumber) { mark = (newNumber — 0 ? 'X' : 'O'); connection = socket; server = servervalue; number = newNumber;
Организация сетей: сокеты на основе потоков и дейтаграммы 733 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 // создание объекта Networkstream для объекта Socket socketstream = new Networkstream(connection); // создание потоков Stream для считывания/записи байтов writer = new BmaryWriter (socket St ream) ; reader = new BinaryReader(socketStгеалй ; } // конец конструктора // уведомление другого игрока о ходе public void OtherPlayerMoved(int location) { // сигнал о ходе противника writer.Write("Opponent moved"); writer.Write(location); // отправка местоположения хода ) // игроки могут делать ходы и принимать ходы // друг от друга public void Run() { bool done = false; // отображение на сервере успешного соединения server.Display("Player " + (number — 0 ? 'X' : 'О') + " connected"); 11 отправка знака текущего игрока ла сервер writer.Writer (mark); // если число равно 0, тогда игрок — X writer.Write("Player ” + (number == 0 ? "X connected\r\n" : "О connected, please wait\r\n")); // ожидание подключения другого игрока if (mark = 'X') { writer.Write("Waiting for another player"); // ожидание уведомления от сервера о подключении fl другого игрока lock (this) { while (threadSuspended) Monitor.Wait(this); } writer.Writer("Other player connected. Your move"); } // конец if // игра while (!done) { // ожидание доступности данных while (connection.Available == 0) { Thread.Sleep(1000); if (server.disconnected) return; }
734 Глава 19 237 // получение данных 238 int location = reader.Readlnt32(); 239 240 // если ход допустим, отобразить его на 241 // сервере и проинформировать о допустимости хода 242 if (server.ValidMove(location, number)) 243 { 244 server.Display("loc: " + location); 245 writer.Write("Valid move."); 1 246 ) 247 248 // проинформировать о недопустимости хода 249 else 250 writer.Write("Invalid move, try again"); 251 252 // если игра окончена, установить done на true для выхода из while 253 if (server.GameOver()) 254 done = true; 255 256 } // конец цикла while x 257 258 // прекращение соединения с сокетом 259 writer.Close(); 2 60 reader.Close(); 261 socketstream.Close(); 2 62 connection.Close(); 263 264 } // конец метода Run 265 266 } // конец класса Player В приложении Server (листинг 19.5) используется конструктор (строки 35—48) для создания массива byte, со- храняющего сделанные игроками ходы (строка 39). Программа создает массив из двух ссылок на объекты Player (строка 41) и массив из двух ссылок на объекты Thread (строка 42). Каждый элемент обоих массивов соответствует игроку в "крестики-нолики". Переменная currentplayer имеет значение 0, соответствующее иг- року "X". В данной программе игрок "X" делает первый ход (строка 43). В строках 46 и 47 создается и начинает исполняться объект getPlayers потока Thread, используемый объектом Server для приема соединений так, что текущий поток Thread не блокирует работу программы во время ожидания игроков. Поток getPlayers исполняет метод Setup (строки 65—92), создающий объект TcpListener для прослушивания запросов на порт 5000 (строки 68 и 69). Затем этот объект прослушивает запросы на соединение от первого и второго игроков. В строках 72—73 и 79—80 создаются объекты Player, обозначающие игроков, а в строках 74—75 и 81—82 формируются два потока Thread, исполняющие методы Run каждого объекта Player. Конструктор Player (листинг 19.5, строки 165—181) принимает в качестве аргументов ссылку на объект Socket (т. е. подключение к клиенту), ссылку на объект server и переменную int, обозначающую знаки ("X" — кре- стик, "О" — нолик) игроков. В данном примере приложение Server вызывает метод Run (строки 193—264) по- сле создания объекта Player. В строках 198—206 сервер уведомляется об успешном соединении, и клиенту пе- редается символ (char), который будет помещаться на игровое поле при ходе..Если метод Run исполняется для игрока (Player) "X", то отрабатывают строки 211—221, и Player "X" ждет подключения второго игрока. В строках 217 и 218 определяется цикл while, приостанавливающий поток (Thread) игрока "х" до тех пор, пока сервер не просигнализирует подключение игрока "О". Сервер уведомляет игрока о соединении присвоением переменной threadSuspended объекта Player значения false (строка 89). Когда ThreadSuspended-принимает значение false, объект Player выходит из цикла while в строках 217 и 218. Метод Run исполняет цикл while (строки 226—256), что дает пользователю возможность играть. Каждая итера- ция этого оператора ожидает, пока клиент отправит целочисленное значение, указывающее место на игровом поле для размещения знака "X" или "О". После этого игрок помещает знак на поле при допустимости указанного местоположения (например, данная клетка еще пустая). Обратите внимание, что цикл while продолжает испол- нение только в том случае, если булева (bool) переменная done имеет значение false. Эта переменная устанав- ливается на значение true обработчиком событий Server_Closing класса Server, активизирующимся при за- крытии сервером соединения.
Организация сетей: сокеты на основе потоков и дейтаграммы 735 [ Листинг 19.6, Клиентская часть взаимодействия "клиент-сервер” при игре "Крестики-нолики" .----.Л£ « «-it...... *«. »w.'i 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 // Листинг 19.6: Client.cs // Класс игры "Крестики-нолики" using System; using System.Drawing; using System.Collections; using System.ComponentModel; using System.Windows.Forms; using System.Data; using System.Net.Sockets; using System.Threading; using System.IO; // представление игрока в "крестики-нолики" public class Client : System.Windows.Forms.Form { private System.Windows.Forms.Label idLabel; private System.Windows.Forms.TextBox displayTextBox; private System.Windowws.Forms.Panel panel1; private System.Windowws.Forms.Panel panel2; z private System.Windowws.Forms.Panel panel3; private System.Windowws.Forms.Panel panel4; private System.Windowws.Forms.Panel panel5; private System.Windowws.Forms.Panel panel6; private System.Windowws.Forms.Panel panel7; private System.Windowws.Forms.Panel panelS; private System.Windowws.Forms.Panel panel9; private Square[,] board; private Square currentSquare; private Thread outputThread; private TcpCiient connection; private Networkstream stream; private Binarywriter writer; private BinaryReader reader; private char myMark; private bool myTurn; private SolidBrush brush; private System.ComponentModel.Container components = null; bool done = false; // конструктор по умолчанию public Client() { InitializeComponent(); board = new Square[3, 3]; 11 создание 9 объектов Square и размещение их на поле board[0, 0] « new Square(panell, 11, 0); board[0, 1] = new Square(panel2, 11, 1); board[0, 2] = new Square(panel3, 12); board[1, 0] = new Square(panel4, '3); board[1, 1] = new Square(panel5, '4);
736 Глава 19 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 board[1, 2] = new Square(panel6, ", 5); board[2, 0] = new Square(panel7, ", 6); board[2, 1] = new Square(panel8, ", 7); board[2, 2] = new Square(panel9. ", 8); // создание объекта SolidBrush для заполнения клеток brush = new SolidBrush(Color.Clack); // Соединение с сервером и получение соответствующего // сетевого потока. Начало разделения потоков, чтобы программа // непрерывно обновляла выходные данные в текстовом поле connection = new TcpClient("localhost", 5000); stream = connection.GetStreamO; writer = new Binarywriter (stream)-; reader = new BinaryReader(stream); // запуск нового потока для отправки и получения сообщений outputThread = new Thread(new Threadstart(Run)); outputThread.Start(); } // конец конструктора Client // код, сгенерированный Visual Studio .NET [STAThread] static void Main() { Application.Run(new Client()); ) protected void Client_Paint ( object sender. System.Windows.Forms.PainEventArgs e) { PaintSquares(); J protected void Client_Closing( object sender, CancelEventArgs e) { done » true; 11 изображение знака каждой клетки public void PaintSquares() { Graphics g; // изображение соответствующего знака в каждой клетке for (int row = 0; row < 3; row++) for (int column =0; cplumn < 3; column++) { // получение объекта Graphics для каждого объекта Panel g = board[row, column] .SquarePanel.CreateGraphicsO; // изображение соответствующего знака на клетке g. Drawstring (board[row, column] .Mark.ToStringO , this.Font, brush, 8, 8); } } // конец метода PaintSquares 11 передача на сервер местоположения отмеченной клетки protected void square_MouseUp( object sender, System.Windows.Forms.MouseEventArgs e)
Организация сетей: сокеты на основе потоков и дейтаграммы 737 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 { // отмечает каждую клетку при щелчке на ней мышью for (int row = 0; row <3; row++) for (int column =0; column < 3; column++) if (board[row, column].SquarePanel = sender) { CurrentSquare = board[row, column]; // передача хода на сервер SendClickedSquare(board[row, column].Location); } } // конец метода squareMouseUp // поток управления, обеспечивающий непрерывное обновление // отображения текстового поля public void Run() { // получение знака игрока (X или О) myMark = reader.ReadChar(); idLabel.Text = "You are player \"" + myMark + myTurn = (myMark == 'X' ? true : false); // обработка входящих сообщений try { // получение сообщений, отправленных клиенту while (true) ProcessMessage(reader.readstring()); ) catch (EndOfStreamException) { MessageBox.Show("Server is down, game over”, "Error", MessageBoxButtons.OK, MessageBoxIcon.Error); } } // конец метода Run II обработка сообщений, отправленных клиенту public void ProcessMessage(string message) { // при допустимости хода, отправленного игроком //на сервер, обновить отображение; знак этой клетки // будет знаком текущего игрока; обновить поле if (message = "Valid move.") { displayTextBox.Text += "Valid move, please wait.\r\n"; currentSquare.Mark = myMark; PaintSquares(); ) // если ход недопустимый, уведомить об этом; // игрок делает другой ход else if (message “ "Invalid move, try again") { displayTextBox.Text += message + "\r\n”; myTurn = true; ) // если противник сделал ход else if (message — "Opponent moved") { // найти место хода int location = reader.readlnt32(); 47 Зак. 3333
738 Глава 19 190 // поставить в данной клетке знак противника 191 //и обновить поле 192 board[location I 3, location % 3 }.Mark = 193 (myMark = 'X' ? ’O' : 'X'); 194 PaintSquares(); 195 196 displayTextBox.Text += 197 "Opponent moved. Your turn.\r\n"; 198 199 // теперь ход данного игрока 200 myTurn = true; 201 } 202 203 // отобразить сообщение 204 else 205 displayTextBox.Text += message + "\r\n"; 206 207 } // конец метода ProcessMessage 208 209 // отправка на сервер номера клетки, на которой сделан щелчок мышью 210 public void, SenSciickedSquare(int location) 211 { 212 // если ход текущего игрока 213 if (myTurn) 214 { 215 // отправить место хода на сервер 216 writer.Write(location); 217 218 // если ход другого игрока 219 myTurn = false; 220 } 221 } 222 223 11- свойство текущей клетки только для записи 224 public Square Currentsquare 225 { 226 set 227 { 228 currentsquare = value; 229 } 230 } 231 232 } // конец класса Client Строка 229 в листинге 19.5 начинается с цикла while, повторяющегося до тех пор, пока свойство Available объекта Socket не проинформирует о наличии сведений для получения с сокета (либо, пока сервер не отключит- ся от клиента). При отсутствии информации поток "засыпает” на одну секунду. После "пробуждения" поток ис- пользует свойство Disconnected для проверки установки переменной disconnect сервера на значение true. Ес- ли значение — true, тогда поток Thread выходит из метода (с прерыванием Thread); в противном случае объект Thread повторяется еще раз. Однако, если свойство Available указывает на наличие данных для приема, тогда цикл while в строках 229—235 прерывается, и информация обрабатывается. Данная информация содержит переменную типа int, обозначающую местоположение, в которое клиент хочет поместить свой знак. В строке 238 вызывается метод Readlnt32 объекта BinaryReader (считывающий данные из объекта Networkstream, созданного вместе с объектом socket) для считывания int. После этого в строке 242 int передается в метод ValidMove объекта Server. Если данный метод подтверждает допустимость хода, тогда игрок (player) помещает свой знак в выбранную клетку. С помощью метода ValidMove (строки 101—131) клиенту направляется сообщение, извещающее его о допусти- мости хода. Местоположения на игровом поле соответствуют цифрам от 0 до 8 (0—2 — для первого ряда, 3— 5 — для второго, 6—8 — для третьего). Все операторы метода ValidMove заключены в оператор lock, позво- ляющий сделать за один раз только один ход. Этим предотвращается изменение информации о состоянии игры одновременно двумя игроками. Если игрок, предпринимающий попытку подтверждения допустимости хода, не является текущим игроком (т. е. игроком, который имеет право на ход), тогда этот игрок (player) помещается
Организация сетей: сокеты на основе потоков и дейтаграммы 739 в состояние ожидания своей очереди на ход. Если пользователь пытается ввести знак в уже занятую клетку по- ля, тогда метод validMove возвращает значение false. Однако, если пользователь выбрал незанятую клетку (строка 111), тогда в строках 114 и 115 знак помещается в локальное представление игрового поля. Строка 121 извещает другого игрока о сделанном ходе, а в строке 124 активизируется метод Pulse с тем, чтобы ожидающий игрок мог подтвердить допустимость хода. После этого данный метод возвращает значение true, подтвер- ждающее допустимость хода. При исполнении приложения Client (см. листинг 19.6) создается текстовое поле TextBox для отображения со- общений сервера и изображение игрового поля. Последнее создается из девяти объектов Square (листинг 19.7), содержащих панели (Panel), на которых пользователь может щелкать кнопкой мыши, указывая положение на поле, в которое нужно поместить знак. Конструктор приложения client (строки 50—82) открывает соединение с сервером (строка 73) и получает ссылку на относящийся к данному соединению объект Networkstream из класса TcpClient (строка 74). В строках 80 и 81 запускается поток для считывания сообщений, отправленных с сервера клиенту. Сервер передает сообщения (например, о допустимости хода) в метод ProcessMessage (строки 163—207). Если в сообщении указано, что ход допустим (строка 168), тогда клиент ставит свой знак в текущую клетку (на которой пользователь щелкнул кнопкой мыши) и обновляет поле. Если сообщение указывает, что ход недопустим (строка 178), тогда программа-клиент уведомляет пользователя о необходимости выбора другой клетки. Если сообщение указывает, что противник сделал ход (строка 185), строка 188 считывает с сервера пе- ременную типа int, задающую местоположение на поле, в которую клиент должен поместить знак противника. 1 // Листинг 19.7: Square.cs 2 // Клетки в игре "Крестики-нолики" 3 4 using System.Windows.Forms; 5 6 // представление клетки в сетке игры "Крестики-нолики" 7 public class square 8 { 9 private Panel panel; 10 private char mark; 11 private int location; 12 13 // конструктор 14 public Square(Panel newPanel, char newMark, int newLocation) 15 { 16 panel = rjewPanel; 17 mark = newMark; 18 location = newLocation; 19 } 20 21 // свойство SquarePanel; панель, представляемая клеткой 22 public Panel SquarePanel 23 { 24 get 25 { 26 return panel; 27 } 28 } •// конец свойства SquarePanel 29 30 // свойство Mark; знак клетки 31 public char Mark 32 { 33 get 34 { 35 return mark; 36 } 37 38 set 39 { 40 mark = value; 41 } 42 } // конец свойства Mark
740 Глава 19 43 // свойство Location; местоположение клетки на игровом поле 44 public int Location 45 { 46 get 47 { 48 return location; 49 } 50 } // свойство Location 51 52 } // конец класса Square 53 Этапы игры представлены на рис. 19.4, а на рис. 19.5 отображены ходы обоих игроков, фиксируемые сервером. Рис. 19.4. Этапы игры "Крестики-нолики": а — подключение первого игрока ("X"); б — подключение второго игрока ("О"); в — первый игрок сделал первый ход; г — второй игрок увидел ход первого игрока; д — второй игрок сделал первый ход; е — первый игрок увидел ход второго игрока; ж— игровое поле первого игрока при последнем ходе; з — игровое поле второго игрока при последнем ходе
Организация сетей: сокеты на основе потоков и дейтаграммы 741 Вывод сервера после действий (а), (б) Вывод сервера после действий (в), (г) Вывод сервера после действий (д), (е) Вывод сервера после действий (ж), (з) Рис. 19.5. Ходы игроков, зафиксированные сервером В данной главе рассмотрено использование сетевых технологий C# с описанием передачи информации как по- средством соединения (на базе потоков), так и независимо от соединения (на базе пакетов). Был продемонстри- рован процесс создания простейших серверного и клиентского приложений с помощью потоковых сокетов, а также многопоточного сервера. В следующей главе будет рассмотрено динамическое сохранение данных, а также несколько ключевых классов, принадлежащих в C# пространству имен System.Collections. 19.7. Резюме Двумя наиболее широко распространенными сокетами являются потоковые сокеты и сокеты дейтаграмм. Пото- ковые сокеты обеспечивают службу маршрутизации; это означает, что один процесс устанавливает соединение с другим процессом, и данные передаются между ними в виде непрерывных потоков. Дейтаграммные сокеты обеспечивают взаимодействие процессов независимо от соединения с использованием сообщений для передачи данных. Обмен данными независимо от соединения — более производителен, но менее надежен, нежели пере- дача информации с установлением соединения. Протокол TCP — предпочтительный протокол для потоковых сокетов. Это надежный и сравнительно оператив- ный способ передачи данных по сети. Протокол UDP предпочтителен для дейтаграммных сокетов. Этот прото- кол ненадежен: нет никакой гарантии, что пакеты информации, отправленные по UDP, дойдут до места назна- чения в порядке их отправки и не потеряются. Организация простого серверного приложения с TCP и потоковыми сокетами в C# включает пять основных шагов. Шаг 1 — создание объекта TcpListener. Этот класс представляет потоковый сокет TCP, используемый сервером для приема соединений. Для получения соединений объект TcpListener должен их "слушать". Для прослушивания запросов на соединение необходимо вызвать метод start класса TcpListener (шаг 2). Метод Acceptsocket класса TcpListener блокируется на неопределенное время до установки соединения; в этом случае метод возвращает объект Socket (шаг 3). Шаг 4 — фаза обработки, когда клиент и сервер взаимодействуют с помощью методов Read и Write посредством объекта Networkstream. По окончании соединения клиента с сер- вером последний закрывает соединение с помощью метода close объекта Socket (шаг 5). После этого большин- ство серверов — посредством цикла управления — возвращается к шагу вызова метода Acceptsocket, ожидая соединения с очередным клиентом. Номер порта, также называемый IP-адресом — числовой идентификационный номер, используемый процессом для самоидентификации на отдельно взятом адресе в сети. Отдельный процесс, выполняющийся на компьютере, идентифицируется по паре "IP-адрес/номер порта". Именно поэтому два процесса не могут иметь одинаковых номеров портов на отдельно взятом IP-адресе. Класс iPAddress представляет адрес протокола Интернета (IP- адрес) Класс IPEndPoint представляет конечную точку в сети, включая пару "IP-адрес/номер порта". Организация простого клиентского приложения включает четыре этапа. Этап 1 — создание объекта TCPClient для соединения с серверным приложением. Это соединение устанавливается вызовом метода Connect класса TcpClient, содержащего два аргумента: IP-адрес сервера и номер порта. На этапе 2 для получения потока stream для записи информации на сервер и ее считывания с него класс TcpClient использует метод Getstream. Этап 3 — фаза обработки, когда клиент и сервер взаимодействуют друг с другом. На этапе 4 клиент закрывает соединение вызовом метода Close класса Networkstream. Методы WriteByte и Writestream класса Networkstream можно использовать для вывода в поток отдельных байтов или наборов байтов соответственно. Методы ReadByte и Read класса Networkstream служат для считывания из потока отдельных байтов или наборов байтов соответственно. Класс UdpClient представлен для передачи данных независимо от соединения. Методы Send и Receive класса UdpClient применяются для передачи данных. Многопоточные серверы могут управлять одновременными соединениями многих клиентов. С помощью таких серверов программист может размещать код фазы обработки в отдельный поток. Такая организация освобожда- ет объект TcpListener для прослушивания запросов на другие соединения.
ГЛАВА 20 (ш Структуры данных и коллекции Многое из того, что я сдерживаю, я не могу освободить; Многое из того, что освободил, вернулось ко мне. Ли Уилсон Додд "Что ж ты улитка", — рыбка сказала, — Нам не годится так отставать... К морю давно уже все собралися, Скучно им будет долго нас ждать. Льюис Кэрролл Наверху всегда есть место. Дэниэл Уэбстер Торопись, двигайся. Томас Мортон Her, никогда мне не узнать Стиха, прекрасного, как древо. Джойс Килмер Темы данной главы: □ создание связанных структур данных с помощью ссылок, самоотносимых классов и рекурсии; □ создание динамических структур данных— связанных списков, очередей, стеков и двоичных деревьев и манипуляции ими; □ важность применения связанных структур данных; □ создание структур данных многократного использования с классами, наследованием и композицией. 20.1. Введение Рассматривавшиеся до сих пор структуры данных имели фиксированный размер, т. е. обладали одинарным или двойным индексированием. В настоящей главе представлены динамические структуры данных, способные расширяться и сжиматься во время исполнения. Связанные списки — это коллекции элементов данных, "выров- ненных в ряд": в любом месте связанного списка пользователи могут делать вставки и удалять данные. Стеки представляют важность для компиляторов и операционных систем, потому что вставки и удаления осуществля- ются только на одном конце стека — сверху. Очереди представляют собой строки ожидания; вставки в них делаются с конца (называемого хвостом), а удаления — с начала очереди (называемого головой). Двоичные де- ревья упрощают процесс высокоскоростного поиска и сортировки данных, эффективного удаления дубликатов данных, представления каталогов файловой системы и компиляции выражений в машинный язык. Перечислен- ные структуры данных также имеют много других интересных применений. В этой главе рассматриваются основные типы структур данных и реализуются программы, осуществляющие их создание и операции с ними. Для создания и пакетирования структур данных с целью многократного использо- вания и удобства сопровождения используются классы, наследование и композиция. Примеры главы представляют собой практические программы, которые могут быть полезными при прохожде- нии более сложных курсов, а также в промышленном применении. В данных программах особый упор сделан на манипуляции ссылками. Упражнения предлагают расширенный набор полезных программных приложений.
Структуры данных и коллекции 743 20.2. Самоотносимые классы Самоотносимый класс (self-referential class) содержит справочный элемент, относящийся к объекту того же ти- па класса. Например, определение класса в листинге 20.1 задает тип Node. Данный тип имеет две переменные экземпляра private: целочисленную data и ссылку next класса Node. Элемент next ссылается на объект типа Node — объект описываемого типа; отсюда термин "самоотносимый класс". На элемент new ссылка делается как на связующее звено (т. е. next можно использовать для "привязывания" объекта типа Node к другому объекту такого же типа). Класс Node имеет два свойства: одно для nepeMeHHoft'data (называемое Data), а другое для пе- ременной next (имеющее имя Next). '-г— * ••'*у-****лм»*.*;ш—ы—ммк.'мамммшш1ммы** м»** ‘ЦН*-1-1 ^У****- * ******* ********* Листинг 20.1. Пример определения самоотносимого класса Node 1 class Node 2 { 3 private int data; 4 private Node next; 5 6 public Node(int d) 7 { 8 // тело конструктора 9 } 10 11 public int data 12 { 13 get 14 { 15 // получение тела 16 } 17 18 set 19 { 20 // установка тела 21 J 22 } 23 24 public Node next 25 { 26 get 27 { 28 // получение тела 29 } 30 31 set 32 { 33 // установка тела 34 } " 35 } 36 } Рис. 20.1. Два связанных объекта самоотносимого класса Самоотносимые объекты можно связывать друг с другом с целью формирования полезных структур данных, например, списки, очереди, стеки и деревья. На рис. 20.1 представлены два самоотносимых объекта, связанные между собой для формирования списка. Символ обратной косой черты (обозначающий ссылку null) помещен ] элемент ссылки второго самоотносимого объекта для указания того, что ссылка не относится к другому объек- ту. Косая черта используется для наглядности; она не соответствует символу обратной косой черты в С#. Ссыл- ка null, как правило, указывает конец структуры данных. Распространенная ошибка программирования_____________________________________________ Если ссылке в последнем узле списка (или другой линейной структуры данных) не присвоить значение null, то это будет логической ошибкой.
744 Гпава 20 Создание и сопровождение динамических структур данных требует динамического распределения памяти', спо- собности программы получения большего объема памяти во время исполнения для. сохранения новых узлов и освобождения неиспользуемого пространства памяти. Как уже отмечалось, программы C# не освобождают ди- намически распределяемую память явно, а осуществляют автоматическую "сборку мусора". Предел распределения динамической памяти в системе виртуальной памяти может быть равен объему доступ- ного пространства на диске. Как правило, пределы намного меньше, потому что доступная память компьютера распределяется среди множества пользователей. Для динамического распределения памяти принципиальное значение имеет оператор new. В качестве операнда оператор new принимает дйнамичЪски распределяемый объект и возвращает ссылку на вновь созданный объект данного типа. Например, выражение Node nodeToAdd = new Node(10); распределяет соответствующий объем памяти для сохранения Node и сохраняет ссылку на данный объект в nodeToAdd. Если память недоступна, тогда оператор, new выдает исключение OutOfMemoryException. Число 10 — данные объекта Node. В следующем разделе.рассматриваются списки, стеки, очереди и деревья. Эти структуры данных создаются и сопровождаются с динамическим распределением памяти и самоотносимыми классами. О хорошем стиле программирования________________________________________________________ При создании очень большого количества объектов следует выполнить проверку на наличие исключения OutOfMemoryException. Рекомендуется выполнять соответствующую обработку ошибок, если запрошенная па- мять не распределена. ' 20.3. Связанные списки Связанный список (linked list) — это линейная коллекция (т. е. последовательность) объектов самоотносимого класса, называемых узлами, соединенная ссылками; отсюда и термин: "связанный" список. Программа осущест- вляет доступ к связанному списку посредством ссылки на первый узел списка? Доступ к каждому последующе- му узлу выполняется посредством элемента ссылки, сохраненного в предыдущем узле. Условно говоря, значе- ние ссылки в последнем узле списка установлено на null для отметки конца списка. Данные сохраняются в свя- занном списке динамически, т. е. каждый узел создается по мере необходимости. Узел может содержать данные любого типа, включая объекты других классов. Стеки и очереди — тоже линейные структуры данных, являю- щиеся ограниченными версиями связанных списков. Деревья не являются линейными структурами данных. Списки данных могут сохраняться в массивах, однако связанные списки обеспечивают ряд преимуществ. Свя- занный список уместен, когда нельзя предсказать количество элементов данных для представления их в струк- туре. В отличие от связанного списка, размер традиционного массива в G# изменить нельзя, потому что он фик- сируется в момент создания. Традиционные массивы могут быть полными, а связанные списки заполняются только при недостаточном объеме памяти для удовлетворения запросов на ее динамическое распределение. Совет по повышению производительности___________________________________________________ Можно объявить, что массив будет содержать больше элементов, чем ожидается, за счет расхода памяти. Свя- занные списки обеспечивают более эффективное использование памяти в таких ситуациях и позволяют про- грамме адаптироваться во время прогона. Совет по повышению производительности_______________________________________х___________ После размещения точки вставки нового элемента в отсортированном связанном списке вставка элемента в список осуществляется очень быстро: изменить требуется только две ссылки. Все существующие узлы остаются в своих текущих местоположениях в памяти. Программисты могут поддерживать отсортированный порядок связанных списков простой вставкой каждого нового элемента в нужную точку списка (размещение нужной точки вставки требует времени). При этом необ- ходимости перемещения существующих элементов списка нет. Совет по повышению производительности___________________________________________________ Элементы массива сохраняются в памяти смежно для обеспечения мгновенного доступа к любому элементу; адрес каждого элемента рассчитывается непосредственно через его смещение от начала массива. В связанных списках подобного мгновенного доступа к элементам не предусмотрено; доступ здесь осуществляется только прохождением списка с начала. Узлы связанных списков, как правило, не сохраняются в памяти смежно; они демонстрируют логическую смеж- ность. На рис. 20.2 показан связанный список с несколькими узлами.
Структуры данных и коллекции 745 Совет по повышению производительности_________________________________________________ Использование динамического распределения памяти (вместо массивов) для расширяющихся и сжимающихся структур данных во время исполнения может экономить объем памяти. Однако помните, что ссылки занимают место, и что динамическое распределение памяти сопровождается непроизводительными издержками обраще- ний к методам. В программе (листинги 20.2 и 20.3) для манипуляций списком объектов различных типов используется объект класса List. Метод Main класса ListTest (листинг 20.3) создает список объектов, вставляет объекты в начало списка с помощью метода insert At Front класса List, вставляет объекты в конец списка с помощью метода insertAtBack класса List, удаляет объекты из начала списка с помощью метода RemovefromFront класса List и удаляет объекты из конца списка с помощью метода RemoveFromBack класса List. Каждая операция вставки и удаления активизирует метод Print класса List для отображения текущего содержимого списка. При попытке удаления элемента из пустого списка имеет место исключение EmptyList Except ion. Совет по повышению производительности__________________________(______________________ Операции вставки элементов в отсортированный массив и удаления из него могут занимать много времени: все элементы, следующие за вставленным или удаленным элементом, должны смещаться соответственно. ^Листинг 20.2. Определения классов ListNode, List и EmptyListExceptron ’ у ~ ............________________________________________ 1 // Листинг 20.2: LinkedListLibrary.es' 2 // Определения классов ListNode, List и EmptyListException 3 4 using System; 5 6 namespace LinkedListLibrary 7 { 8 // класс, представляющий один узел списка 9 class ListNode Ю { 11 private object data; 12 private ListNode next; 13 14 // конструктор для создания объекта ListNode со ссылкой на* 15 // dataValue, являющегося последним объектом ListNode в списке 16 public ListNode(object dataValue) 17 : this(dataValue, null) 18 . { 19 } 20 21 // конструктор для создания объекта ListNode со ссылкой 22 // на dataValue и на следующий объект ListNode в списке 23 public ListNode(object dataValue, ListNode nextNode) 24 { 25 'data = dataValue; 26 next = nextNode; 27 } 28 29 // свойство Next 30 public ListNode Next 31 { 32 get
746 Гпава 20 33 34 35 36 37 зе 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 { return next; } set { next = value; } } // свойство Data public object Data ( get { return data; } } } // конец класса ListNode 11 определение класса List public class List { private ListNode firstNode; private ListNode lastNode; private string name; // отображение строки, подобной "list" // построение пустого списка List с заданным именем public List(string listName) { name e listName; firstNode « lastNode = null; } // построение пустого списка List c "list" в качестве имени public List() : this("list") { ) // Вставка объекта в начало списка. Если список пуст, элементы // firstNode и lastNode будут относиться к одному объекту. // В противном случае, firstNode относится к новому узлу public void InsertAtFront(object insertitem) ( lock (this) У { if (IsEmptyO) firstNode = lastNode « new ListNode(insertItem); else firstNode « new ListNode(insertItem, firstNode); } ) • // Вставка объекта в конец списка. Если список пуст, элементы // firstNode и lastNode будут относиться к одному объекту // Иначе св-во Next элемента lastNode относится к новому узлу, public void InsertAtBack(object insertitem) { lock(this)
Структуры данных и коллекции 747 95 { 96 if (IsEmpty()) 97 firstNode = lastNode = 98 new ListNode(insertItem); 99 100 else 101 lastNode = lastNode.Next = 102 new ListNode(insertItem); 103 ) 104 ) 105 106 // удалить первый узел из списка 107 public object RemovefromFront() 108 { 109 lock (this) 110 { 111 if (IsEmpty()) 112 throw new EmptyListException(name); 113 // извлечение данных 114 object removeitem = firstNode.Data; 115 116 // сброс ссылок на firstNode и lastNode 117 if (firstNode — lastNode) 118 firstNode = lastNode = null; 119 120 else 121 firstNode = firstNode.Next; 122 123 return removeitem; // возвращение удаленных данных 124 ) . 125 } 126 127 // удаление последнего узла из списка 128 public object RemoveFromBack() 129 { 130 lock (this) 131 { 132 if (IsEmpty()) 133 throw new EmptyListException(name); 134 // извлечение данных 135 object removeitem = lastNode.Data; 136 137 // сброс ссылок на элементы firstNode и lastNode 138 if (firstNode — lastNode) 139 firstNode = lastNode = null; 140 ‘ 141 else 142 { 143 ListNode current = firstNode; 144 145 // повторение, пока текущий узел не станет lastNode 146 while (current .Next !== lastNode) 147 current = current.Next; // переход к следующему узлу 148 149 // текущий — новый lastNode 150 lastNode = current; 151 current.Next = null; 152 ) 153 X 154 return removeitem; // возвращение удаленных данных 155 } 156 ) 157
748 Гпава 20 158 // возвращение true, если список пуст 159 public bool IsEmptyO 160 ( 161 lock (this) 162 { 163 return firstNode — null; 164 ) 165 166 ) 167 // вывод содержимого списка 168 virtual public void Print() 169 { 170 lock (this) 171 { 172 if (IsEmptyO) 173 { 174 Console.WriteLine("Empty " + name); 175 return; 176 177 ) 178 Console.Write("The " + name + " is: "); 179 180 ListNode current = firstNode; 181 182 // вывод данных текущего узла не в конце списка 183 while (current != null) 184 { 185 Console.Write(current.Data + " "); 186 current = current.Next; 187 ) 188 189 Console.WriteLine(”\n"); 190 } 191 } 192 193 ) // конец класса List 194 x 195 // определение класса EmptyListException 196 public class EmptyListException : ApplicationException 197 { 198 public EmptyListException(string name) 199 : base("The " + name + " is empty") 200 { 201 ) 202 203 } // конец класса EmptyListException 204 205 } // конец пространства имен LinkerListLibrary j Листинг 20.3. Демонстрация связанного списка I _______________________________________________;.и...*Х. 1 // Листинг 20.3: ListTest.cs 2 // Тестирование класса List 3 4 using System; 5 using LinkedListLxbrary; 6 . 7 namespace ListTest 8 { 9 // класс для тестирования функциональности класса List 10 class ListTest 11 ( 12 static void Main(string[] args)
Структуры данных и коллекции 749 13 { 14 List list = new List(); // создание контейнера List 15 16 // создание данных для сохранения в классе List 17 bool aBoolean = true; 18 char aCharacter = 19 int anlnteger = 34567; 20 string aString = "hello"; 21 22 // использование методов вставки класса List 23 list.InsertAtFront(aBoolean); 24 list.Print(); 25 list.InsertAtFront (aCharacter); 26 list.Print(); 27 list.InsertAtBack (anlnteger); 28 list.Print(); 29 list.InsertAtBack (aString); 30 list.Print(); 31 32 // использование методов удаления класса List 33 object removedObject; 34 35 // удаление данных из списка и печать после каждого удаления 36 try 37 { 38 removedObject = list.RemoveFromFront(); 39 Console.WriteLine(removedObject + "removed"); 40 list.Print(); 41 42 removedObject = list.RemoveFromFront(); 43 Console.WriteLine(removedObject + "removed"); 44 list.Print(); 45 46 removedObject = list.RemoveFromBack(); 47 Console.WriteLine(removedObject + "removed"); 48 list.Print(); 49 50 removedObject = list.RemoveFromBack(); 51 Console.WriteLine(removedObject + "removed"); 52 list.Print(); 53 } 54 у 55 // обработка исключения, если список пустой при 56 // попытке удаления элемента 57 catch (EmptyException emptyListException) 58 { 59 Console.Error.WriteLine("\n" + emptyListException); 60 } 61 62 } // конец метода Main 63 64 } // конец класса ListTest 65 } Программа состоит из четырех классов: ListNode (листинг 20.2, строки 9—52), List (листинг 20.2, строки 55— 193), EmptyListException (листинг 20.2, строки 196—203) и класса ListTest (листинг 20.3). Классы листин- га 20.2 создают библиотеку связанного списка (определена в пространстве имен LinkedListLibrary), которая на протяжении главы используется многократно. В каждом объекте List инкапсулирован связанный список объектов ListNode. Класс ListNode (листинг 20.2, строки 9—52) состоит из двух переменных элементов — data и next. Элемент data может относиться к любому объекту. В элементе next сохраняется ссылка на следующий объект класса ListNode в связанном списке. Класс List осуществляет доступ к переменным элемента ListNode посредством свойств Data (строки 44—50) и Next (строки 30—41) соответственно.
750 Гпава 20 Класс List содержит элементы типа private — firstNode (ссылка на первый объект ListNode в классе List) и lastNode (ссылка на последний объект ListNode в классе List). Конструкторы (строки 62—66 и 69—71) ини- циализируют обе ссылки на null. Методы InsertAtFront (строки 76—87), insertAtBack (строки 92—104), RentoveFromFront (строки 107—125) и RemoveFromBack (строки 128—156) являются основными методами класса List. В каждом из этих методов присутствует блок lock для обеспечения многопоточной безопасности объектов класса List при использовании в многопоточной программе. Если один поток модифицирует содержимое объ- екта класса List, тогда ни один другой поток не может одновременно модифицировать этот же объект. Метод isEmpty (строки 159—165) является предикатным методом, определяющим, пуст список или нет (т. е. ссылка на первый узел списка имеет значение null). Предикатные методы, как правило, проверяют условие, но не мо- дифицируют объект, для которого они вызваны. Если список пуст, тогда метод isEmpty возвращает значение true; в противном случае возвращается значение false. Метод Print (строки 168—191) отображает содержи- мое списка. Оба метода (isEmpty и Print) используют блоки lock так, что состояние списка не меняется во вре- мя выполнения этими методами их задач. Класс EmptyList Except ion (строки 196—203) определяет класс исключения для управления недопустимыми операциями с пустым классом List. Класс ListTest (листинг 20.3) использует библиотеку связанного списка для создания последнего и манипуля- ций им. В строке 14 создается новый экземпляр типа List с именем list. В строках 17—20 создаются данные для добавления в список. В строках 23—30 используются методы вставки класса List для вставки объектов, а также метод Print класса List для вывода содержимого списка List после каждой вставки. Код в рамках блока try (строки 36—53) удаляет объекты с помощью методов удаления класса List, выводит удаленные объекты и после каждой операции удаления выводит экземпляр list. Если предпринимается попытка удаления объекта из пустого списка, блок try захватывает исключение EmptyLi st Except ion. Обратите внимание на то, что класс ListTest использует пространство имен LinkedListLibrary (листинг 20.2); таким образом, решение для класса ListTest должно иметь ссылку на библиотеку классов LinkedListLibrary. Результат работы программы таков: ’• The list is: True The list is: $ True The list is: $ True 34567 The list is: $ True 34567 hello $ removed The list is: True 34567 hello True removed The list is: 34567 hello hello removed The list is: 34567 34567 removed Empty list , Далее каждый метод класса List рассматривается подробно. Метод InsertAtFront (листинг 20.2, строки 76—87) размещает новый узел в начале списка. Данный метод со- стоит из трех шагов (показаны на рис. 20.3): 1. Вызов метода isEmpty для определения, пустой список или нет (строка 80). 2. Если список пуст, задание элементов firstNode и lastNode для обращения к новому объекту ListNode, ини- циализированному с insertitem (строки 81 и 82). Конструктор ListNode в строках 16—19 (листинг 20.2) вы- зывает в строках 23—27 конструктор ListNode (листинг 20.2) для задания экземпляра переменной data для ссылки на объект, переданный в качестве первого аргумента, и для задания ссылке next значения null. 3. Если список не пустой, тогда новый узел "вплетается" (не путать с многопоточностью) в список заданием элемента firstNode для ссылки на новый объект класса ListNode, инициализированный с элементами insertitem и firstNode (строки 84 и 85). При исполнении конструктора ListNode (строки 23—27 листин- га 20.2) задается переменная экземпляра data для ссылки на объект object, переданный в качестве первого аргумента, и выполняется вставка заданием ссылки next объекту ListNode, переданному в качестве второго аргумента.
Структуры данных и коллекции 751 На рис. 20.3, а показан список и новый узел во время операции insertAtFront до ввода нового узла в список. Пунктирные стрелки на рис. 20.3, б иллюстрируют шаг 3 операции InsertAtFront, превращающий узел, содер- жащий 12, в первый узел нового списка. firstNode firstNode Рис. 20.3. Графическое представление операции InsertAtFront: а — список и новый узел; б — вставка нового узла в начало списка Метод insertAtBack (листинг 20.2, строки 92—104) размещает новый узел в конец списка. Данный метод со- стоит из трех шагов (показаны на рис. 20.4): 1. Вызов метода isEmpty для определения, пустой список или нет (строка 96). 2. Если список пуст, задание элементов firstNode и lastNode для обращения к новому объекту ListNode, ини- циализированному с “insert item (строки 97 и 98). Конструктор ListNode в строках 16—19 (листинг 20.2) вы- зывает в строках 23—27 конструктор ListNode (листинг 20.2) для задания экземпляра переменной data для ссылки на объект, переданный в качестве первого аргумента, и для задания ссылке next значения null. 3. Если список не пустой, тогда новый узел ’’вплетается" в список заданием элементов lastNode и lastNode.next для ссылки на новый объект ListNode, инициализированный с элементом insertitem (стро- ки 101 и 102). При исполнении конструктора ListNode (строки 16—19 листинга 20.2) задается переменная экземпляра data для ссылки на объект object, переданный в качестве первого аргумента, и ссылке next за- дается значение null. На рис. 20.4, а представлен список и новый узел во время операции InsertAtBack до ввода нового узла в список. Пунктирные стрелки на рис. 20.4, б иллюстрируют шаги метода InsertAtBack, обеспечивающего добавление узла в конец не пустого списка. firstNode lastNode New ListNode б Рис. 20.4. Графическое представление операции InsertAtBack: а — список и новый узел; б—вставка нового узла в конец списка
752 Гпава 20 Метод RemoveFromFront (листинг 20.2, строки 107—127) удаляет первый узел из списка и возвращает ссылку на удаленные данные. Метод выдает исключение EmptyListException (строка 114), если программист пытается удалить узел из пустого списка. В противном случае метод возвращает ссылку на удаленные данные. Данный метод состоит из четырех шагов (показаны на рис. 20.5): 1. Присвоение firstNode. Data (удаляемые из списка данные) ссылке removeitem (строка 116). ' 2. Если объекты, на которые ссылаются элементы firstNode и lastNode, являются одним объектом, тогда спи- сок имеет только один элемент перед попыткой удаления В этом случае метод задает значение null элемен- там firstNode и lastNode (строка 120) для вывода из потока (удаления) узла из списка (список остается пус- тым). 3. При наличии в списке более одного узла до операции удаления, тогда метод оставляет ссылку lastNode как есть и просто присваивает элемент firstNode.Next ссылке firstNode (строка 123). Таким образом, firstNode делает ссылку на узел, который был вторым до вызова метода RemoveFromFront. 4. Возвращение ссылки removeitem. На рис. 20.5, а показан список до операции удаления, а на рис. 20.5, б — фактические манипуляции ссылкой. firstNode lastNode firstNode lastNode removeitem 6 Рис. 20.5. Графическое представление операции RemoveFromFront: а — список до удаления первого узла; б — изменение ссылки Метод RemoveFromBack (листинг 20.2, строки 130—460) удаляет последний узел списка и возвращает ссылку на удаленные данные. Данный метод выбрасывает исключение EmptyListException (строка 137), если программа делает попытку удаления узла из пустого списка. Метод состоит из нескольких шагов (показаны на рис. 20.6): 1. Присвоить lastNode.Data (удаляемые из списка данные) ссылку removeitem (строка 139). 2. Если объекты, на которые ссылаются firstNode и lastNode, являются одним объектом (строка 142), тогда список имеет только один элемент перед попыткой удаления. В этом случае метод задает элементам firstNode и lastNode значение null (строка 143) для удаления узла из списка (список остается пустым). 3. Если перед операцией удаления список сбдержит более одного узла, создать ссылку current класса ListNode и присвоить ее элементу firstNode (строка 147). 4. "Проход списка" ссылкой current до ссылки на предпоследний узел списка. Цикл while (строки 150—151) присваивает current .Next ссылке current, поКа current. Next не равно lastNode.
Структуры данных и коллекции 753 5. После определения местоположения предпоследнего узла присвоить current элементу lastNode (строка 154) для удаления из списка последнего узла. 6. Задать значение null методу current.Next (строка 155) в новом последнем узле списка для обеспечения надлежащего завершения списка. 7. Возвратить ссылку removeitem (строка 140). На рис. 20.6, а представлен список до операции удаления, а на рис. 20.6, б— фактические манипуляции ссылкой. firstNode lastNode а firstNode lastNode current removeltem 6 Рис. 20.6. Графическое представление операции RemoveFromBack: а — список до удаления последнего. б — изменение ссылки Метод Print (листинг 20.4, строки 172—195) сначала определяет степень заполнения списка (строка 176). Если список пуст, тогда метод Print отображает строку, содержащую слово "Empty" и название списка (name), после чего возвращает управление в вызывающий метод. В противном случае метод Print выдает данные в списке. Метод распечатывает строку, состоящую из строки "The", названия name и строки "is:". Затем в строке 184 создается ссылка current класса ListNode, которая инициализируется элементом firstNode. Пока ссылка current не равна null, в списке имеется несколько элементов. Следовательно, метод распечатывает элемент cur rent. Data (строка 189), после чего присваивает current. Next ссылке current (строка 190) для перехода к следующему узлу списка. Обратите внимание, что если ссылка в последнем узле списка не имеет значения null, тогда алгоритм будет предпринимать ошибочную попытку печати после окончания списка. Алгоритм печати одинаков для связанных списков, стеков и очередей. 20.4. Стеки Стеком называется ограниченный вариант связанного списка; стек принимает новые узлы и освобождает узлы только сверху списка. По этой причине стек называется структурой данных, организованной по принципу LIFO (last-in, first-out, последним пришел — первым вышел). Элемент ссылки в нижнем (последнем) узле стека имеет значение null для обозначения нижней части стека. Основными операциями со стеками являются операции проталкивания (push) и вытеснения (pop). Операцией проталкивания новый узел добавляется в верхнюю часть стека Операцией вытеснения узел удаляется из верх- ней части стека и возвращается элемент из вытесненного узла. 48 Зак. 3333
754 Глава 20 Стеки имеют много различных и интересных применений. Например, при обращении программы к методу по- следний должен "знать" способ возвращения к вызывающему элементу, поэтому обратный адрес проталкивает- ся в стек исполнения программы. Если имеет место серия обращений к методу, тогда последовательные воз- вращаемые значения проталкиваются в стек в порядке LIFO так, что каждый метод может вернуться к вызы- вающему элементу. Стеки поддерживают рекурсивное обращение к методам так же, как и традиционные нерекурсивные обращения. Стек исполнения программы содержит пространство, созданное для локальных переменных при каждой активи- зации метода во время работы программы. Когда метод возвращается к вызывающему элементу, пространство для локальных переменных этого метода вытесняется из стека, и программа не распознает эти переменные. Пространство имен System.Collections содержит класс stack для реализации и сопровождения стеков, кото- рые могут расширяться и сжиматься во время исполнения программы. Класс stack описывается в разд. 20.7. Воспользуемся преимуществом близкого родства списков и стеков для реализации класса стеков пу гем повтор- ного использования класса списка. Демонстрируются две разные формы многократного использования. Во- первых, класс stack реализуется наследованием от класса List из листинга 20.2. Затем посредством составле- ния формируется стек с идентичной функциональностью путем включения объекта List в качестве элемента private класса stack. В настоящей главе для сохранения ссылок object и стимулирования многократного ис- пользования объектов реализуются структуры данных в виде списка, стека и очереди. Следовательно, в списке, стеке или в очереди можно сохранить объект любого типа. В программах из листингов 20.4 и 20.5 создается класс stack путем наследования из класса List (см. лис- тинг 20.2). Необходимо, чтобы стек имел методы Push, Pop, IsEmpty и Print. В сущности, это методы InsertAtFront, RemoveFromFront, IsEmpty и Print класса List. Конечно, класс List содержит и Другие методы (например, InsertAtBack и RemoveFromBack), которые не будут доступны через public-интерфейс стека. Важно помнить, что все методы в public-интерфейсе класса List также являются public-методами производного клас- са Stackinheritance (листинг 20.4). При реализации методов стека каждый метод stackinheritance вызывает соответствующий метод класса List: метод Push вызывает метод InsertAtFront, метод Pop — метод RemoveFromFront. Класс Stackinheritance не определяет методы IsEmpty и Print, потому что stackinheritance наследует эти методы из класса List в public-интерфейс класса stackinheritance Методы в классе stackinheritance не используют операторов lock. Каждый метод в данном классе вызывает метод из класса List, использующий оператор lock. Если два потока вызывают метод Push на одном объекте стека, то только один поток сможет в конкретный момент вы- звать метод InsertAtFront класса List. Обратите внимание, что в классе stackinheritance используется про- странство имен LinkedListLibrary (см. листинг 20.2); таким образом, решение для библиотеки класса, опреде- ляющей класс stackinheritance, должно иметь ссылку на библиотеку классов LinkedListLibrary. 1 // Листинг 20.4: StackInheritanceLibrary.cs 2 // Реализация стека путем наследования от класса List 3 4 using System; 5 using LinkedListLibrary; 6 7 namespace StacklngeritanceLibrary 8 { 9 // khacc Stackinheritance наследует функции класса List 10 public class Stackinheritance : List 11 ( 12 // передача имени "stack” в конструктор класса List 13 * public Stackinheritance() : base("stack") 14 { 15 } 16 17 • // размещение dataValue наверху стека 18 // вставкой dataValue в начало связанного списка 19 public void Push(object dataValue) 20 { 21 InsertAtFront(dataValue); 22 } 23
Структуры данных и коллекции 755 24 // удаление элемента из верхней части стека 25 // удалением элемента в начале связанного списка 26 public object Рор() 27 {. 28 return RemoveFromFront(); 29 } 30 31 } // конец класса Stackinheritance 32 } Метод Main класса StacklnheritanceTest (листинг 20.5) использует класс Stackinheritance для создания стека объекта с именем stack. В строках 18—21 определяются четыре объекта, которые будут проталкиваться в стек и вытесняться из него. Программа проталкивает в стек (строки 24, 26, 28 и 30) переменную bool со значением true, символ char, содержащий $, переменную int, равную 34567, и строку, содержащую слово hello. Беско- нечный цикл while (строки 36—41) вытесняет эти элементы из стека. При отсутствии объектов для вытеснения метод Pop выбрасывает исключение EmptyListException, и программа отображает состояние стека на время возникновения исключения. Программа использует метод Print (унаследованный от класса List) для вывода содержимого стека после каждой операции. Обратите внимание, что класс StacklnheritanceTest использует пространство имен LinkedListLibrary (см. листинг 20.2) и пространство имен StacklnheritanceLibrary (лис- тинг 20.5); таким образом, решение для класса stackinheritancetest должно иметь ссылки на библиотеки обо- их классов. 1 // Листинг 20.5: StacklnheritanceTest.cs 2 // Тестирование класса Stackinheritance 3 4 using System; 5 using StacklnheritanceLibrary; 6 using LinkedListLibrary; 7 8 namespace StacklnheritanceTest 9 { 10 // демонстрация функциональности класса Stackinheritance 11 class StacklnheritanceTest, 12 { 13 static void Main(string[] args) 14 { 15 Stackinheritance stack = new Stackinheritance(); 16 * 17 // создание объектов для сохранения в стеке 18 bool aBoolean = true; 19 char aCharacter = 20 int anlnteger = 34567; 21 string aString = "hello"; 22 23 // использование метода Push для добавления элементов в стек 24 stack.Push(aBoolean); 25 stack.Print(); 26 stack.Push(aCharacter); 27 stack.Print(); 28 stack.Push(anlnteger); 29 stack.Print(); 30 stack.Push(aString); 31 stack.Print(); 32 33 // использование метода Pop для удаления элементов из стека 34 try 35 { ’ 36 while (true) 37 { 38 object removedObject = stack.PopO;
756 Гпава 20 39 Console.WriteLine(removedObject + " popped"); 40 stack.Print(); 41 } 42 } 43 44 11 при исключении распечатать состояние стека 45 catch (EmptyListException emptyListException) 46 { 47 Console.Error.WriteLine( 4 8 emptyListException.StackTrace); 49 } 50 51 } // конец метода Main 52 53 } // конец класса Stackinheritancetest 54 } Результат выполнения программы: The stack is: True The stack is: $ True The stack is: 34567 $ True The stack is: hello 34567 $ True hello popped The stack is: 34567 $ True True removed The stack is: 34567 hello 34567 popped The stack is: $ True $ popped The stack is: True True popped Empty stack at LinkedListLibrary.List.RemoveFromFront() in z:\ch20\LinkedlistLibrary\Linkedlistlibpary.cs:line 114 at StacklnheritanceLibrary.Stackinheritance.Pop() in z:\ch20\Stackinheritancelibrary\ Stackinheritancelibrary.es:line 28 at Stackinheritancetest.StacklnheritanceTest.Main(String[] args in z:\ch20\Fig20_ll\Stackinheritancetest.cs:line 41 Другим способом реализации класса стека является повторное использование класса списка посредством ком- позиции. Класс в листинге 20.6 использует объект private класса List (строка 12) в определении класса stackcomposition. Композиция позволяет скрыть методы класса List, которых не будет в интерфейсе public стека, путем предоставления методов интерфейса public только для необходимых методов класса List. Данный класс реализует каждый метод стека путем передачи функций соответствующему методу класса List. В частно- сти, класс Stackcomposition вызывает методы InsertAtFront, RemoveFromFront, IsEmpty и Print класса List. В данном примере класс StackCompositionTest не представлен, потому что единственным отличием этого при- мера от предыдущего является смена типа стека со stackinheritance на stackcomposition. Если приложение исполняется из кода, размещенного на Web-сайте, то читатель легко обнаружит, что выходные данные — иден- тичны. 1 // Листинг 20.6: StackCompositionbLibrary.cs 2 // Определение класса Stackcomposition композицией объекта List 3
Структуры данных и коллекции 757 4 using System; 5 using LinkedListLibrary; 6 7 namespace StackCompositionLibrary 8 { 9 // класс Stackcomposition инкапсулирует функции класса List 10 public class Stackcomposition 11 ( 12 private List stack; 13 14 // построение пустого стека 15 public Stackcomposition() 16 { 17 stack = new List("stack"); 18 } 19 20 // добавление объекта в стек 21 public void Push(object dataValue) 22 { 23 stack.InsertAtfrent(dataValue); 24 } 25 26 // удаление объекта из стека 27 public object Pop() 28 { 2 9 return stack.RemoveFromFront(); 30 } 31 32 // определение заполненности стека 33 public bool IsEmpty() 34 { 35 return stack.IsEmpty(); 36 } 37 38 // вывод содержимого стека 39 public void Print() 40 { 41 stack.Print(); 42 } 43 44 } // конец класса Stackcomposition 45 } 20 .5. Очереди Другой распространенной структурой данных является очередь. Интерактивная очередь напоминает очередь в супермаркете: первый человек обслуживается первым; покупатели присоединяются к очереди с конца и дожи- даются обслуживания. Узлы очереди удаляются только с ее головы, а добавляются только с хвоста. По этой причине очередь представляет собой структуру данных, организованную по принципу FIFO (first-in, first-out, первым пришел — первым вышел). Операции вставки и удаления называются постановкой в очередь и выведе- нием из очереди. В вычислительных системах очереди имеют много разных применений. В большинстве компьютеров установ- лен один процессор, поэтому за один раз обслуживаться может только один пользователь. Записи для других пользователей помещаются в очередь на исполнение. Запись в голове очереди обрабатывается первой. Каждая запись постепенно приближается к голове очереди по мере обслуживания пользователей. Очереди поддерживают вывод документов на печать. Многопользовательская среда может иметь только один принтер. Выводить материалы на печать могут многие пользователи. Если принтер занят, то материалы по- прежнему можно отправлять на печать; в этом случае они накапливаются на диске, где дожидаются своей оче- реди на печать.
758 Гпава 20 Пакеты информации в компьютерных сетях также ожидают своей очереди на обработку. Всякий раз при посту- плении пакета на узел сети узел маршрутизации должен направить его на следующий узел сети по заданному пути в конечное местоположение пакета. Узел маршрутизации передает по одному пакеты, поэтому все допол- нительные пакеты помещаются в очередь, ожидая своего отправления маршрутизатором. Файловый сервер компьютерной сети управляет запросами на доступ к файлам от многих клиентов сети. Серве- ры имеют ограниченную мощность пропуска запросов от клиентов. При превышении этой пропускной способ- ности запросы ожидают обработки в очередях. ' Л В программе из листингов 20.7 и 20.8 создается класс очереди путем наследования от класса List'. Необходимо, чтобы класс Queueinheritance (листинг 20.7) имел методы Enqueue, Dequeue, IsEmpty и Print. Обратите внима- ние, ЧТО ЭТИ методы, ПО сути, ЯВЛЯЮТСЯ методами InsertAtBack, RemoveFromFront, IsEmpty И Print класса List. При этом класс List содержит и другие методы (например, InsertAtFront и RemoveFromBack), которые не будут доступны через интерфейс public класса очереди. Помните, что все методы в интерфейсе public класса List также являются методами public производного класса Queueinheritance. При реализации методов очереди необходимо, чтобы каждый метод класса Queueinheritance вызывал соответ- ствующий метод класса List: метод Enqueue вызывает метод InsertAtBack, метод Dequeue вызывает метод RemoveFromFront, а методы IsEmpty и Print вызывают версии их базового класса. Класс Queueinheritance не определяет методы IsEmpty и Print, потому что Queueinheritance наследует эти методы от класса List в ин- терфейс public класса Queueinheritance. При этом методы в классе Queueinheritance не используют операто- ров lock. Каждый из методов данного класса вызывает метод, содержащий оператор lock в классе List. Обра- тите внимание, что в классе Queueinheritance используется пространство имен LinkedListLibrary (см. лис- тинг 20.2); следовательно, решение для библиотеки классов, определяющей класс Queueinheritance, должно иметь ссылку на библиотеку классов LinkedListLibrary. 1 // Листинг 20.7: QueueInheritanceLibrary.cs 2 // Реализация очереди путем наследования от класса List 3 4 using System; 5 using LinkedListLibrary; 6 7 namespace QueuelnheritanceLibrary 8 { 9 // класс Queueinheritance наследует функции класса List 10 public class Queueinheritance : List 11 { 12 // передача имени "queue" в конструктор класса List' 13 public Queueinheritance() : base("queue") 14 { 15 } 16 17 // размещение dataValue в конец очереди вставкой 18 // dataValue в конец связанного списка 19 public void Enqueue(object dataValue) 20 { 21 InsertAtBack(dataValue); 22 } 23 24 // удаление элемента из начала очереди удалением 25 // элемента из начала связанного списка 26 public object Dequeue() 27 { 28 return RemoveFromFront(); 29 } 30 31 } // конец класса Queueinheritance 32 } В методе Main класса QueuelnheritanceTest (листинг 20.8) для создания очереди объектов object с именем queue используется класс Queueinheritance. В строках 18—21 определяются четыре объекта, которые будут
Структуры данных и коллекции.759 \ помещаться в очередь и удаляться из нее. Программа помещает в очередь (строки 24, 26, 28 и 30) переменную bool, содержащую значение true, символ char, содержащий $, переменную int, равную 34567, и строку, содер- жащую слово "hello". Бесконечный цикл while (строки 39—44) выводит элементы из очереди в порядке FIFO. Когда объектов для вывода из очереди не остается, метод Dequeue выбрасывает исключение EmptyListException, и программа ото- бражает след (состояние) стека в момент обработки исключения. Для вывода содержимого очереди после каж- дой операции программа вызывает метод Print (унаследованный от класса List). Обратите внимание, что класс QueuelnheritanceTest использует пространство имен LinkedListLibrary (см. листинг 20.2) и пространство имен QueuelnheritanceLibrary (см. листинг 20.7); таким образом, решение для класса QueuelnheritanceTest должно иметь ссылки на библиотеки обоих классов. 1 // Листинг 20.8: QueucTest.es 2 // Тестирования класса Queueinheritance 3 4 using System; 5 using QueuelnheritanceLibrary; 6 using LinkedListLibrary; 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 namespace QueueTest { // демонстрирует функциональность класса Queueinheritance class QueueTest { static void Main(string [] srgs) { Queueinheritance queue = new Queueinheritance(); // создание объектов для сохранения в стеке bool aBoolean = true; char aCharacter = '$'; int anlnteger = 34567; string aString = "hello"; // использование метода Enqueue для добавления элементов в очередь queue.Enqueue(aBoolean); queue.Print(); queue.Enqueue(aCharacter); queue.Print(); queue.Enqueue(anlnteger); queue.Print(); queue.Enqueue(aString); queue.Print(); // использование метода Dequeue для удаления элементов из очереди object removedObject = null; // удаление элементов из очереди try { while (true) { removedObj ect = queue.Dequeue(); Console.WriteLine(removedObj ect + "dequeue"); queue.Print(); } } // при возникновении исключения распечатать след стека catch (EmptyListException emptyListException)
760 Гпава 20 49 { 50 Console.Error .WriteLine( 51 emptyListException.StackTrace); 52 } 53 54 } // конец метода Main 55 56 } // конец класса QueueTest 57 } Результат работы программы: The queue is: True The queue is: True $ The queue is: True $ 34567. The queue is: True $ 34567 hello True dequeue The queue is: $ 34567 hello $ dequeue The queue is: 34567 hello 34567 dequeue The queue is: hello hello dequeue Empty queue at LinkedListLibrary.List.RemoveFromFront() in z:\ch20\Linkedlistlibrary\LinkedlistLibrary.cs:line 114 at QueuelnheritanceLibrary.Queueinheritance.Dequeue() in z:\ch20\Queueinheritancelibrary\ Queueinheritancelibrary.es:line 28 at QueueTest.QueuetTest.Main(String[] args) in z:\ch20\Fig20_14\Queuetest.cs:line 41 20.6. Деревья Связанные списки, стеки и очереди являются линейными структурами данных (т. е. последовательностями). Дерево представляет собой нелинейную, двумерную структуру данных, обладающую особыми свойствами. Узлы дерева содержат одну или несколько связок. В данном разделе рассматриваются двоичные деревья (рис. 20.7) — деревья, все узлы которых содержат две ссылки (из которых одна или обе могут иметь, либо не иметь значение null). Корневой узел — первый узел дерева. Каждая ссылка в корневом узле обозначает потом- ка. Левый потомок — это первый узел в левом поддереве, а правый потомок — первый узел в правом поддере- ве. Потомки в заданном узле называются братьями (т. е. вершинами дерева, имеющими общего "родителя"). Узел без потомков называется концевым. Компьютерщики обычно изображают деревья от корневого узла вниз — точно противоположно тому, как растут настоящие деревья. Распространенная ошибка программирования____________________________________________ Если не задать значение null ссылкам в концевых узлах дерева, то результатом станет логическая ошибка В приводимом примере двоичного дерева создается особое двоичные дерево, называемое деревом двоичного поиска. Дерево двоичного поиска (не имеющее дублирующихся значений узлов) обладает характеристикой, при которой значения в любом левом поддереве меньше значения в родительском узле этого поддерева, а значения в любом правом поддереве — больше значения в родительском узле данного поддерева. На рис. 20.8 показано дерево двоичного поиска с 12 целочисленными значениями. Обратите внимание, что форма дерева двоичного поиска, соответствующая множеству данных, может зависеть от порядка, в котором значения вставляются в дерево.
Структуры данных и коллекции 761 Рис. 20.7. Графическое представление двоичного дерева Рис. 20.8. Дерево двоичного поиска, содержащее 12 значений 20.6.1. Дерево двоичного поиска целочисленных значений В программном приложении из листингов 20.9 и 20.10 создается дерево двоичного поиска целочисленных зна- чений и его прохождение (т. е. перемещение по всем его узлам) тремя способами: с помощью рекурсивных про- хождений симметричным способом, а также прохождений в прямом и обратном порядке. Программа генери- рует 10 произвольных чисел и вставляет их в дерево. В листинге 20.9 определяется класс Tree в пространстве имен BinaryTreeLibrary для целей многократного использования. В листинге 20.10 определяется класс TreeTest для демонстрирования функциональности класса Tree. Метод Main класса TreeTest создает пустой объект Tree, после чего произвольно генерирует 10 чисел и вставляет каждое значение в двоичное дерево путем вызова метода insertMode класса Tree. Затем программа выполняет прохождение дерева в прямом, обратном порядке, а также симметричным способом. Эти способы описываются ниже. 1 // Листинг 20.9: BinaryTreeLibrary.cs 2 // Определение класса TreeNode и Tree 3 4 using System; 5 б namespace BinaryTreeLibrary 7 { 8 // определение класса TreeNode ,9 class TreeNode 10 { 11 private TreeNode leftNode; 12 private int data; 13 private TreeNode rightNode; 14 15 // инициализация данных и создание концевого узла 16 public TreeNode( int nodeData) 17 ( 18 data = nodeData; 19 leftNode = rightNode = null; У/ узел не имеет потомков 20 } 21 22 // свойство LeftNode 23 public TreeNode LeftNode 24 { 25 get 26 { 27 return leftNode; 28 } ' 29 30 set 31 {
762 Гпава 20 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 К 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 } 96 leftNode = value; } } // свойство Data public int Data { get { return data; } set { data “ value; } } // свойство RightNode public TreeNode RightNode { get { return rightNode; } set { rightNode = value; } } // вставка TreeNode в дерево, содержащее узлы; // игнорировать дублирующиеся значения public void Insert(int insertValue) { // вставка в левое поддерево if (insertValue < data) { 11 вставка нового класса TreeNode if (leftNode == null) leftNode = new TreeNode(insertvalue); // продолжение прохождения левого поддерева else leftNode.Insert(insertValue); ) // вставка в правое поддерево else if (insertvalue > data) { 11 вставка нового класса TreeNode if (rightNode = null) rightNode « new TreeNode(insertValue); // продолжение прохождения правого поддерева else rightNode.Insert(insertvalue); } ) // конец метода Insert // конец класса TreeNode
Структуры данных и коллекции 763 97 // определение класса Tree 98 public class Tree 99 { 100 101 private TreeNode root; 102 // построение пустого класса Tree целых чисел 103 public TreeO 104 { 105 root = null; 106 107 } 108 // Вставка нового узла в дерево двоичного поиска. 109 // Если корневой узел имеет значение null, создать 109а // здесь корневой узел. 110 //В противном случае вызвать метод Insert класса TreeNode 111 public void InsertNode(int insertValue) 112 { 113 lock (this) 114 { 115 if (root — null) 116 117 root = new TreeNode(insertvalue); 118 else 119 root.Insert(insertvalue); 120 } 121 122 } 123 // начало прохождения в прямом порядке 124 public void PreorderTraversal() 125 { 126 lock (this) 127 { 128 PreorderHelper(root); 129 } 130 131 ) 132 // рекурсивный метод для прохождения в прямом порядке 133 private void PreorderHelper(TreeNode node) 134 { 135 if (node = null)* 136 137 return; 138 // вывод данных узла 139 Console.Write(node.Data + " "); 140 141 // прохождение левого поддерева 142 143 PreorderHelper(node.LeftNode); 144 // прохождение правого поддерева 145 PreorderHelper(node.RightNode); 146 147 } 148 // начало симметричного прохождения 149 public void InorderTraversal() 150 { 151 lock (this) 152 { 153 InorderHelper(root); 154 } 155 156 } 157 // рекурсивный метод симметричного прохождения 158 private void InorderHelper(TreeNode node)
764 Гпава 20 159 { 160 if (node = null) 161 return; 162 163 // прохождение левого поддерева 164 InorderHelper(node.LeftNode); 165 166 // вывод данных узла 167 Console.Write(node.Data + " "); 168 169 // прохождение правого поддерева 170 InorderHelper(node.RightNode); 171 } 172 173 // начало прохождения в обратном порядке 174 public void PostorderTraversal() 175 { 176 lock (this) • 177 { 178 PostorderHelper(root); 179 } 180 } 181 182 // рекурсивный метод прохождения в обратном порядке 183 private void PostorderHelper(TreeNode node) 184 { 185 if (node == null) 186 return; 187, 188 // прохождение левого поддерева 189 PostorderHelper(node.LeftNode); 190 191 // прохождение правого поддерева 192 PostorderHelper(node.RightNode); 193 194 // вывод данных узла 195 Console.Write(npde.Data + " "); 196 ) 197 198 ’ } //конец класса Tree 199 } 1 // Листинг 20.10: TreeTest.es 2 // Программа тестирует класс Tree 3 4 using System; 5 using BinaryTreeLibrary; 6 7 namespace TreeTest 8 { 9 // определение класса TreeTest 10 public class TreeTest 11 { 12 // тестирование класса Tree 13 static void Main(string[] args) 14 { 15 Tree tree = new TreeO; 16 int insertvalue; 17 18 Console.WriteLine("Inserting values: "); 19 Random random = new RandomO; 20
Структуры данных и коллекции 765 21 // вставка 10 произвольных чисел в дерево от 0 до 99 22 for (int i = 1; i <= 10; i++) 23 { 24 insertvalue = random.Next(100); 25 Console.Write(insertValue + " "); 26 27 tree.InsertNode(insertvalue); 28 } 29 30 // прямое прохождение дерева 31 Console.WriteLine("\n\nPreorder traversal"); 32 tree.PreorderTraversal(); 33 34 // симметричное прохождение дерева 35 Console.WriteLine("\n\nlnorder traversal”); 3 6 tree.InorderTraversal(); 37 38 // обратное прохождение дерева 39 Console.WriteLine("\n\nPostorder traversal"); 40 tree.PostorderTraversal(); 41 Console.WriteLine(); 42 } 43 44 } // конец класса TreeTest 45 ) Результат работы программы: Inserting values: 39 69 94 47 50 72 55 41 97 73 Preorder traversal 39 69 47 41 50 55 94 72 73 97 Inorder traversal 39 41 47 50 55 69 72 73 94 97 Postorder traversal 41 55 50 47 73 72 97 94 69 39 Класс TreeNode (листинг 20.9, строки 9—95) — самоотносимый класс, содержащий три элемента данных типа private: leftNode и rightNode типа TreeNode и элемент data типа int. Изначально каждый класс TreeNode явля- ется концевым узлом, поэтому конструктор (строки 16—20) инициализирует значения ссылок leftNode и rightNode как null. Свойства LeftNode (строки 23—34), Qata (строки 37—48) и RightNode (строки 51—62) обеспечивают доступ к элементам данных private объекта ListNode. Метод Insert (строки 67—93) класса TreeNode рассматривается ниже. Класс Тгёе (строки 98—198) манипулирует объектами класса TreeNode. Класс Tree имеет данные root типа private (строка 100) — ссылку на корневой узел дерева. Данный класс содержит метод InsertNode типа public (строки 111—121) для вставки в дерево нового узла, а также методы PreorderTraversal (строки 124—130), InorderTraversal (строки 149—155) и PostorderTraversal (строки 174—180) типа public ДЛЯ прохождения дерева. Каждый из этих методов вызывает отдельный рекурсивный утилитный метод для выполнения операций прохождения внутреннего представления дерева. Конструктор Tree (строки 103—106) инициализирует корень root со значением null, для указания на то, что изначально дерево пустое. Метод InsertNode класса Tree (строки 111—121) сначала блокирует объект Tree для безопасности потока, по- сле чего определяет, пустое дерево или нет. Метод InsertNode вызывает метод insert класса TreeNode (стро- ки 67—93), который рекурсивно определяет местоположение для нового узла в дереве и вставляет в него узел. В дереве двоичного поиска узел можно вставить только в качестве концевого узла. Метод insert класса TreeNode сравнивает значение для вставки со значением данных в корневом узле. Если вставляемое значение меньше значения данных корневого узла, тогда программа определяет, пустое левое под- дерево или нет (строка 73). Если левое поддерево пустое, тогда в строке 74 назначается новый класс TreeNode, инициализируется вводимым целым числом, и новый узел присваивается ссылке leftNode. В противном случае
766 Гпава 20 в строке 78 рекурсивно вызывается метод insert для левого поддерева для вставки значения в него. Если встав- ляемое значение превышает данные корневого узла, тогда программа определяет, пустое правое поддерево или нет (строка 85). Если правое поддерево пустое, тогда в строке 86 назначается новый класс TreeNode, инициали- зируется вводимым целым числом, и новый узел присваивается ссылке rightNode. В противном случае в стро- ке 90 рекурсивно вызывается метод insert для правого поддерева для вставки значения в него. Методы InorderTraversal, PreorderTraversal и PostorderTraversal вызывают вспомогательные методы InorderHelper (строки 158—171), PreorderHelper (строки 133—146) и PostorderHelper (строки 183—196), соответственно для прохождения дерева и распечатки значений узлов. Целью вспомогательных методов в клас- се Tree является обеспечение программиста возможностью начать прохождение без необходимости предвари- тельного получения ссылки на корневой узел (root) с последующим вызовом рекурсивного метода с этой ссыл- кой. Методы InorderTraversal, PreorderTraversal и PostorderTraversal просто принимают ссылку root типа private и передают ее в нужный вспомогательный метод для начала прохождения дерева. Для представленного ниже обсуждения используется дерево двоичного поиска, показанное на рис. 20.9. Рис. 20.9. Дерево двоичного поиска Метод InorderHelper (строки 158—171) определяет шаги для прохождения дерева симметричным способом: 1. Если аргумент — null, немедленный возврат. 2. Прохождение левого поддерева обращением к методу InorderHelper (строка 164). 3. Обработка значения в узле (строка 167). 4. Прохождение правого поддерева обращением к методу InorderHelper (строка 170). При симметричном прохождении дерева значение в узле не обрабатывается до обработки значений в левом поддереве этого узла. Симметричное прохождение дерева на рис. 20.9 следующее: б 13 17 27 33 42 48 Обратите внимание, что при симметричном прохождении дерева двоичного поиска значения распечатываются в возрастающем порядке. В процессе создания дерева двоичного поиска осуществляется фактическая сортировка данных; поэтому процесс называется сортировкой овиичного дерева. Методом PreorderHelper (строки 133—146) определяются шаги для прохождения дерева в прямом порядке: 1. Если аргумент — null, немедленный возврат. 2. Обработка значения в узле (строка 139). 3. Прохождение левого поддерева обращением к методу PreorderHelper (строка 142). 4. Прохождение правого поддерева обращением к методу PreorderHelper (строка 145). При прямом прохождении дерева обрабатывается значение в каждом узле. После обработки значения в каком- либо узле алгоритм обрабатывает значения сначала в левом поддереве, а затем в правом поддереве. Прямое прохождение дерева на рис. 20.9 следующее: 27 13 б 17 42 33 48 Методом PostorderHelper (строки 183—198) определяются шаги для прохождения дерева в обратном порядке: 1. Если аргумент — null, немедленный возврат. 2. Прохождение левого под дерева обращением к методу PostorderHelper (строка 189). 3. Прохождение правого поддерева обращением к методу PostorderHelper (строка 192). 4. Обработка значения в узле (строка 195). При обратном прохождении дерева обрабатывается значение в каждом узле после обработки значений всех по- томков данного узла. Обратное прохождение дерева на рис. 20.9 следующее: б 17 13 33 48 42 27
Структуры данных и коллекции 767 Дерево двоичного поиска упрощает процесс удаления дублирующихся значений. При построении дерева опера- ция вставки распознает попытки вставки дублирующихся значений, потому что дубликат следует за решениями о прямом или обратном прохождении так же, как и значение-оригинал. Таким образом, операция вставки, в ко- нечном итоге, сравнивает дубликат с узлом, содержащим то же значение. На данном этапе операция вставки может просто удалить дублирующееся значение. Поиск в двоичном дереве значения, соответствующего ключевому значению, осуществляется очень быстро; особенно это относится к "плотно сжатым" деревьям. В деревьях такого типа каждый уровень содержит почти в два раза больше элементов, нежели предыдущий. На рис. 20.9 представлено плотно сжатое двоичное дерево. Дерево двоичного поиска с п элементами имеет минимум log2n уровней. Следовательно, для нахождения соот- ветствия или для определения его отсутствия требуется log2w сравнений. Поиск в (плотно сжатом) дереве дво- ичного поиска, состоящем из 1000 элементов, требует максимум 10 сравнений, потому что 2,0> 1000. Поиск в (плотно сжатом) дереве двоичного поиска, состоящем из 1 000 000 элементов, требует максимум 20 сравнений, потому что 220 > 1 000 000. 20.6.2. Дерево двоичного поиска объектов /Comparable Пример двоичного дерева в разд. 20.6.1 прекрасно работает, когда все данные принадлежат к типу int. Предпо- ложим, что необходимы манипуляции двоичным деревом значений с плавающей точкой (double). Можно пере- писать классы TreeNode и Tree под другими именами и настроить их на манипуляции double-значениями. Точно так же для каждого типа данных можно создать заказные версии классов TreeNode и Tree. Результатом этого становится разрастание кода с осложнением управления им и сопровождения. Язык программирования С++ предоставляет технологию, называемую шаблонами, позволяющую один раз написать определение класса, по- сле чего компилятор сгенерирует новые версии класса для любого выбранного типа данных. В идеале, хотелось бы один раз определить функциональность двоичного дерева и использовать ее впоследст- вии для многих типов данных. Такие языки, как Java и С#, предоставляют полиморфные возможности, обеспе- чивающие однородные манипуляции всеми объектами. Использование этих возможностей позволяет проекти- ровать более гибкие структуры данных. В следующем примере используется преимущество полиморфных возможностей C# путем реализации классов TreeNode и Tree, манипулирующих объектами любого типа и использующих интерфейс icomparable (простран- ство имен System). Обязательным условием здесь является возможность сравнения объектов, сохраненных в двоичном поиске с тем, чтобы определить путь к точке вставки нового узла. Классы, реализующие интерфейс icomparable, определяют метод СошрагеТо, сравнивающий объект, активизирующий метод, с объектом, прини- маемым этим методом в качестве аргумента. Метод возвращает значение int, меньшее нуля, если вызывающий объект меньше объекта-аргумента, ноль, — если объекты равны, и положительное значение, — если вызываю- щий объект больше объекта-аргумента. При чтом вызывающий объект и объект-аргумент должны принадлежать к одному типу данных; в противном случае метод выбрасывает исключение ArgumentException. Программа, представленная в листингах 20.11 и 20.12, в некоторой степени расширяет программу, описанную в разд. 20.6.1, для манипуляций объектами. Одним из ограничений новых версий классов TreeNode и Tree в лис- тинге 20.11 является то, что каждый объект Tree может содержать объекты только одного типа данных (напри- мер, все string или все double). Если программа предпринимает попытку вставки разных типов данных в один объект Tree, то имеют место исключения ArgumentException. В классе TreeNode изменены только шесть строк кода (строки 13, 17, 38, 67, 70 и 82), а в классе Tree — только одна строка (111) для обеспечения обработки объ- ектов icomparable. За исключением строк 70 и 82, все прочие изменения просто заменили тип int на тип icomparable. В предыдущем примере в строках 70 и 82 для сравнения вставляемого значения со значением в заданном узле использовались операции < и >. Теперь объекты icomparable в этих строках сравниваются по- средством метода СошрагеТо интерфейса, после чего проверяется возвращенное методом значение для опреде- ления, меньше оно нуля (вызывающий объект меньше объекта-аргумента) или больше (вызывающий объект больше объекта-аргумента) соответственно. В классе TreeTest (листинг 20.12) создаются три объекта Tree для сохранения значений int, double и string, каждое из которых .NET Framework определяет как принадлежащие к типу icomparable. Программа заполняет деревья значениями В массивах intArray (строка 15), doubleArray (строки 16 и 17) И stringArray (строки 18 и 19) соответственно. Метод PopulateTree (строки 38—48) принимает массив Array, содержащий значения инициализатора для клас- са Tree, класс Tree, в который будут помещаться элементы массива, и строку string, представляющую в каче- стве аргументов имя Tree, после чего вставляет каждый элемент массива Array в класс Tree. Метод TraverseTree (строки 51—68) принимает класс Tree и строку string, представляющую в качестве аргументов имя Tree, после чего выводит результаты прямого, обратного и симметричного прохождения класса Tree. Обра-
768 Гпава 20 тите внимание, что при симметричном проходе каждого класса Tree данные выводятся в отсортированном по- рядке, независимо от типа данных, сохраненных в классе. Полиморфная реализация класса Tree активизирует метод СошрагеТо соответствующего типа данных для определения пути к точке вставки каждого значения, ис- пользуя правила вставки для стандартного дерева двоичного поиска. Также обратите внимание, что дерево Tree для строк отображается в алфавитном порядке. 1 // Листинг 20.11: BinaryTreeLibrary2.cs 2 // Определение классов TreeNode и Tree для объектов 3 // типа IComparable 4 5 using System; 6 7 namespace BinaryTreeLibrary2 8 { 9 // определение класса TreeNode 10 class TreeNode 11 { 12 private TreeNode leftNode; 13 private IComparable data; 14 private TreeNode rightNode; 15 16 11 инициализация данных и создание концевого узла 17 public TreeNode( IComparable nodeData) 18 { 19 data = nodeData; 20 leftNode = rightNode = null; // узел не имеет потомков 21 } 22 23 // свойство LeftNode 24 public TreeNode LeftNode 25 { 26 get 27 { 28 return leftNode; 29 } 30 31 set 32 { 33 leftNode = value; 34 ) 35 } 36 37 // свойство Data 38 public IComparable Data 39 { 40 get 41 { ’ 42 return data; 43 } 44 45 set 46 { 47 - data = value; 48 } 49 } 50 51 // свойство RightNode 52 public TreeNode RightNode 53 { 54 get
Структуры данных и коллекции 769 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 { return rightNode; } set { rightNode = value; ) } // вставка TreeNode в Tree, содержащее узлы; // игнорировать дублирующиеся значения public void Insert(IComparable insertvalue) ( // вставка в левое поддерево' if (insertValue.CompareTo(data) < 0) < // вставка нового класса TreeNode if (leftNode = null) leftNode = new TreeNode(insertvalue); // продолжение прохождения левого поддерева else leftNode.Insert(insertValue); ) // вставка в правое поддерево else if (insertvalue.CompareTo(data) > 0) { // вставка нового класса TreeNode if (rightNode == null) rightNode = new TreeNode(insertValue); // продолжение прохождения правого поддерева else rightNode.Insert(insertvalue); } } // конец метода Insert ) // конец класса TreeNode // определение класса Tree public class Tree { private TreeNode root; // построение пустого класса Tree целых чисел public Tree() { root = null; ) // Вставка нового узла в дерево двоичного поиска. // Если корневой узел имеет значение null, создать здесь // корневой узел. Иначе вызвать метод Insert класса TreeNode. public void InsertNode(IComparable insertvaluej { lock (this) { if (root = null) root = new TreeNode(insertvalue); 49 Зак. 3333
770 Гпава 20 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 else root.Insert(insertValue); ) } // начало прохождения в прямом порядке public void PreorderTraversal() { lock (this) { PreorderHelper(root); } } // рекурсивный метод для прохождения в прямом порядке private void PreorderHelper(TreeNode node) < if (node = null) return; // вывод данных узла Console.Write(node.Data + * "); // прохождение левого поддерева PreorderHelper(node.LeftNode); // прохождение правого поддерева PreorderHelper(node.RightNode); - ) // начало симметричного прохождения public void InorderTraversal() { lock (this) { InorderHelper(root); ) } // рекурсивный метод симметричного прохождения private void InorderHelper(TreeNode node) { if (node = null) return; 11 прохождение левого поддерева InorderHelper(node.LeftNode); // вывод данных узла Console.Write(node.Data + " "); // прохождение правого поддерева InorderHelper(node.RightNode); } 11 начало прохождения в обратном порядке public void PostorderTraversal/) { lock (this) { PostorderHelper(root); } )
Структуры данных и коллекции 771 182 // рекурсивный метод прохождения в обратном порядке 183 private void PostorderHelper(TreeNode node) 184 { 185 if (node == null) 186 return; 187 188 // прохождение левого поддерева 189 PostorderHelper(node.LeftNode); 190 191 // прохождение правого поддерева 192 PostorderHelper (node.RightNode); *• 193 194 I/ вывод данных узла 195 Console.Write(node.Data + " "); 196 } 197 198 } //конец класса Tree 199 } 1 // Листинг 20.12: TreeTest.es 2 // Тестирование класса Tree 3 4 using System; 5 using BinaryTreeLibrary2; 6 7 namespace TreeTest 8 < 9 // определение класса TreeTest 10 public class TreeTest 11 ( 12 // тестирование класса Tree 13 static void Main(string[] args) 14 { 15 int[] intArray = { 8, 2, 4, 3, 1, 7, 5, 6 }; 16 doubled doubleArray = 17 { 8.8, 2.2, 4.4, 3.3, 1.1, 7.7, 5.5, 6.6 }; 18 string[] stringArray = { "eight", "two", "four", 19 "three", "one", "seven", "five", "six” }; 20 21 // создание значения int класса Tree 22 Tree intTree = new Tree(); 23 PopulateTree(intArray, intTree, "intTree"); 24 TraverseTree(intTree, "intTree"); 25 26 // создание значения double класса Tree 27 Tree doubleTree = new Tree(); 28 PopulateTree(doubleArray, doubleTree, "doubleTree"); 29 TraverseTree(doubleTree, "doubleTree"); 30 31 // создание значения string класса Tree 32 Tree stringTree = new Tree(); 33 PopulateTree(stringArray, stringTree, "stringTree"); 34 TraverseTree(stringTree, "stringTree"); 35 } 36 37 // заполнение класса Tree массивом элементов 38 static void populateTree( 39 Array array, Tree tree, string name) 40 { 41 Console.WriteLine("\nlnserting into " + name + ”:"); 42 *
772 Глава 20 43 foreach (IComparable data in array) 44 { 45 Console.Write(data + " "); 46 tree.InsertNode(data); 47 } 48 } 49 50 // вставка выполняет прохождения 51 static void traverseTree(Tree tree, string treeType) 52 { 53 // выполнение прямого прохода Дерева 54 Console.WriteLine( 55 "\n\nPreorder traversal of " + treeType; 5 6 tree.PreorderTraversal(); 57 58 // выполнение симметричного прохода дерева 59 Console.WriteLine( 60 ”\n\nlnorder traversal os " + treeType); 61 tree.InorderTraversal(); 62 63 // выполнение обратного прохода дерева 64 Console.WriteLine( 65 "\n\nPostorder traversal of " + treeType); 66 tree.PostorderTraversal(); 67 Console.WriteLine("\n"); 68 } 69 70 } // конец класса TreeTest 71 } Результат работы программы: Inserting into intTree: 82431756 Preorder traversal of intTree 82143756 Inorder traversal of intTree 12345678 Postorder traversal of intTree 13657428 Inserting into doubleTree: 8. 8 2.2 4.4 3.3 1.1 7.7 5.5 6.6 Preorder traversal of doubleTree 8. 8 2.2 1.1 4.4 3.3 7.7 5.5 6.6 Inorder traversal of doubleTree 1. 1 2.2 3.3 4.4 5.5 6.6 7.7 8.8 Postorder traversal of doubleTree 1. 1 3.3 6.6 5.5 7.7 4.4 2.2 8.8 Inserting into stringTree: eight two four thre one seven Preorder traversal of stringTree eight two four five three one five six seven six
Структуры данных и коллекции 773 Inorder traversal of stringTree eight five four one seven six Postorder traversal of stringTree five six seven one three four three two two eight 20.7. Коллекции классов В предыдущих разделах главы рассматривалось создание структур данных и манипуляции ими. Обсуждение велось на "низком уровне" в том смысле, что авторы кропотливо и динамически создавали каждый элемент каждой структуры данных с помощью объекта new и модифицировали структуры данных прямыми манипуля- циями их элементов и ссылок на элементы. В данном разделе рассматриваются предварительно распределенные по пакетам классы структур данных, предоставленные .NET Framework. Эти классы называются коллекциями классов (collection classes): в них сохранены коллекции данных. Каждый экземпляр одного из этих классов называется коллекцией, представляющей собой набор элементов. С помощью коллекций классов вместо создания структур данных программист просто использует существую- щие структуры данных, не заботясь об их реализации. Данная методология представляет собой замечательный пример повторного использования кода. Разработчики тратят гораздо меньше времени на написание кодов и ожидают очень высокой производительности за счет доведения до максимума скорости исполнения и сведения к минимуму потребления ресурсов памяти. Некоторыми примерами коллекций являются карты в карточной игре, любимые песни, сохраненные на компь- ютере, а также записи о недвижимости в реестре сделок (в которых отражены номера книг и страниц по каждо- му владельцу объектов недвижимости). .NET Framework предоставляет несколько коллекций. В данной главе продемонстрированы четыре коллекции классов: Array, ArrayList, stack и Hashtable (все они принадлежат пространству имен System.Collections), а также некоторые интегрированные функции массивов. Кроме того, пространство имен System.Collections представляет несколько других структур данных, включая BitArray (коллекция значений true/false), Queue и SortedList (коллекция пар "ключ/значение", отсортированных по ключу, доступ к которым осуществляется либо по ключу, либо по номеру). .NET Framework предоставляет готовые компоненты многократного использования; разработчику нет необхо- димости написания собственных коллекций классов. Коллекции стандартизированы, поэтому их легко исполь- зовать в любых программных приложениях, не вдаваясь в подробности реализации. Коллекции создаются для многократного применения. Они настроены на оперативное исполнение, а также на эффективное использование ресурсов памяти. По мере создания новых структур данных и алгоритмов, подходящих для такой структуры, многие программисты познакомятся с интерфейсами и алгоритмами, реализованными указанными структурами данных. 20.7.1. Класс Array В главе 4 представлены базовые возможности обработки массивов; в последующих главах использовались ме- тодики, описанные в главе 4. Коротко упоминалось о том, что все массивы наследуют от класса Array (про- странство имен System), определяющего свойство Length, которое задает число элементов в массиве. Кроме того, класс Array предоставляет методы типа static с алгоритмами для обработки массивов. Класс Array обыч- но перегружает эти методы для предоставления многочисленных опций для исполнения алгоритмов. Например, метод Reverse класса Array может обратить порядок элементов во всем массиве, либо обратить порядок эле- ментов в заданном диапазоне массива. Полный список методов static класса Array и их перегруженных версий представлен в интерактивной документации по данному классу. В листинге 20.13 приведено несколько методов static класса Array. 1 // Листинг 20.13: UsingArray.cs 2 // Использование класса Array для выполнения обычных манипуляций 3 4 using System; 5 using System.Windows.Forms; 6 using System.Collections; 7
774 Гпава 20 8 9 -10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 '63 64 65 66 67 68 69 70 71 namespace UsingArray { // демонстрация алгоритмов класса Array class UsingArray { private int[] intValues = { 1, 2, 3, 4, 5, 6 ); private doublet] doubleValues = { 8.4, 9.3, 0.2, 7.9, 3.4 }; private int|] intValuesCopy; private string output; // метод построения и отображения выходных данных программы public void Start() { intValuesCopy = new int[ intValues.Length ]; output = ’’Initial array values:\n"; PrintArray(); // вывод первоначального содержимого массива // сортировка doubleValues Array.Sort(doubleValues); // копирование intValues в intValuesCopy Array.Copy(intValue s, intValuesCopy, intValues.Length); i output += ”\Array values after Sort and Copy:\n"; PrintArray(); // вывод содержимого массива output += "\n"; // поиск цифры 5 в методе intValues int result = Array.Binarysearch(intValues, 5); output += (result >= 0 ? ”5 found at element " + result : "5 not found") + " in intValues\n"; // поиск числа .87 63 в intValues result = Array.Binarysearch(intValues, 8763); output += (result >= 0 ? ”8763 found at element " + result : "8763 not found") + " in intValues"; MessageBox.Show(output, "Using Class Array", MessageBoxButtons.OK, MessageBoxIcon.Information); } // добавление содержимого массива в строку вывода private void PrintArray() ( output += "doubleValues: "; foreach (double element in doubleValues) output += element + " "; output += "\nintValues: foreach (int element in intValues) output += element + " "; I output += "XnintValuesCopy: "; foreach (int element in intValuesCopy) output += element + " ";
Структуры данных и коллекции 775 72 output += "\п" ; 73 } 74 75 // главная точка входа для приложения 76 static void Main(string!] args) 77 { 78 UsingArray application = new UsingArray () ; 79 80 application.Start(); 81 } 82 83 } // конец класса UsingArray 84 } Рис. 20.10. Демонстрация возможностей класса Array В строке 28 используется static-метод Sort класса Array для сортировки массива значений типа double. Воз- вращаемые этим методом данные содержат первоначальные элементы, отсортированные в возрастающем по- рядке. В строках 31 и 32 используется static-метод Сору класса Array для копирования элементов массива intArray в массив intArrayCopy. Первый аргумент — массив для копирования (intvalues), второй — массив назначения (intvaluesCopy), а третий — целое число, обозначающее количество элементов для копирования (в данном слу- чае все Элементы обозначают объект intvalues.Length). В строках 39 и 45 активизируется static-метод Binarysearch класса Array для выполнения двоичного поиска в массиве intvalues. Метод Binarysearch принимает отсортированный массив, в котором осуществляется по- иск, а также ключ поиска. Метод возвращает указатель в массиве, по которому обнаруживается ключ (или отри- цательное число, если ключ не обнаружен). В число других методов типа static класса Array входят метод clear (для сброса диапазона элементов в 0 или null), метод Createinheritance (для создания нового массива заданного типа данных), метод IndexOf (для об- наружения первого случая появления объекта в массиве или в части массива), метод Lastindexof (для обнару- жения последнего появления объекта в массиве или его части) и метод Reverse (для обращения содержимого массива или его части). Результат работы программы представлен на рис. 20.10. 20.7.2. Класс ArrayList В большинстве языков программирования обычные массивы имеют фиксированный размер: их нельзя изменять динамически для соответствия требованиям времени исполнения и необходимой памяти программному прило- жению. В некоторых приложениях ограничение по размеру представляет собой довольно серьезную проблему для разработчиков. Им приходится выбирать между применением массивов достаточно большого фиксирован- ного размера для сохранения максимального числа элементов, которое может потребоваться программе, и ис- пользованием динамических структур данных, которые могут расширять и сокращать объем памяти, необходи- мый для сохранения данных в ответ на изменяющиеся требования программы во время исполнения. Коллекция класса ArrayList .NET Framework имитирует функциональность традиционных массивов и обеспе- чивает динамическое изменение размера коллекции посредством методов класса. В любой момент времени коллекция ArrayList содержит определенное число элементов, меньшее или равное емкости— количеству элементов, зарезервированных в текущее время для коллекции ArrayList. Программа может манипулировать емкостью посредством свойства Capacity класса ArrayList. При необходимости расширения класса ArrayList текущая емкость (capacity) удваивается по умолчанию. Совет по повышению производительности________________________________________________ Как и в случае со связанными списками, результатом вставки в класс ArrayList дополнительных элементов с текущим размером, меньшим, чем емкость, становится ускорение выполнения операции. Совет по повышению производительности________________________________________________ Операция вставки в класс ArrayList элемента, который должен расширяться для соответствия новому элемен- ту, выполняется достаточно медленно. Совет по повышению производительности________________________________________________ Если во главу угла ставится сохранение элементов, воспользуйтесь методом TrimToSize класса ArrayList для установки точного размера (подгонки) класса ArrayList. При этом оптимизируется использование памяти клас-
776 Гпава 20 са ArrayList. Здесь следует соблюдать осторожность: если программа должна вставить дополнительные эле- менты, то процесс замедлится, потому что класс ArrayList должен расширяться динамически (подгонка не ос- тавляет места для расширения). Совет по повышению производительности Приращение емкости по умолчанию — удвоение размера класса ArrayList — может показаться пустым расхо- дом пространства для сохранения, однако удвоение емкости является эффективным способом быстрого расши- рения класса ArrayList "практически до нужного размера". При этом использование времени становится более эффективным, нежели при увеличении размера класса ArrayList по одному элементу во время ответа на опе- рации вставки. Класс ArrayList сохраняет ссылки на object. Все классы являются производными от класса object, поэтому класс ArrayList может содержать объекты любого типа. В табл. 20.1 перечислены некоторые полезные методы класса ArrayList. Таблица 20.1. Некоторые методы класса ArrayList Метод Описание Add Добавление объекта object в класс ArrayList. Возвращает значение int, указывающее указа- тель, в который добавлен object Clear Удаление всех элементов из класса ArrayList Contains Возвращение значения true, если указанный объект object находится в классе ArrayList; в про- тивном случае, возвращение значения false IndexOf Возвращение указателя первого появления указанного объекта object в классе ArrayList Insert Вставка объекта object согласно заданному индексу Remove Удаление первого появления указанного объекта object RemoveAt Удаление объекта по заданному индексу RemoveRange Удаление заданного количества элементов начиная с заданного индекса в классе ArrayList Sort Сортировка класса ArrayList , TrimToSize Задание емкости (Capacity) класса ArrayList текущим количеством элементов в классе ArrayList В листинге 20.14 представлен класс ArrayList и несколько его методов. Пользователи могут ввести строку в текстовое поле (TextBox) пользовательского интерфейса и нажать кнопку, реализующую метод класса ArrayList, для просмотра функций данного метода. В другом текстовом поле TextBox отображаются сообще- ния, указывающие результаты каждой операции. I- ‘ ' ./ ' -- ’ X .. ' : С ' '' ' ‘ ; Листинг 20.14. Демонстрация класса ArrayList ж . -'SftOT ХСЛ. I ... ...................................................................................................... 1 // Листинг 20.14: ArrayListTest.cs 2 // Использование класса ArrayList 3 4 using System; 5 using System.Drawing; 6 using System.Collections; 7 using.System.ComponentModel; 8 using.System.Windows.Forms; 9 using.System.Data; 10 using.System.Text; 11 12 namespace ArrayListTest 13 { 14 // демонстрация функциональности класса ArrayList 15 public class ArrayListTest : System.Windows.Forms.Form 16 { 17 private.System.Windows.Forms.Button addButton; 18 private.System.Windows.Forms.TextBox inputTextBox; 19 private.System.Windows.Forms.Label inputLabel;
Структуры данных и коллекции 777 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53» 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 private.System.Windows.Forms.Button removeButton; private.System.Windows.Forms.Button firstButton; private.System.Windows.Forms.Button lastButton; private.System.Windows.Forms.Button isEmptyButton; private.System.Windows.Forms.Button containsButton; private.System.Windows.Forms.Button locationButton; private.System.Windows.Forms.Button trimButton; private.System.Windows.Forms.Button statisticsButton; private.System.Windows.Forms.Button displayButton; // требует переменную программы-конструктора private System.Comp,onentModel.Container components = null; private System.Windows.Forms.TextBox consoleTextBox; // класс ArrayList для манипуляций строками private ArrayList arrayList = new ArrayList(1); public ArrayListTest() { // необходимо для поддержки конструктора Windows-форм InitializeComponent(); } // код, сгенерированный Visual Studio .NET // главная точка входа для приложения [STAThread] static void Main() { Application.Run(new ArrayListtest()); } // добавление элемента в конец класса arrayList private void addButton_Click( object sender, System.EventArgs e) ( arrayList.Add(inputTextBox.Text); consoleTextBox.Text » "Added to end: ” + inputTextBox.Text; inputTextBox.Clear(); } // удаление указанного элемента из arrayList private void removeButton_Click( object sender, System.EventArgs e) { ' arrayList.Remove(inputTextBox.Text); consoleTektBox.Text = "Removed: " + inputTextBox.Text; inputTextBox.Clear(); } // отображение первого элемента private void firstButton_Click( object sender, System.EventArgs e) { // получение первого элемента try { consoleTextBox.Text = "First element: " + arrayList[ 0 ]; }
778 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 *111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 Гпава 20 // выбросить исключение при отсутствии элементов в классе arrayList catch (ArgumentOutOfRangeException outOfRange { consoleTextBox.Text = outOfRange.Tostring() ; } } // отображение последнего элемента private void lastButton_Click( object sender, System.EventArgs e) { // получение последнего элемента try { consoleTextBox.Text = "Last element: " + arrayListГ arrayList.Count — 1 ]; } // выбросить исключение при отсутствии элементов в классе arrayList catch (ArgumentOutOfRangeException outOfRange) { consoleTextBox.Text = outOfRange.Tostring(); j .. // определение заполнения класса arrayList private void isEmptyButton_Clipk( object sender, System.EventArgs e) { consoleTextBox.Text = (arrayList.Count == 0 ? "arrayList is empty" : "arrayList is not empty"); } // определение, содержит ли класс arrayList указанный объект private void containsButton_Click( object sender, System.EventArgs e) ( if (arrayList.Contains(inputTextBox.Text)) consoleTextBox.Text = "arrayList contains " + inputTextBox.Text; else consoleTextBox.Text = inputTextBox.Text + " not found"; // определение местоположения указанного объекта private void locationButton_Click( object sender, System.EventArgs e) { consoleTextBox.Text = "Element is at location " + arrayList.IndexOf(inputTextBox.Text); } // "подгонка" класса arrayList под текущий размер private void trimButtoh_Click( object sender, System.EventArgs e) { arrayList.TrimToSize(); consoleTextBox.Text = "Vector trimmed to size"; } // показать текущие размер и емкость класса arrayList private void statisticsButton_Click( object sender, System.EventArgs e)
Структуры данных и коллекции 779 146 { 147 cons,oleText Box. Text = "Size = " + arrayList.Count + 148 capacity = " + arrayList.Capacity; 149 } 150 151 // отображение содержимого класса arrayList 152 private void displayButton_Click( 153 object sender, System.EventArgs e) 154 { 155 lEnumerator enumerator = arrayList.GetEnumerator(); 156 StringBuilder buffer = new StringBuilder(); 157 158 while (enumerator.MoveNext()) 159 buffer.Append(enumerator.Current + " "); 160 161 consoleTextBox.Text = buffer.ToString() ; 162 } 163 } 164 } В данном примере в классе ArrayList сохраняются строки, вводимые пользователем в текстовое окно TextBox (рис. 20.11). В строке 35 создается класс ArrayList с первоначальной емкостью в один элемент. Размер данного класса ArrayList будет удваиваться всякий раз при заполнении пользователем массива и попытках добавления другого элемента. б Рис. 20.11. Демонстрация работы класса ArrayList: а — ввод элемента, б — нажатие кнопки Add; в — проверка пустоты массива; г — отображение текущего количества элементов и количества элементов, которое можно сохранить в классе ArrayList; д — вывод первого элемента массива Метод Add класса ArrayList добавляет новый элемент в конец массива ArrayList. При нажатии пользователем кнопки Add обработчик события addButton Click (строки 53—60) активизирует метод Add (строка 56) для до- бавления строки string В текстовое поле ввода (inputTextBox) класса ArrayList.
780 Глава 20 С помощью метода Remove класса ArrayList заданный элемент удаляется из класса ArrayList. При нажатии пользователем кнопки Remove обработчик события removeButton Click (строки 63—69) активизирует метод Remove (строка 66) для удаления указанной в текстовом поле ввода inputTextBox строки string из класса ArrayList. Если переданный в метод Remove объект находится в классе ArrayList, тогда удаляется первое появ- ление этого объекта, и все следующие за ним элементы смещаются к началу класса ArrayList для заполнения освобождающегося места. Программа может получить доступ к классу ArrayList как к обычному массиву элементов вводом оператора индекса массива ([]) после имени ссылки на ArrayList, а также нужного индекса элемента. Обработчики собы- тий firstButton_ciick (строки 72—87) и lastButton_ciick (строки 90—105) используют индексный оператор класса ArrayList для извлечения первого элемента (строка 79) и последнего элемента (строка 97) соответствен- но. Исключение ArgumentOutOfRangeException имеет место, если заданный индекс не больше нуля и не меньше количества элементов, сохраненных в текущий момент в классе ArrayList. В обработчике события isEmptyButton_ciick (строки 108—113) используется свойство Count (строка 111) клас- са ArrayList для определения заполненности класса ArrayList. В обработчике события containsButton Click (строки 116—125) есть метод Contains (строка 119) класса ArrayList для определения нахождения заданного объекта в классе ArrayList. Если указанный элемент размещен в классе, тогда метод возвращает значение true; в противном случае возвращается значение false. Совет по повышению производительности_______________________________________________________ Метод Contains класса ArrayList выполняет линейный поиск, являющийся довольно затратной операцией для классов ArrayList больших объемов. Если класс ArrayList отсортирован, воспользуйтесь методом Binarysearch класса ArrayList для выполнения более эффективного поиска. При нажатии пользователем кнопки Location обработчик события locationButton_ciick (строки 128—133) активизирует метод IndexOf (строка 132) класса ArrayList для определения индекса конкретного объекта в классе ArrayList. Метод IndexOf возвращает значение -1, если элемент не обнаружен. При нажатии пользователем кнопки Trim обработчик события trimButton ciick (строки 136—141) активизи- рует метод TrimToSize (строка 139) для присвоения свойству Capacity значения, равного значению свойства Count. При этом емкость сохранения класса ArrayList сокращается до точного количества элементов, разме- щенных В текущий момент В классе ArrayList. При нажатии пользователем кнопки Statistics в обработчике события statisticsButton Ciick (строки 144— 149) используются свойства Count и Capacity для отображения текущего количества элементов в классе ArrayList и максимального количества элементов, которое можно сохранить в классе ArrayList без выделения ему дополнительного объема памяти. При нажатии пользователем кнопки Display обработчик события displayButton_ciick (строки 152—162) выво- дит содержимое класса ArrayList. Данный обработчик использует объект lEnumerator (иногда называемый ну- мератором или итератором) для прохождения элементов (по одному) в классе ArrayList. Интерфейс lEnumerator определяет методы MoveNext и Reset, а также свойство Current. Метод MoveNext перемещает нуме- ратор к следующему элементу класса ArrayList. При первом вызове метод MoveNext размещает нумератор на первом элементе класса ArrayList. Метод MoveNext возвращает значение true, если в классе ArrayList имеется хотя бы на один элемент больше; в противном случае метод возвращает значение false. Метод Reset размещает нумератор перед первым элементом класса ArrayList. Методы MoveNext и Reset выдают исключение invalidOperationException, если содержимое коллекции было изменено после создания нумератора. С по- мощью свойства Current объект возвращается в текущее местоположение в коллекции ArrayList. В строке 155 создается интерфейс lEnumerator с именем enumerator, которому присваивается результат вызова метода GetEnumerator класса ArrayList. Строки 158 и 159 итерируются при возвращении методом MoveNext значения true, извлекают текущий элемент посредством свойства Count и добавляют его в буфер buffer. При прекращении цикла в строке 161 отображается содержимое буфера. 20.7.3. Класс Stack Как становится понятно из имени класса stack, он реализует стековую структуру данных. Данный класс обеспе- чивает большую часть функциональности, определенной при реализации в разд. 20.4. Читателям рекомендуется обратиться к указанному разделу для ознакомления с концепциями стековой структуры данных. В программном приложении из листинга 20.15 представлен GUI, дающий пользователю возможность тестирования многих ме- тодов класса stack. Работа программы продемонстрирована на рис. 20.12.
Структуры данных и коллекции 781 Листинг 20.15. Использован1'е * пасса Stack 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 // Листинг 20.15: StackTest.cs // Демонстрация класса Stack пространства имен System.Collections using System; using System.Drawing; using System.Collections; using.System.ComponentModel; using. System. Windows. Forms; using.System.Data; us ing.System.Text; namespace StackTest { // демонстрация коллекции Stack public class StackTest : System.Windows.Forms.Form { private.System.Windows.Forms.Label inputLabel; private.System.Windows.Forms.TextBox inputTextBox; private.System.Windows.Forms.Button pushButton; private.System.Windows.Forms.Button popButton; private.System.Windows.Forms.Button peekButton; private.System.Windows.Forms.Button isEmptyButton; private.System.Windows.Forms.Button searchButton; private.System.Windows.Forms.Button displayButton; private.System.Windows.Forms.Label statusLabel; // требуется переменная программы-конструктора private System.ComponentModel.Container components = null; private Stack stack; public StackTest() { // требуется для поддержки конструктора Windows-форм InitializeComponent(); // создание стека stack = new Stack(); ) // код, сгенерированный Visual Studio .NET // главная точка входа для приложения [STAThread] static void Main() { Application.Run(new StackTest()); } // проталкивание элемента в стек private void pushbutton_Click( object sender, System.EventArgs e) ( stack.Push(inputTextBox.Text); statusLabel.Text = "Pushed: " + inputTextBox.Text; } // вытеснение элемента из стека private void popButton_Click( object sender, System.EventArgs y)
782 Глава 20 61 62 63 64 65 66 67 68 69 70 71 72 "73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 • 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 11 вытеснение элемента try { statusLabel.Text = "Popped: " + stack.Pop(); } // распечатка сообщения, если стек пустой catch (InvalidOperationException invalidoperation) { statusLabel.Text = invalidoperation.ToString(); } } // считывание верхнего элемента в стеке private void peekButton_Click( object sender, System.EventArgs. e) { // просмотр верхнего элемента try { statusLabel.text = "Top: " + stack.Peek(); } ' // распечатка сообщения, если стек пустой catch (InvalidOperationException invalidoperation) ( statusLabel.Text = invalidOperation.ToString(j; } } // определение заполнения стека private void isEmptyButton_Click( object sender, System.EventArgs e) { statusLabel.Text = (stack.Count == о ? "Stack is empty" : "Stack is not empty"); } // определение наличия в стеке указанного элемента private void searchButton_Click( object sender, System.EventArgs e) { string result = stack.Contains(inputTextBox.Text) ? " found" : " not found"; statusLabel.Text = inputtextBox.Text + result; } // отображение содержимого стека private void displayButton_Click( object sender, System.EventArgs e) { lEnumerator enumerator = stack.GetEnumerator(); StringBuilder buffer = new StringBuilder(); // если нумератор может переместиться к следующему элементу, // распечатать этот элемент while (enumerator.MoveNext()) , buffer.Append(enumerator,Current + " "); statusLabel.Text - buffer.ToString();
Структуры данных и коллекции 783 Рис. 20.12. Демонстрация класса Stack: а — ввод первого значения; б — ввод второго значения; в — отображение содержимого стека; г — просмотр значения верхнего элемента; д — выталкивание верхнего элемента; е — отображение текущего содержимого стека В строке 38 конструктора stackTest создается класс stack с изначальным значением емкости, заданным по умолчанию (10 элементов). Как можно догадаться, класс stack имеет методы Push и Pop для выполнения базовых операций стека. Метод Push принимает объект object в качестве аргумента и добавляет его в верхнюю часть класса stack. Если коли- чество элементов в классе stack (свойство Count) равно его емкости на время выполнения операции Push, тогда stack увеличивается для размещения большего количества объектов object. Обработчик события pushButton Click (строки 51—56) использует метод Push для добавления в стек определенной пользователем строки (строка 54). Метод Pop не принимает никаких аргументов. С его помощью объект удаляется и возвращается в верхнюю часть класса stack. Обработчик события popButton Click (строки 59—73) вызывает метод Pop (строка 57) для удаления объекта из класса stack. Если на время вызова программой метода Pop класс stack пустой, то имеет место исключение InvalidOperationException. Методом Реек возвращается значение верхнего элемента в стеке, но сам элемент не удаляется из класса stack. Метод Реек демонстрируется в строке 82 обработчика события peekButton_Click (строки 76—90) для просмот- ра верхнего объекта в классе Stack. Как и в случае С методом Pop, исключение InvalidOperationException име- ет место, если класс stack — пустой при вызове программой метода Реек. Распространенная ошибка программирования_____________________________________________________ Попытка просмотра (Реек) или вытеснения (Pop) элемента из пустого класса Stack (свойство Count которого равно нулю) вызывает исключение InvalidOperationException. Обработчик события isEmptyButton_ciick (строки 93—98) определяет заполнение класса stack сравнением свойства Count класса с нулем. Если свойство Count класса stack равно нулю, это означает, что класс пустой; в противном случае— нет. Обработчик события searchButton_ciick (строки 101—108) использует метод Contains класса stack (строки 104 и 105) для определения наличия в нем объекта, указанного как аргумент. Me-
784 Гпава 20 тод Contains возвращает значение true, если класс stack содержит указанный элемент; в противном случае возвращается значение false. Обработчик события isEmptyButton_click (строки 111—123) использует интерфейс lEnumerator для прохож- дения класса stack и отображения его содержимого. 20.7.4. Класс Hashtable Языки объектно-ориентированного программирования упрощают создание новых типов. При создании про- граммой объектов новых или существующих типов ей (программе) требуется их последующее эффективное сопровождение. В этот процесс входит сортировка и извлечение объектов. Сортировка и извлечение информа- ции с помощью массивов эффективны, если какой-либо аспект данных полностью совпадает с ключевым значе- нием, если ключи уникальны и плотно сжаты. Предположим, в компании работают 100 сотрудников, каждый из которых имеет 9-значный код службы социального обеспечения, и данные о сотрудниках желательно сохранять и извлекать с помощью номера социального обеспечения в качестве ключа, но для этого потребуется номиналь- ный массив с 999 999 999 элементами, потому что существует 999 999 999 уникальных 9-значных чисел. Такая организация непрактична абсолютно для всех приложений, в которых в качестве ключа используются коды со- циального обеспечения. Если компания может себе позволить создание массива такого размера, то сохранение и извлечение информации о сотрудниках по их -кодам социального обеспечения в качестве индекса массива будут осуществляться весьма эффективно. Очень многие программные приложения сталкиваются с подобной проблемой, а именно: либо ключи в них не- корректного типа (т. е. не являются неотрицательными числами), либо их тип — корректен, но они размещены очень "разбросанно" в широком диапазоне. Что касается приведенного примера, то тут необходимо наличие высокоскоростной схемы преобразования та- кцх ключей, как коды службы социального обеспечения и артикулы наименований товаров на складе, в уни- кальные индексы массива. В этом случае, при необходимости сохранения программой какого-либо элемента, данная схема может оперативно преобразовать ключ приложения в индекс, и информация может быть сохране- на в указанном местоположении массива. Извлечение данных происходит подобным же образом: при наличии ключа, для которого программа предполагает извлечь информацию, просто применяется его (ключа) преобра- зование с получением указателя массива для местоположения нужной информации. Описываемая схема является основой методики, называемой хэшированием. От чего произошел такой термин? Дело в том, что при преобразовании ключа в указатель (индекс) массива происходит в буквальном смысле пе- ремешивание битов с образованием некоего "смешанного" числа, которое не имеет никакой реальной значимо- сти, кроме цели сохранения и извлечения той или иной записи данных из массива. Схема дает сбой при возник- новении конфликтов (т. е. два разных ключа "перемешиваются" в одной ячейке (или элементе) массива). По- скольку рассортировать две разные записи в одном пространстве нельзя, то необходимо найти альтернативное местоположение для всех записей, кроме первой, хэшированной в конкретном индексе массива. Для этой цели существует много различных механизмов. Один из них — "повторное хэширование" (т. е. повторное примене- ние к ключу преобразования хэширования для предоставления очередной ячейки в массиве). Процесс хэширо- вания не упорядочен, поэтому предполагается, что доступная ячейка будет найдена при нескольких ходах хэши- рования. ' В другой схеме используется один ход хэширования для определения местоположения первой ячейки-кандидата на размещение информации. Если ячейка занята, то осуществляется линейный поиск последующих ячеек до нахождения доступной. Извлечение информации происходит подобным же образом: ключ однократно хэширу- ется; результирующая ячейка проверяется на наличие в ней нужных данных^ При их наличии поиск завершает- ся. Если нужные данные отсутствуют, то осуществляется линейный поиск в последующих ячейках до нахожде- ния нужных данных. Самым популярным решением проблемы столкновений хэш-таблиц является превращение каждой ячейки хэш- таблицы в хэш-"сегмент" — типичный связный список всех пар "ключ/значение", кэшируемый в данную ячей- ку. Это решение реализуется классом Hashtable .NET Framework. Коэффициент нагрузки — один из факторов, влияющих на производительность схем хэширования. Коэффици- ент нагрузки — это отношение числа занятых ячеёк в хэш-таблице к размеру хэш-таблицы. Чем ближе данный коэффициент приближается к 1.0, тем выше вероятность столкновений. Совет по повышению производительности_____________________________________________________ Коэффициент нагрузки в хэш-таблице представляет собой классический пример компромисса между исполь- зуемым пространством и временем на совершение операций: с увеличением коэффициента нагрузки память ис- пользуется более эффективно, но программа работает медленнее из-за возрастающего количества конфликтов
Структуры данных и коллекции 785 при хэшировании. При уменьшении коэффициента нагрузки скорость исполнения программы увеличивается из- за снижения количества конфликтов при хэшировании, но память используется менее эффективно из-за того, что большая часть хэш-таблицы остается пустой. Задача надлежащего программирования хэш-таблиц слишком сложна для большинства разработчиков- непрофессионалов. Студенты-электронщики досконально изучают схемы хэширования на курсах "Структуры данных" и "Алгоритмы". Признавая преимущества хэширования, разработчики языка C# предоставляют класс Hashtable и некоторые связанные с ним функции для того, чтобы программисты могли ими пользоваться, не вдаваясь в сложные подробности. Предыдущее предложение весьма важно при изучении объектно-ориентированного программирования. Классы инкапсулируют и скрывают всю внутреннюю сложность (т. е. подробности реализации), предлагая вполне дру- жественные пользовательские интерфейсы. Разработка классов для надлежащего решения этих задач является одним из наиболее ценных навыков в области объектно-ориентированного программирования. Хэш-функция выполняет расчеты, определяющие место размещения данных в хэш-таблице. Хэш-функция при- меняется к ключу в паре "ключ/значение" объектов. В качестве ключа класс Hashtable может принять любой объект. По этой причине класс object определяет метод GetHashCode, наследуемый всеми объектами в С#. Большинство классов-кандидато! на использование в качестве ключей хэш-таблиц подменяют этот метод для создания другого метода, выполняющего эффективные расчеты для особых типов данных. Например, строка имеет расчет хэш-кода, базирующийся на содержимом данной строки. В листинге 20.16 показано несколько методов класса Hashtable. Результат работы программы представлен на рис. 20.13. Листинг 20.16. Использование класса Hashtable ....... Л .Л.’,i... .г. . ..... X ......ж L........mi* 'Л.* 1 // Листинг 20.16: HashtableTest.cs 2 // Демонстрация класса Hashtable 2а // пространства имен System.Collections 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 System.Collections; using System; using System.Drawing; using System.Collections; using.System.ComponentModel; using.System.Windows.Forms; using.System.Data; using.System.Text; namespace HashTableTest // демонстрация функциональности класса Hashtable public class HashtableTest : System.Windows.Forms.Form { private.System.Windows.Forms.Label firstNameLabel; private.System.Windows.Forms.Label lastnNameLabel; private.System.Windows.Forms.Button addButton; private.System.Windows.Forms.TextBox lastNameTextBox; private.System.Windows.Forms.TextBox consoleTextBox; private.System.Windows.Forms.TextBox firstNameTextBox; private.System.Windows.Forms.Button private.System.Windows.Forms.Button private.System.Windows.Forms.Button private.System.Windows.Forms.Button private.System.Windows.Forms.Button private.System.Windows.Forms.Button private.System.Windows.Forms.Button getButton; removeButton; emptyButton; containsKeyButton; clearTableButton; listObjectsButton; listKeysButton; private.System.Windows.Forms.Label statusLabel; // требуется переменная программы-конструктора private System.ComponentModel.Container components = null; // класс Hashtable для демонстрации функциональности private Hashtable table; 50 Зак. 3333
786 Гпава 20 3R 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 public HashTabletest() t { // требуется для поддержки конструктора Windows-форм InitializeComponent (); // создание объекта Hashtable(); table = new Hashtable(); } // код, Сгенерированный Visual Studio .NET // главная точка входа для приложения [STAThread] _ static void Mainf) { Application.Run(new HashTableTest()); _ } // добавление фамилии сотрудника и объекта Employee в таблицу private void addButton_Click( object sender, System.EventArgs e) { Employee employee = new Employee(firstNameTextBox.Text, lastNameTextBox.Text); // добавление новой пары "ключ/значение" try { table.Add(lastNameTextBox.Text, employee); statusLabel.Text = "Put: " + employee.Tostring(); } / // исключение, если ключ — null или уже имеется в таблице catch (ArgumentException argumentException) { statusLabel.Text « argumentException.ToString(); } } // получение объекта для заданного ключа private void getButton_Click( object sender, System.EventArgs e) { object result = tablet lastNameTextBox.Text ]; if (result != null) statusLabel.Text = "Get: " + result.ToString(); else statusLabel.Text = "Get: " + lastNameTextBox.Text + " not in table"; } // удаление из таблицы пары "ключ/значение" private void removeButton_Click( object sender, System.EventArgs e) { table.Remove(lastNameTextBox.Text); statusLabel.Text = "Object Removed"; } // определение заполнения таблицы private void emptyButtonClick( object sender. System.EventArgs e)
Структуры данных и коллекции 787 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 •121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 { stattisLabel .Text = "Table is " + ( table.Count == 0 ? "empty" : "not empty"); ) // определение наличия в таблице заданного ключа private void containsKeyButton_Click( object sender, System.EventArgs e) { statusLabel.Text = "Contains key: " + table.ContainsKey(lastNameTextBox.Text); } // сброс всего содержимого таблицы private void clearTableButton_Click( object sender, Syste.EventArgs e) { table.Clear.(); statusLabel.Text = "Clear: Table is now empty"; } // отображение списка объектов в таблице private void listObjectsButton_Click( object sender, System.EventArgs ₽) ( IDictionaryEnumerator enumerator = table.GetEnumeratdr(); StringBuilder buffer = new StringBuilder(); while (enumerator.MoveNext()) buffer.Append(enumerator.Value + "\r\n"); ♦ consoleTextBox.Text = buffer.ToStringO; ) // отображение списка ключей в таблице private void listKeysButton_Click( object sender, System.EventArgs e) { IDictionaryEnumerator enumerator = table.GetEnumerator(); StringBuilder buffer = new StringBuilder(); while (enumerator.MoveNext()) buffer.Append(enumerator.Key + "\r\n"); consoleTextBox. Text = buffer.ToStringO; } } // конец класса HashtableTest // класс Employee для использования c HashtableTest class Employee { private string first, last; // конструктор public Employee(string fName, string IName) { first = fName; last = IName; )
788 Гпава 20 164 // возвращение в виде строки имени и фамилии сотрудника Employee 165 public override string ToString() 166 { 167 return first + " " + last; 168 } 169 170 } // конец класса Employee 171 } Рис. 20.13. Демонстрация работы класса Hashtable: а — ввод данных о первом сотруднике; б — ввод данных о втором сотруднике; в — вывод списка объектов; г — вывод списка ключей Обработчик события addButton Click (строки 57—75) считывает введенные имя и фамилию сотрудника, созда- ет объект класса Employee (определен в строках 153—170) и добавляет сотрудника (Employee) в класс Hashtable с помощью метода Add (строка 66). Этот метод принимает два аргумента: объект ключа и объект значения. В данном примере ключом является фамилия сотрудника Employee (string), а значение— соответствующий объект Employee. Исключение Argument Except ion ймеет место, если класс Hashtable уже содержит ключ, либо если ключ имеет значение null. Обработчик события getButton_ciick (строки 78—88) извлекает объект, относящийся к конкретному ключу, с помощью оператора индекса класса Hashtable, как показано в строке 81. Выражение в квадратных скобках
Структуры данных и коллекции 789 является ключом, для которого класс Hashtable должен возвратить соответствующий объект Если ключ не об- наружен, то результатом будет значение null. Обработчик события removeButton_click (строки 91—96) активизирует метод Remove класса Hashtable для уда- ления ключа и соответствующего ему объекта из класса Hashtable. Если ключа в таблице нет, то никакие дейст- вия не происходят. Обработчик события emptyButton Click (строки 99—104) использует свойство Count класса Hashtable для оп- ределения заполнения таблицы (т. е. равно ли свойство count нулю). Обработчик события containsKeyButton Click (строки 107—112) активизирует метод ContainsKey класса Hashtable для определения, содержит ли таблица Hashtable указанный ключ. Если содержит, тогда метод воз- вращает значение null; в противном случае возвращается значение false. Обработчик события clearTableButtonClick (строки 115—120) активизирует метод clear класса Hashtable для удаления всех записей из таблицы. Класс Hashtable предоставляет метод GetEnumerator, возвращающий нумератор типа IDictionaryEnumerator, который является производным от класса lEnumerator. Такие нумераторы предоставляют свойства Key и value для доступа к информации для пары "ключ/значение". Обработчик события в строках 123—134 (listobjectButton ciick) использует свойство Value нумератора для вывода объектов в класс Hashtable. Об- работчик события в строках 123—134 (listKeysButton_click) использует свойство Key нумератора для вывода ключей в класс Hashtable. 20.8. Резюме Динамические структуры данных могут расширяться и сжиматься во время исполнения программы. Создание и сопровождение динамических структур данных требует динамического распределения памяти: способности программы к получению большего объема памяти во время исполнения (для сохранения новых узлов) и к осво- бождению объемов памяти, необходимости в которых нет. Ограничение динамического распределения памяти может измеряться доступным объемом физической памяти компьютера, либо доступным объемом дискового пространства в системе виртуальной памяти. Самоотносимый класс содержит элемент данных, относящийся к объекту класса того же типа. Самоотносимые объекты могут объединяться между собой для образования полезных структур данных, таких как списки, очере- ди, стеки и деревья. Связанный список представляет собой линейную коллекцию (т. е. последовательность) объектов самоотносимо- го класса, называемых узлами, объединенными между собой звеньями ссылок. Узел может содержать данные любого типа, включая объекты других классов. Доступ к связному списку осуществляется посредством ссылки на первый узел списка. Доступ к каждому последующему узлу выполняется через номер звена ссылки, сохра- ненный в предыдущем узле. Для обозначения конца списка ссылка в последнем узле списка, как правило, имеет значение null. Стеки представляют важность для компиляторов и операционных систем. Стек — это ограниченная версия свя- занного списка; новые узлы добавляются в стек и удаляются из него только сверху. Стеки называются структу- рами данных, организованными по принципу LIFO. Основными операциями со стеками являются операции проталкивания и вытеснения. Операцией проталкивания в верхнюю часть стека добавляются новые узлы, а опе- рацией вытеснения узлы удаляются из верхней части стека и возвращают объект данных из вытесненного узла. Очереди являются цепочками ожидания. Вставка новых элементов осуществляется в конец очереди (иногда называется хвостом), а удаление — из ее начала (иногда называется головой). Очереди напоминают обычные очереди из покупателей в супермаркете: первый человек обслуживается первым; новые покупатели встают в конец очереди и ожидают своей очереди на обслуживание Точно так же узлы очереди удаляются только с голо- вы очереди и добавляются в конец очереди. По этой причине очередь является структурой данных, организо- ванной по принципу FIFO. Операции вставки элементов в очередь и их удаления из нее называются постанов- кой в очередь и выведением из очереди соответственно. Двоичные деревья упрощают процесс высокоскоростного поиска данных и их сортировки. Узлы дерева обычно содержат две или более ссылки. Двоичное дерево — это дерево, все узлы которого содержат только две ссылки. Корневой узел является первым узлом дерева. Каждая ссылка в корневом узле обозначает "потомка". Левый потомок является первым узлом в левом поддереве, а правый потомок — первый узел в правом поддереве. По- томки узла называются "братьями". Узел без потомков называется концевым узлом. Дерево двоичного поиска (без дублирующихся значений узлов) обладает характеристикой, при которой значе- ния в любом левом поддереве меньше значений родительского узла этого поддерева, а значения в любом пра-
790 Гпава 20 вом поддереве — больше значений родительского узла этого поддерева. В дерево двоичного поиска узел вста- вить можно только в виде концевого узла. При прохождении дерева двоичного поиска симметричным способом значения узлов обрабатываются в восхо- дящем порядке. В процессе создания дерева двоичного поиска имеет место фактическая сортировка данных, отсюда термин — ’’сортировка двоичного дерева". При прямом прохождении дерева значение в каждом узле обрабатывается по мере посещения узла. После обработки значения в отдельно взятом узЛе обрабатываются значения в левом поддереве узла, а после этого обрабатываются значения в правом поддереве узла. При обрат- ном прохождении дерева значение в каждом его узле обрабатывается после обработки левого и правого подде- ревьев данного узла. * Дерево двоичного поиска упрощает процесс удаления дублирующихся значений. При построении дерева опера- ция вставки распознает попытки вставки дублирующихся значений, потому что дубликат следует за решениями о прямом или обратном прохождении так же, как и значение-оригинал. Таким образом, операция вставки, в ко- нечном итоге, сравнивает дубликат с узлом, содержащим то же значение. На данном этапе операция вставки может просто удалить дублирующееся значение. t Предварительно созданные классы структуры данных, предоставляемые .NET Framework, называются коллек- * цией классов; в них сохраняются коллекции данных. Каждый экземпляр одного из этих классов называется кол- лекцией, состоящей из набора элементов.
ГЛАВА 21 Обеспечение доступности программных приложений Хорошей книгу делает только хороший читатель... Ральф Уолдо Эмерсон Когда-то я потерялся, Теперь меня нашли, Я был слеп, но теперь прозрел. Джон Ньютон Темы данной главы: □ представление Руководства по доступу к Web-содержимому версии 1.0 (Web Content Accessibility Guide- lines — WCAG 1.0) Консорциума World Wide Web; □ использование атрибута alt HTML-тега <img> с целью описания изображений для слабовидящих людей, пользователей портативных Web-устройств и других пользователей без возможности просмотра в сети гра- фических изображений; □ составление более доступных таблиц для посетителей Web-страниц; □ верификация надлежащего использования тегов XHTML и обеспечение просмотра Web-страниц на любом типе монитора или считывающего устройства; □ описание изменения способов доступа к информации пользователей с ограниченными физическими возмож- ностями программными пакетами VoiceXML и CallXML; □ представление инструментальных средств доступа в операционной системе Windows 2000. 21.1. Введение На протяжении книги мы рассматривали создание программных приложений на языке С#. В последних главах описана разработка содержимого на базе Web с помощью Web-форм, ASP.NET, XHTML и XML. В настоящей главе обсуждается тема доступности, имеющая отношение к степени удобства и простоте использования, обес- печиваемым Web-сайтами людям с ограниченными физическими возможностями. Последние являются доста- точно распространенным явлением и часто влияют на нормальную работу таких пользователей с компьютером и Интернетом. К ограниченным физическим возможностям относятся нарушения зрения, слуха, различные фи- зические травмы, делающие невозможным, например, использование клавиатуры или мыши. В современном компьютерном сообществе такие проблемы закрывают мир Интернета и преимущества программных приложе- ний для многих пользователей. Создание программ и Web-сайтов в соответствии с потребностями лиц с ограниченными физическими возмож- ностями должна стать приоритетным направлением для всех компаний-разработчиков программных средств и электронного бизнеса. Люди с физическими недостатками составляют внушительную долю общества, и на ком- пании, пренебрегающие обеспечением адекватного и универсального доступа к своим ресурсам, могут нала- гаться юридические санкции. В настоящей главе исследуются Инициатива доступа во Всемирную сеть, разра- ботанная Консорциумом World Wide Web, ее правила, а также рассматриваются различные законодательные акты, касающиеся доступности компьютерных и интернет-ресурсов для людей с физическими недостатками. Здесь же указываются компании, создавшие системы, продукты и службы, соответствующие потребностям дан- ной демографической группы. При использовании C# и связанных с этим языком технологий для разработки
792 Глава 21 программных средств и Web-сайтов читатели должны помнить о требованиях к доступности и рекомендациях, изложенных в настоящей главе. 21.2. Законодательство и компьютерные ресурсы За последние несколько лет в США в юридическом плане были сделаны шаги, обеспечивающие людей с физи- ческими недостатками инструментальными средствами, необходимыми для работы с компьютером и доступа во Всемирную сеть. Многочисленные законодательные постановления, включая Закон о гражданах США с огра- ниченными физическими возможностями (Americans With Disabilities Act, ADA) от 1990 года, регулируют дос- тупность к компьютерам и Интернету (табл. 21.1). Такие законы инспирировали многочисленные судебные ис- ки. Так, в соответствии с ADA, компании обязаны предоставлять адекватный доступ к своим ресурсам лицам с нарушениями зрения. Национальная федерация слерых (National Federation for the Blind, NFB) ссылалась на вышеуказанный закон в деле против компании AOL (America Online) в 1999 году в ответ на ее пренебрежение требованиями к обеспечению доступности предоставляемых компанией служб для людей с ограниченными фи- зическими возможностями. Таблица 21.1. Принятые в США законы, направленные на усовершенствование системы доступа к Интернету и компьютерам людей с ограниченными физическими возможностями Закон Цель Закон о гражданах США с ограниченными физическими возможностями (Americans with Disable Act, ADA) Закон о телекоммуникациях 1996 года (Telecommunications Act of 1996) ADA запрещает дискриминацию по причине ограниченных физических воз- можностей в том, что касается приема на работу, участия в работе государ- ственных и местных органов управления, пользования общественными бла- гами, торговыми предприятиями, транспортом и телекоммуникациями Закон о телекоммуникациях 1996 года имеет две поправки к статьям 255 и 251(a)(2) Закона о коммуникациях 1934 года. Поправки предписывают обя- зательную доступность для граждан с ограниченными физическими возмож- ностями средств коммуникации: сотовых телефонов, телефонов и пейдже- ров Закон об образовании лиц с ограниченными физическими возможностями 1997 года (Individuals with Disablities Education Act of 1997) Закон об образовании лиц с ограниченными физическими возможностями предписывает обязательную доступность учебных материалов в школах для детей с ограниченными физическими возможностями Закон о реабилитации инвалидов (Rehabilitation Act) Статья 504 Закона о реабилитации инвалидов гласит, что спонсируемая деятельность колледжей, состоящих на федеральном кредитовании, не должна дискриминировать интересы лиц с ограниченными физическими возможностями. Статья 508 требует от всех государственных институтов с федеральной финансовой поддержкой разработки Web-сайтов с учетом возможности доступа к ним лиц с ограниченными физическими возможно- стями. Предприятия, обслуживающие государственные учреждения, также обязаны соблюдать данный закон В США насчитывается 54 млн граждан данной категории; по различным оценкам они представляют группу с объемом покупательской способности порядка одного триллиона долларов. В дополнение к юридическим тре- бованиям, многие организации и ресурсы сосредотачивают свои усилия на помощи людям с физическими не- достатками в доступе к компьютерам и Интернету. WeMedia.com (рис. 21.1) — один из Web-сайтов, на котором представлены новости, информация, товары и услуги для миллионов людей с ограниченными физическими возможностями, а также для их семей, друзей и попечителей. Как показывают вышеприведенные законы и ресурсы, доступность компьютеров и Интернета для лиц с ограни- ченными физическими возможностями быстро становится реальностью. Такая доступность позволяет людям с физическими недостатками проявлять себя в огромном спектре новых областей деятельности. Отчасти это про- исходит потому, что Интернет предоставляет среду, посредством которой инвалиды могут осуществлять дис- танционный доступ к местам работы и общаться с другими гражданами, не выходя из дома. Такие технологии, как речевое управление, применение визуальных усилителей и слуховых вспомогательных средств, создают дополнительные возможности трудоустройства. Например, люди с нарушениями зрения могут пользоваться компьютерными мониторами с увеличенным текстом, а люди с физическими недостатками — головными ука- зателями вкупе с экранными клавиатурами. В последующих разделах главы рассматриваются различные орга- низации, методики, товары и услуги, обеспечивающие доступ к компьютерам и Интернету людям с ограничен- ными физическими возможностями.
Обеспечение доступности программных приложений 793 Рис. 21.1. Домашняя страница WeMedia (wemedia.com) 21.3. Инициатива доступности Web В настоящее время Web-сайты частично или полностью недоступны пользователям с нарушениями зрения, не- способным к обучению или ограниченным в передвижении. Полной доступности добиться весьма сложно, во- первых, из-за множества различных нарушений здоровья, которые придется учитывать, а во-вторых, из-за язы- ковых проблем и возможной несовместимости аппаратных и программных средств. Впрочем, довольно высо- кий уровень доступности вполне достижим. По мере того, как все больше людей с физическими ограничениями становятся пользователями Интернета, для разработчиков Web-сайтов настоятельным требованием является повышение уровня их доступности. Несмотря на то, что доступность к компьютерным средствам и Интернету стала предметом определенных законодательных актов, принятых за последнее время, организации, занимаю- щиеся стандартизацией, также видят необходимость в промышленных рекомендациях. С целью решения про- блемы доступности Консорциум WWW (W3C) предложил в апреле 1997 года Инициативу доступности Все- мирной сети (Web Accessibility Initiative, WAI). Подробно WAI и ее основные положения описаны на сайте www.w3.org/WAI. В данной главе рассматриваются различные методики, используемые при разработке доступных Web-сайтов. В 1999 году WAI опубликовала Руководство по доступу к Web-содержимому (Web Content Accessibility Guidelines, WCAG) версии I 0 в помощь коммерческим предприятиям при определении ими степени доступно- сти своих корпоративных Web-сайтов. В WCAG 1.0 (доступно на сайте www.w3.org/TR/WCAG10) использу- ются контрольные пункты для составления списков особых требований к доступности. Каждый контрольный пункт сопровождается соответствующим порядком очередности, указывающим степень важности того или ино- го требования. Контрольные пункты с приоритетом класса 1 — это цели, достижение которых должно обес- печить доступность; в настоящей главе эти пункты описываются подробно. Контрольные пункты с приорите- том класса 2 не столь существенны, но настоятельно рекомендуются. При несоблюдении этих пунктов лица с определенными физическими недостатками будут испытывать трудности при доступе к Web-сайтам. Контроль- ные пункты с приоритетом класса 3 повышают уровень доступности в небольшой степени. На время публикации данной книги WAI работала над WCAG 2.0; рабочая версия данного материала имеется на сайте www.w3.org/TR/WCAG20. Один контрольный пункт в рабочей версии WCAG 2.0 может заключать в се- бе несколько контрольных пунктов версии 1.0. После рецензирования и опубликования WCAG 2.0 Консорциу- мом World Wide Web контрольные пункты версии 1.0 будут заменены контрольными пунктами версии 2.0. Бо-
794 Глава 21 лее того, новую версию станет возможным применять к большему числу языков разметки (например, XML, WML и т. д.) и типов содержимого, чем ее предшественницу. WAI также предоставляет дополнительный список оперативных подсказок, подкрепляющих десять важных пунктов, относящихся к проектированию универсально доступных Web-сайтов. Более подробная информация об оперативных подсказках WAI имеется на Web-сайте www.w3.org/WAI/References/Quicktips. 21.4. Альтернативные способы просмотра изображений в сети Одно из важных требований WAI предписывает наличие сопроводительного текста для каждого графического изображения на Web-странице, поясняющего его предназначение. Для решения этой задачи Web-разработчики могут пользоваться атрибутом alt тегов img и input для включения текстового эквивалента каждого изображе- ния или рисунка на сайте. Web-разработчики, не применяющие атрибут alt для обеспечения текстовых эквивалентов, усугубляют слож- ность доступа к Web-сайтам людей с нарушениями зрения. Специализированные пользовательские агенты (или вспомогательные средства доступа), такие как устройства считывания экрана (программы, обеспечивающие пользователям возможность слышать текст, отображенный на мониторе) и мониторы Брайля (устройства, по- лучающие данные с устройств считывания экрана и преобразующие их в шрифт Брайля), обеспечивают доступ лиц с нарушениями зрения к текстовой информации, отображенной на экране монитора. Пользовательский агент визуально преобразует исходный код Web-страницы, переводя его в формат, доступный для людей с раз- личными ограничениями физических возможностей. Примерами пользовательских агентов являются браузеры Microsoft Internet Explorer и Netscape Communicator, а также устройства считывания экрана, упоминающиеся в данной главе. Подобным же образом доступ людям с нарушениями зрения и слуха к Web-страницам, не имеющим эквивален- тов для аудио- и видеороликов, очень осложнен. Устройства считывания экрана не могут преобразовывать изо- бражения, кинофильмы и большую часть прочего XHTML-содержимого Web-страниц. Однако предоставление различными способами информации, основанной на мультимедиа (например, с помощью атрибута alt или по- следовательного описания изображений), Web-дизайнеры могут максимально повысить доступность содержи- мого своих сайтов. Разработчики должны предоставлять в атрибуте alt выразительные и соответствующие текстовые эквиваленты для их использования невизуальными пользовательскими агентами. Например, если в атрибуте alt описывается диаграмма роста объемов продаж, то в нем должно быть указано краткое резюме данных, вместо их представ- ления в диаграмме. Полное разъяснение данных диаграммы должно быть включено в атрибут longdesc (от англ. long description— подробное описание), предназначенный щя дополнения и расширения описания атрибута alt. Атрибут longdesc содержит ссылку на Web-страницу, описывающую содержимое изображения или муль- тимедиа. В настоящее время мало браузеров, поддерживающих атрибут longdesc. Альтернативой ему является D-ссылка, предоставляющая описательный текст о графах и диаграммах. Более подробная информация о D-ссылках имеется на Web-сайте CORDA Technologies (www.corda.com). Использование устройств считывания экрана для упрощения просмотра Web-сайта может превратиться в до- вольно нудный и "долгоиграющий" процесс, потому что они не интерпретируют графические изображения. Включение ссылки в верхнюю часть каждой Web-страницы, обеспечивающей прямой доступ к содержимому страницы, позволяет людям с физическими недостатками "обходить" длинные списки ссылок и прочего ненуж- ного или недоступного содержимого. Такой "прыжок" экономит много времени и сохраняет нервные клетки людей с нарушениями зрения. Emacspeak (www.cs.cornell.edu/home/raman/emacspeak/emacspeak.html)— это экранный интерфейс, повы- шающий возможности доступа к Интернету людям с нарушениями зрения, путем перевода текста в голосовые данные. В данном открытом продукте также реализованы аудиопиктограммы, проигрывающие различные зву- ки. Программу Emacspeak можно настроить на функционирование в операционных системах на базе Linux, она обеспечивает поддержку речевого механизма ViaVoice от корпорации IBM. В марте 2001 года на сайте WeMedia был представлен еще один пользовательский агент— WeMediaBrowser, обеспечивающий более удобный доступ в Интернет людям с нарушениями зрения и страдающим дислексией (неспособностью к чтению). В WeMediaBrowser возможности традиционных браузеров значительно расширены применением кнопок большого размера и команд, выполняемых одним нажатием клавиши, которые существен- но облегчают процесс просмотра Web-сайтов. Браузер "считывает" выбранный пользователем текст Web- страницы с регулируемыми скоростью и громкостью. Программа WeMediaBrowser бесплатно доступна на сайте www.wemedia.com.
Обеспечение доступности программных приложений 795 IBM Home Page Reader (HPR) — другой браузер с возможностью "считывания" выбранного пользователем тек- ста. В HPR используется технология ViaVoice корпорации IBM для синтеза голоса Пробная версия HPR имеет- ся на сайте www-3.ibm.com/able/hpr.htinl. 21.5. Повышение удобочитаемости совершенствованием структуры Во многих Web-сайтах теги XHTML используются только для эстетических целей, без функций этих тегов. На- пример, тег заголовка <hl> часто ошибочно применяется для увеличения размера шрифта и выделения текста полужирным стилем, вместо указания заголовка основного раздела содержимого. Подобная практика может создать желаемый визуальный эффект, но для устройств считывания экрана она создает проблемы. При нахож- дении тега <hl> устройство считывания может сообщить пользователю словами о том, что найден новый раздел. Если на самом деле это не так, то тег <ы> просто будет сбивать пользователей с толку. Поэтому разработчикам следует использовать тег <ы> только в соответствии с его спецификациями в XHTML (например, для разметки заголовка, обозначающего важный раздел документа). Вместо использования тега <hl> для увеличения размера текста и выделения его полужирным стилем для форматирования разработчики могут пользоваться каскадными таблицами стилей (Cascading Style Sheets, CSS) или XSL (Extensible Stylesheet Language, расширяемый язык стилей). Другие примеры имеются на Web-сайте WCAG 1.0 www.w3.org/TR/WCAG10. Примечание________________________________________________ Для выделения текста полужирным стилем также можно использовать тег <strong>, однако устройства считы- вания акцентируют полужирный текст что влияет на интонацию Еще одним аспектом доступности является удобочитаемость. При создании Web-страницы, предназначенной для общей аудитории, важно учитывать уровень текста (т. е. уровень сложности прочтения и понимания), кото- рым изложено содержимое страницы. Web-разработчики могут делать свои сайты более простыми для чтения путем использования коротких фраз. Более того, разговорная лексика и прочие нетрадиционные языковые осо- бенности могут стать проблемными местами для пользователей-иностранцев, поэтому разработчикам следует ограничивать использование таких слов. Для передачи общего смысла абзаца текста в WCAG 1.0 предполагается использовать его первое предложение Когда смысл абзаца текста на Web-сайте излагается в первом предложении абзаца, то лицам с ограниченными физическими возможностями проще выделить для себя важную информацию, не учитывая второстепенную. Gunning Fog Index — формула, определяющая степень удобочитаемости при применении к образцу текста, — может оценить ее для Web-сайта. Более подробная информация о программе Gunning Fog Index размещена на сайте www.trainingpost.org/3-2-inst.htm. 21.6. Доступность в Visual Studio .NET В предыдущих разделах кратко описаны различные руководства по обеспечению доступности, представленные в инициативе доступности Web, разработанной W3C. В Visual Studio .NET имеются свои руководства по проек- тированию доступных программных средств в рамках данной программной среды. Например, в одном руко- водстве рекомендуется резервировать цвета для расширения или акцентирования информации, а не просто "для красоты". В другом руководстве советуется представлять информацию средствам доступности (специализиро- ванным программным средствам, делающим приложения доступными для лиц с физическими недостатками) об объектах (например, о пиктограммах рабочего стола и открытых окнах). Такая информация может содержать название, местоположение и размер окна. Третье руководство предписывает разрабатывать пользовательские интерфейсы так, чтобы их можно было настраивать в соответствии с предпочтениями пользователей. Например, люди с нарушениями зрения должны иметь возможность изменения размера шрифтов пользовательского ин- терфейса. Четвертое руководство рекомендует предоставление пользователям возможности настройки времени для программных приложений, в которых предусмотрены ограничения по времени. Например, пользователи, ограниченные в движении или с дефектами речи, могут испытывать трудности при работе с программами, тре- бующими ввода данных в рамках предварительно заданного отрезка времени (например, 10 секунд) Однако если в таких приложениях пользователь может задать отрезок времени, то ему будет проще настроить програм- му под свои возможности. Помимо руководств по разработке доступных программных приложений. Visual Studio .NET также предлагает функции, позволяющие людям с физическими недостатками самостоятельно использовать среду разработки. Например, пользователи могут увеличивать размер пиктограмм и шрифта текста, настраивать панели инстру- ментов, раскладку клавиатуры и перегруппировывать окна. Данные возможности описаны в следующих раз- делах.
796 Глава 21 21.6.1. Увеличение размеров значков на панелях инструментов Для увеличения размера значка в Visual Studio выберите в меню Tools команду Customize. На вкладке Options диалогового окна Customize отметьте флажок Large icons— большие значки (рис. 21.2) и нажмите кнопку Close. На рис. 21.3 показаны увеличенные пиктограммы окна разработки Visual Studio. I coramems «дооге | лз* jtz , Peer Other ji' jP '*ort ," .F? ShOwScreenJIptw.toflbar» : “ Г Showd)ortajtkaysl-5oeerirpi ik1JJ ariftattohfahsysteni default) y| Keyboard... Efee - Рис. 21.2. Увеличение размера значков с помощью окна Customize Рис. 21.3. Увеличенные пиктограммы в окне разработки 21.6.2. Увеличение размера шрифта текста В Visual Studio при отображении текста используются настройки гарнитур шрифтов по умолчанию, выполнен- ные операционной системой. Однако некоторые пользователи не могут ознакомиться с этими настройками, что делает программу для них недоступной. Для решения этой проблемы Visual Studio дает пользователям возмож- ность изменения размера гарнитуры шрифта. Выберите в меню Tools команду Options. В окне Options открой- те папку Environment и выберите команду Fonts and Colors. В раскрывающемся списке Show settings for вы- берите Text Editor. В раскрывающемся списке Font... выберите нужную гарнитуру, а в раскрывающемся списке Size— нужный ее размер. На рис. 21.4 показан редактор Text Editor до изменения размера гарнитуры, на рис. 21.5 — окно Options с новой настройкой размера, а на рис. 21.6 — Text Editor после применения внесен- ных изменений. Рис. 21.4. Редактор Text Editor до изменения размера гарнитуры шрифта
Обеспечение доступности программных приложений 797 Рис. 21.5. Настройка размера гарнитуры текста в окне Options Рис. 21.6. Редактор Text Editor после изменения размера гарнитуры шрифта 21.6.3. Модификация панелей инструментов Панель инструментов Toolbox Visual Studio содержит многочисленные элементы проектирования, упрощаю- щие создание Web-приложений; однако разработчики могут использовать только некоторые из них. Для Рис. 21.7. Добавление разделов в панель инструментов Toolbox
798 Гпава 21 удовлетворения потребностей отдельных разработчиков Visual Studio дает программистам возможность на- стройки панели инструментов созданием новых закладок с последующей вставкой в них элементов проектиро- вания. При этом для пользователей с физическими недостатками устраняется необходимость прохождения мно- гочисленных закладок или просмотра длинных списков в поисках нужных элементов проектирования. Для соз- дания нового раздела щелкните правой кнопкой мыши на любой существующей вкладке и выберите в контекстном меню команду Add Tab. В текстовом окне введите идентификатор раздела (например, FrequentlyUsed (Часто используется)) и нажмите клавишу <Enter>. По умолчанию во все разделы помещается элемент Pointer — указатель (рис. 21.7). Этот элемент обеспечивает нормальную работу курсора. Для вставки элементов во вновь созданный раздел выберите в меню Tools команду Customize Toolbox. На вкладке .NET Framework Components выберите элементы для ввода в новый раздел и нажмите кнопку ОК. Выбранные элементы будут помещены на новой вкладке. 21.6.4. Модификация клавиатуры Другая функция доступности в Visual Studio .NET позволяет людям с ограниченными физическими возможно- стями настраивать раскладки клавиатуры созданием "горячих” клавиш (т. е. комбинаций клавиш, при нажатии которых выполняются часто используемые операции; например, при нажатии комбинации клавиш <Ctrl>+<V> из буфера вставляется текст). Для создания "горячей" клавиши начните с выбора в меню Tools команды Options. В окне Options выберите в каталоге Environment элемент Keyboard В раскрывающемся списке Keyboard mapping scheme выберите набор, в котором будет сохранена новая комбинация клавиш, и нажмите кнопку Save As. Затем присвойте комбинации имя в диалоговом окне Save Scheme и нажмите кнопку ОК. В текстовом поле Show commands containing введите операцию "горячей" клавиши. Например, при создании "горячей" клавиши для функции вставки в данное текстовое поле должно быть введено слово Paste, либо нуж- ная функция выбирается из списка, расположенного непосредственно под текстовым полем. После этого в рас- крывающемся списке Use new shortcut in выберите программные приложения, в которых будет использоваться данная комбинация клавиш. Если ее планируется использовать во всех приложениях, выберите строку Global. Наконец, в текстовом поле Press shortcut key(s) присвойте "горячую" клавишу операции в форме Нетексто- вая клавиша>+<текстовая клавиша>. В число нетекстовых клавиш входят <Ctrl>, <Shift> и <Alt>; текстовые клавиши — от А до Z включительно. Примечание_________________________________________________________________________ Для ввода нетекстовой клавиши просто нажмите ее не следует вводить слова Ctrl, Shift или Alt. В качестве час- ти "горячей" клавиши можно ввести более одной нетекстовой клавиши. Не следует вводить символ "+". Таким образом, действующей комбинацией клавиш может быть <Ctrl>+<Alt>+<D>. После присвоения комби- нации клавиш нажмите сначала кнопку назначения Assign, а потом кнопку ОК. На рис. 21.8 показан процесс создания комбинации клавиш для функции NewBreakpoint. Комбинация клавиш <Ctrl>+<Alt>+<D> действи- тельна только для текстового редактора Text Editor. Выбор операции Приложение, в котором работают "горячие" клавиши Определение клавиш Рис. 21.8. Создание "горячей" клавиши
Обеспечение доступности программных приложений 799 21.6.5. Перегруппировка окон Некоторые устройства считывания с трудом интерпретируют пользовательские интерфейсы, имеющие большое количество вкладок. Это происходит потому, что большинство устройств считывает информацию только из од- ного окна. Для успешного использования таких устройств Visual Studio позволяет разработчикам настраивать пользовательские интерфейсы так, что видимыми будут только консольные окна. Для удаления вкладок выбе- рите в меню Tools команду Options. Затем в окне Options выберите в каталоге Environment элемент General. В разделе Settings отметьте переключатель MDI environment и нажмите кнопку ОК. На рис. 21.9 показано ок- но Options, а на рис. 2110 — консольное окно с вкладками и без вкладок. Рис. 21.9. Удаление вкладок из среды Visual Studio 1 Console Application! Microsoft Visual F (dewQu) - Clavst л * Вкладка •MainfrtringO arjs) J ConsoieApeacacionl. Classi class Cl Start Page Оам1ж«'|--- Ji .Cisz4 ‘Debug ’ К? " ____________________________ _______ Ox loofe Window tWu 9,-i Debug "'tW TooteV<SAA\accbrow.«pp * ; *3 ftrogr*» [Mi6]ConsoteAppfcab< » Throed [izoo]<NoMi.>e>T Cons ole Application! Microsoft Visual С#ЛЕГ [ break j - using Systems □ namespace ConsoleApplicationl Без вкладок жеа using System; class Classi i/j <sunaiery> !i ' Summary description for Classi. Рис. 21.10. Консольные окна с вкладками и без вкладок 21.7. Доступность в C# Visual Studio .NET предоставляет расширенные функции доступности и руководства по созданию доступных программных приложений в собственной среде разработки. Подобные же рекомендации регулируют разработку приложений С#, доступных для людей с ограниченными физическими возможностями. Важно, чтобы програм- мы C# были удобны для использования как можно большим количеством потенциальных пользователей, а не только "среднестатистическими". После определенных изменений большинство программ можно сделать дос- тупными для широкого круга пользователей.
800 Гпава 21 Общие правила при создании доступных приложений следующие: □ использование гарнитур шрифтов больших размеров: люди с нарушениями зрения могут видеть текст; □ разработка гибких приложений, обеспечивающих создание комбинаций клавиш для всех функций в рамках приложения: люди смогут работать с программой без помощи мыши; П донесение информации до пользователя в видео- и аудиоформатах; х □ использование при необходимости графики и изображений: наглядные средства повысят доступность ин- формации для людей,-испытывающих трудности с чтением текста с экрана монитора. П сигналы об информации должны подаваться не только звуками: некоторые пользователи не имеют громко- говорителей, либо страдают нарушениями слуха1; □ убедитесь в возможности работы с программой без мыши или клавиатуры; доступ к функциям программы не должен быть ограничен только одним устройством ввода. Более подробную информацию об этих и других руководствах по проектированию универсально доступных приложений см. в документации Visual Studio .NET в подразделе overview индексного указателя accessibility. В данном разделе представлены ссылки на форумы обсуждения вопросов проектирования доступных приложе- ний Windows и ASP.NET. Одним из специфических способов повышения доступности приложений является использование в них элемен- та управления речевого воспроизведения текста. С его помощью текст можно преобразовывать в речь: синте- зированный "компьютерный" голос произносит слова, введенные в элемент управления как текст. Такие уст- ройства облегчают доступ к информации людей, не видящих экрана монитора. Другой способ повышения доступности — использование ТаЬ-остановок. Tab-остановка имеет место при нажа- тии пользователем клавиши <ТаЬ>; при этом курсор переносится на другой элемент управления. Порядок выде- ления элементов управления называется порядком табуляции, определяемый значением Tabindex элементов управления (элементы управления выделяются в восходящем порядке). Каждый элемент управления также об- ладает свойством Tabstop; если оно имеет значение true, тогда данный элемент управления включен в порядок табуляции; в противном случае — нет. Использование свойств Tabindex и Tabstop значительно облегчает соз- дание доступных программных приложений. При некорректной установке этих свойств логическая упорядочен- ность приложения поддерживаться не будет. Третьим и важным способом повышения программистами доступности своих приложений является использо- вание специальных классов, предоставляемыми .NET. Например, класс Control обладает многими свойствами, предназначенными для донесения?информации до пользователей. После этого такие приложения, в свою оче- редь, могут находить нужную информацию, сохраненную в виде свойств. В табл. 21.2 перечислены некоторые свойства класса Control, предназначенные для передачи информации пользователям. Таблица 21.2. Свойства класса Control, относящиеся к доступности Свойство Назначение AccessibleDescription Описывает элемент управления к доступности клиентского приложения. Например, по- зиция списка CheckBox, обозначающая "New User", больше не предполагает никакого описания, но позиция списка с изображением кота потребует задания свойству AccessibleDescription описания "A CheckBox with an image of a cat on it" AccesibleName Сокращенное название или идентификатор элемента управления AccesibleRole Элемент перечисления AccessibleRole. Обозначает роль элемента управления в при- ложении: данная информация помогает клиентскому приложению определить операции к выполнению IsAccessible Содержит значение bool, указывающее на видимость элемента управления в клиент- ском приложении Рассмотрим приложение, имеющее текстовые поля TextBox, в которые пользователь вводит имя, фамилию и адрес. Логический порядок табуляции будет начинаться с текстового поля для ввода имени, переходить к полям ввода фамилии и адреса. В программе, представленной в листинге 21.1, показан элемент речевого воспроизведения текста, ТаЬ-оста- новки и свойства класса Control, имеющие отношение к доступности. Программа состоит из формы с тремя 1 См. "Basic Principles of Accessible Design". .NET Framework Developer's Guide, Visual Studio .NET Online Help.
Обеспечение доступности программных приложений 801 метками Label, тремя текстовыми полями TextBox и кнопкой Button, обеспечивающими предоставление поль- зователю информации. Передача информации прекращает исполнение приложения: оно предназначено только для демонстрации работы элемента речевого воспроизведения текста. Функции доступности в данной программе работают следующим образом: когда курсор мыши подводится к метке Label, элемент речевого воспроизведения подсказывает пользователю необходимость ввода нужной ин- формации в текстовое поле TextBox, расположенное справа от метки Label. Если курсор мыши помещен на тек- стовое поле TextBox, то его содержимое озвучивается голосом. Наконец, если курсор мыши расположен на кнопке Submit, то пользователю сообщается о необходимости нажатия этой кнопки для передачи информации. Порядок табуляции следующий: сначала представлены текстовые окна, в которые пользователь вводит свое имя, фамилию и адрес, затем кнопка Button. Метки Label и элемент речевого воспроизведения не включены в порядок табуляции, потому что пользователь не может с ними взаимодействовать, и, таким образом, их вклю- чение в порядок табуляции не будет иметь смысла. Свойства доступности заданы так, что доступные для клиен- тов приложения будут получать нужную информацию об элементах управления. Обратите внимание, что в лис- тинг 21.1 включен только код, сгенерированный Visual Studio .NET. Для использования элемента речевого вос- произведения введите его в панель инструментов Toolbox. Это делается выбором в меню Tools команды Customize Toolbox. Откроется окно CustomizeToolbox: отметьте позицию списка рядом с опцией TextToSpeech Class. Нажмите кнопку ОК для закрытия диалогового окна. Теперь элемент VText размещен в панели инструментов ToolBox, и его можно перетянуть на форму так же, как и любой другой элемент. Программа имеет три метки Label, указывающие, что в соседние поля надо вводить имя пользователя, номер телефона и пароль. В три соответствующих текстовых поля вводится пользовательская информация, а кнопка Button дает пользователю возможности передачи формы. В строке 25 объявляется элемент речевого воспроиз- ведения текста с именем speaker. Необходимо, чтобы при помещении курсора мыши на элементы управления пользователь слышал речевые описания их предназначений. В строках 112—119 определяется обработчик со- бытия controls_MouseHover; этот метод прикрепляется к трем текстовым полям и кнопке как обработчик собы- тия MouseHover. 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 // Листинг 21.1: TextToSpeech.cs // Речевое воспроизведение текста для людей с нарушениями слуха using System; using System.Drawing; using System.Collections; using System.ComponentModel; using System.Windows.Forms; using System.Data; // форма co звуковым воспроизведением текста public class TextToSpeech : System.Windows.Forms.Form { private System.Windows.Forms.Label nameLabel; private System.Windows.Forms.Label phoneLabel; private System.Windows.Forms.TextBox nameTextBox; private System.Windows.Forms.TextBox phoneTextBox; private System.Windows.Forms.TextBox passwordTextBox; private System.Windows.Forms.Button submitButton; private System.Windows.Forms.Label passwordLabel; 25 private AxHTTSLib.AxTextToSpeech speaker; 26 27 private System.ComponentModel.Container components = null; 28 29 // конструктор по умолчанию 30 public TextToSpeech() 31 { 32 InitializeComponent(); 33 51 Зак. 3333
802 Гпава 21 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 // задание видимой формы для приложений доступности this.IsAccessible = true; // все элементы — видимые для приложения доступности foreach (Control current in this.Controls) current.IsAccessible = true; } private void InitializeComponent() { this.nameLabel.AccessibleDescription = "User Name"; this.nameLabel.AccessibleName » "User Name"; this.nameLabel.Tabindex = 5; this.nameLabel.MouseHoyer += new System.EventHandler(this.controls_MouseHover); this.phoneLabel.AccessibleDescription = "Phone Number Label"; this.phoneLabel.AccessibleName = "Phone Number Label"; this.phoneLabel.Tabindex = 6; this.phoneLabel.MouseHover += new System.EventHandler(this.controls_MouseHover); this.nameTextBox.AccessibleDescription "Enter User Name"; this.nameTextBox.AccessibleName = "User Name TextBox"; this.nameTextBox.Tabindex =1; 'this. nameTextBox. MouseHover += new System.EventHandler(this.controls_MouseHover); this.phoneTextBox.AccessibleDescription = "Enter Phone Number"; this.phoneTextBox.AccessibleName = "Phone Number TextBox"; this.phoneTextBox.Tabindex = 2; this.phoneTextBox.MouseHover += new System.EventHandler(this.controls_MouseHover); this.passwordTextBox.AccessibleDescription = "Enter Password"; this.passwordTextBox.AccessibleName = "Password TextBox"; this.passwordTextBox.Tabindex « 3; this.passwordTextBox.MouseHover += new System.EventHandler(this.controls_MouseHover); this.submitButton.AccessibleDescription = "Submit the Information"; this.submitButton.AccessibleName = "Submit Information"; this.submitButton.Tabindex « 4; this.submitButton.Text = "&Submit"; this.submitButton T Click += new System.EventHandler(this.submitButton_Click); this.submitButton.MouseHover += new System.EventHandler(this.controls_MouseHover); this.passwordLabel.AccessibleDescription = "Password Label"; this.passwordLabel.AccessibleName = "Password Label"; this.passwordLabel.Tabindex = 7; thi s.passwordLabel.MouseHover += new System.EventHandler(this.controlsJMouseHover); this.speaker.AccessibleDescription = "Give Information about Form";
Обеспечение доступности программных приложений 803 97 this.speaker.AccessibleName = "Speaker"; ч 98 this.speaker.Tabindex = 8; 99 this.speaker.TabStop = false; 100 101 this.AccessibleDescription = "Registration Form"; 102 this.AccessibleName = "Registration Form"; 103 ) 104 105 [STAThread] 106 static void Main() 107 { 108 Application.Run(new TextToSpeech()); 109 } 110 111 // сообщение пользователю, на каком элементе курсор мыши 112 private void controls_MouseHover ( < 113 object sender, System.EventArgs e) 114 { 115 // если курсор мыши на Label, сообщение о вводе информации 116 if (sender. Get Туре () == nameLabel.GetType()) 117 { 118 Label temporary = (Label) sender; 119 speaker.Speak("Please enter your " + temporary.Text + 120 " in the textbox to the right") 121 } 122 123 // если курсор мыши на TextBox, сообщение введенной информации 125 else if (sender.GetType() = nametextBox.GetType()) 126 { 127 TextBox temporary = (TextBox) sender; 128 speaker.Speak("You have entered " + 129 (temporary.Text == ? "nothing" : 130 temporary.Text) + " in the " + temporary.Name); 131 } 132 133 // в противном случае, курсор на кнопке Button; сообщить 134 // пользователю о нажатии кнопки для передачи информации 135 else 136 speak.Speak( 137 "Click on this button to submit your information") 138 139 } // окончание метода controls_MouseHover 140 141 // благодарность пользователю за предоставление информации 142 private void submitButton_Click( 143 object sender, System.EventArgs e) 144 ( 145 speaker.Speak( 146 "Thank you, your information has been submitted."); , 147 148 App Lication.Exit(); 149 , } 150 151 } // окончание класса TextToSpeech Метод controls_MouseHover определяет тип элемента управления, на который наведен курсор мыши, и генери- рует соответствующее голосовое сообщение. В строке 116 определяется, является ли тип элемента управления, вызывающего метод, тем же, что и тип объекта nameLabel. Здесь используется метод GetType класса Туре, воз- вращающий экземпляр класса туре; данный класс предоставляет информацию о том или ином классе. Метод GetType вызывается на объект sender. Аргумент sender обработчика событий является ссылкой на элемент, ак- тивизировавший событие. Когда условие в строке 116 принимает значение true (т. е. элемент, активизировав- ший событие,— nameLabel), тогда выполняются строки 118—120. В строке 118 объект sender приводится к Label (теперь известно, что это единица) и присваивает sender объекту Label temporary. В строках 119 и 120
804 Глава 21 вызывается метод Speak объекта speaker, предоставляющий строку string, которая должна быть преобразована в речь. Подобный же процесс исполняется при определении, находится ли курсор мыши на объекте TextBox (стро- ка 125), и для генерирования соответствующего голосового сообщения (строки 127—130). Наконец, если эле- мент управления, на который наведен курсор мыши, не является ни объектом Label, ни TextBox, то, значит, это кнопка (Button); в строках 136—137 пользователь получает сообщение о необходимости нажатия кнопки для передачи информации. Метод submitButton_ciick (строки 142—149) выполняется при нажатии пользователем кнопки Button. Этот обработчик события вызывает метод Speak объекта speaker, передавая в качестве аргумен- та сообщение благодарности за предоставленную информацию, после чего выходит из приложения. В строке 82 свойство Text объекта submitButton устанавливается на "&Submit". Это пример обеспечения досту- па к функциональности приложения с клавиатуры. Вспомните, что в главе 10 "горячие” клавиши определялись вводом символа & перед буквой, которая станет "горячей" клавишей. В данном случае то же самое делается для объекта submitButton: нажатие комбинации клавиш <Alt>+<S> эквивалентно нажатию кнопки submitButton. Порядок табуляции в данном приложении устанавливается заданием свойств Tabindex и Tabstop. Свойства Tabindex элементов управления присваиваются в строках 46, 60, 67, 74, 81, 91 и 98. Текстовым полям (TextBox) присваиваются индексы табуляции 1—3 в порядке их появления (вертикально) на форме. Объекту Button при- сваивается индекс табуляции 4, а остальным элементам присвоены индексы 5—8. Необходимо, чтобы в порядок табуляции входили только текстовые поля и кнопка Button. Установка по умолчанию свойства Tabstop класса Label — false, поэтому менять ее не нужно: метки не включаются в порядок табуляции. Значение свойства Tabstop классов TextBox и Button — true; это означает, что для этих элементов значения менять также не нуж- но. Однако свойство Tabstop объекта speaker по умолчанию имеет значение true. Значение этого свойства ме- няется на false, указывая, что объект speaker не должен включаться в порядок табуляции. Вообще говоря, все свойства Tabstop элементов управления, с которыми пользователь не может взаимодействовать напрямую, должны иметь значение false. Последняя функция доступности данного программного приложения задействует установку свойств элементов управления так, чтобы клиентские приложения могли бы иметь к ним доступ для надлежащей работы. В строках 44, 50—51, 57—58, 64—65, 71—72, 78—79, 88—89 и 95—96 задаются свойства AccessibleDescription всех элементов управления (включая форму). В строках 45, 52, 59, 66, 73, 80, 90х и 97 задаются свойства AccessibleName всех элементов управления (также, включая форму). Во время проектирования свойство IsAccessible невидимо в окне Properties, поэтому для задания ему значения true необходимо написать код. В строке 35 значение свойства IsAccessible класса TextToSpeech устанавливается на true. В строках 38 и 39 по каждому элементу управления на форме проходит цикл, устанавливая значение свойства IsAccessible на true. Теперь форма и все ее элементы управления будут видимы для клиентских программных приложений. Окно приложения представлено на рис. 21.11. You have entered 5-5-5-1-2-3-4 in the phone-number Textbox. Рис. 21.11. Приложение, реализующее функции речевого доступа 21.8. Доступность в таблицах XHTML Сложные Web-страницы часто содержат таблицы, форматирующие содержимое и представляющие данные. Од- нако многие устройства считывания не способны корректно преобразовывать таблицы, если разработчики не проектируют их в соответствии с требованиями этих устройств. Например, CAST eReader — механизм считыва- ния, созданный Center for Applied Special Technology (CAST, Центром прикладных технологий; www.cast.org), запускается с левой верхней ячейки и осуществляет считывание столбцов таблицы слева направо и сверху вниз. Такая методика считывания данных таблицы называется линеаризованной. В листинге 21.2 создается простая таблица со списком цен на разные фрукты; далее мы намерены ввести эту таблицу в CAST eReader для демон- страции линейного считывания таблицы программой. CAST eReader считывает таблицу в листинге 21.2 сле- дующим образом: Price of Fruit Fruit Price Apple $0.25 Orange $0.50 Banana $1.0 Pineapple $2.0
Обеспечение доступности программных приложений 805 Считывание подобного рода не представляет содержимого таблицы адекватно: при считывании не определяют- ся ни заголовки информации, ни ссылки на данные, содержащиеся в ячейках под заголовками столбцов, их опи- сывающих. WCAG 1.0 рекомендует пользоваться CSS вместо таблиц, если содержимое таблицы не линеаризо- вано в доступный и понятный вид. 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 <?xml version = "1.0"?> <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" ,"ht tp: / /www. w3. org/TR/xhtml 1 / DTD/xhtml1-strict. dtd" > <!— Листинг 21.2: withoutheaders.html —> <!— Таблица без заголовков —> <html xmlns = "http://www.w3.org/1999.xhtml> <head> <title>XHTML Table Without Headers</title> <style type = "text/css"> body { background-color: #ccffaa; text-align: center } </style> </head> <body> <p>Price of Fruit</p> <table border = "1" width = "50%"> <tr> <td>Fruit</td> <td>Price</td> </tr> <tr> <td>Apple</td> <td>$0.25</td> </tr> <tr> <td>Orange</td> <td>$0.50</td> </tr> <tr> <td>Banana</td> <td>$1.00</td> </tr> <tr> <td>Pineapple</td> <td>$2.00</td> </tr> </table> </body> </html> Рис. 21.12. XHTML-таблица, не поддерживающая модификацию для повышения доступности Результат работы программы представлен на рис. 21.12.
806 Глава 21 Если бы таблица в листинге 21.2 была большой, тогда ее линеаризованное считывание механизмом было бы еще более непонятным для пользователей. Однако при модификации тега <td> атрибутом headers, а также при изменении ячеек заголовка (ячеек, отмеченных тегом <th>) атрибутом id таблица считывается так, как предпо- лагалось. В листинге 21.3 демонстрируется, как подобные модификации изменяют способ интерпретации таб- лицы механизмом считывания. «... . Г.. . .М.М- . . .М .. Листинг 21.3. Таблица, оптимизированная с помощью атрибута headers для считывающего механизма . S». Л ИП Т1. И < «HOW 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 55 46 47 48 49 50 51 52 53 54 <?xml version = "1.0"?> <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtmll/DTD/xhtmll-strict.dtd”> <!— Листинг 21.3: withheaders.html —> <!— Таблица с заголовками —> <html xmlns = "http://www.w3.org/1999.xhtml> <head> <title>XHTML Table With Headers</title> <style type = "text/css"> body { background-color: #ccffaa; text-align: center } </style> ' </head> <body> <i— в данной таблице использованы атрибуты id и headers для —> <!— обеспечения удобочитаемости браузерами на основе текста.—> <!— Здесь также применен атрибут summary, используемый —> <!— механизмами считывания для описания таблицы. —> <table width = "50%" border = "1" summary = "This table uses th elements and is and headers attributes to make the table readable by screen readers"> <capt ion><string>Price of Fruit</strongx/caption> <tr> <th id = "fruit">Fruit</th> <th id = "price">Price</th> </tr> <tr> <td headers = "fruit">Apple</th> <td headers = "price">$0.25</th> </tr> <tr> <td headers = "fruit">Orange</th> <td headers = "price">$0.50</th> </tr> <tr> <td headers = "fruit">Banana</th> <td headers = "price">$1.00</th> </tr> <tr> <td headers = "fruit">Pineapple</th> <td headers = "price">$2.00</th> 55 </tr>
Обеспечение доступности программных приложений 807 56 57 </table> 58 59 </body> 60 </html> Результат работы программы представлен на рис. 21.13. Рис. 21.13. XHTML-таблица, поддерживающая модификацию для повышения доступности Данная таблица не слишком отличается от стандартной таблицы XHTML, представленной в листинге 21.2. Од- нако ее форматирование позволяет механизму считывать данные в более понятной форме. Механизм считыва- ния озвучивает данные таблицы в листинге 21.3 следующим образом: Caption: Price of Fruit Summary: This table uses th elements and id and headers Attributes to make the table readable by screen readers Fruit: Apple, Price: $0.25 Fruit: Orange, Price: $0.50 Fruit: Banana, Price: $1.00 Fruit: Pineapple, Price: $2.00 При считывании каждой ячейке таблицы предшествует соответствующий ей заголовок. Такой формат облегчает понимание пользователем данных таблицы. Атрибут headers специально предназначен для использования в таблицах, содержащих большие объемы данных. Большинство небольших таблиц линеаризуются достаточно легко, пока корректно используется тег <th>. Пущей ясности ради, мы рекомендуем использовать атрибут summary и элемент caption. Дополнительные примеры повышения доступности табличных данных представле- ны на сайте www.w3.org/TR/WCAG. 21.9. Доступность во фреймах XHTML Для отображения в одном окне браузера нескольких файлов XHTML Web-дизайнеры часто используют фрей- мы. Фреймы предоставляют удобный способ отображения на экране определенного содержимого страницы. К сожалению, фреймам часто недостает надлежащих описаний, и пользователи текстовых браузеров и речевых синтезаторов не могут получить доступ к определенным Web-сайтам. Сайт, в котором применяются фреймы, должен предоставлять полноценное описание каждого из них в теге <title> фрейма. В число хороших названий входят "Фрейм просмотра” и "Фрейм основной информации”. Пользователи текстовых браузеров (например, Lynx) должны выбрать фрейм для открытия; описательные на- звания упрощают выбор. Однако присвоение фреймам названий не решает всех ’’навигационных*1 проблем, свя- занных с фреймами. Web-дизайнеры должны использовать тег <nofrantes>, предоставляющий альтернативное содержимое в браузеры, не поддерживающие фреймы. Замечание о “впечатлениях и ощущениях"____________________________________________ Фреймам всегда рекомендуется присваивать названия для обеспечения альтернативы пользовательским аген- там, не поддерживающим фреймы.
808 Гпава 21 Замечание о “впечатлениях и ощущениях"___________________________________________________ Название содержимого каждого фрейма следует вводить с элементом frame; по возможности, рекомендуется вводить ссылки на отдельные страницы в рамках набора фреймов с тем, чтобы пользователи могли просматри- вать Web-страницы. Для предоставления альтернативного содержимого в браузеры, не поддерживающие фреймы, рекомендуется пользоваться тегом <noframes>. Это повысит доступность информации для браузеров с ограниченной поддержкой фреймов. В качестве альтернативы фреймам WCAG 1.0 рекомендует пользоваться каскадными таблицами стилей (CSS), потому что последние обеспечивают схожую функциональность и легко настраиваются под пользовательские требования. К сожалению, возможность отображения нескольких документов XHTML в одном окне браузера требует полной поддержки HTML 4 — не очень широко распространенного формата. Однако второе поколение CSS отображает один документ так, будто он представляет собой несколько документов. На сегодняшний день далеко не все пользовательские агенты в полной мере поддерживают CSS. 21.10. Доступность в XML XML предоставляет разработчикам свободу в создании новых языков разметки. Несмотря на многочисленные преимущества данной функции, новые языки могут не иметь функций универсальной доступности. Для предот- вращения активного распространения недоступных языков WAI разрабатывает правила— Руководства XML (XML Guidelines, XML GL) для упрощения процессов создания полностью доступных документов XML. В Ру- ководствах XML рекомендуется включение текстового описания (подобного тегу <alt> документов XHTML) для каждого нетекстового объекта Web-страницы. Для дальнейшего повышения доступности типы элементов должны обеспечивать группирование и классификацию, а также идентифицировать важную информацию. При отсутствии доступного пользовательского интерфейса все прочие попытки реализации функций доступности не очень эффективны. Поэтому весьма важное значение имеет создание таблиц стилей, способных обеспечить множественные выходные данные, включая общие структуры документов. * Во многих языках XML, включая Synchronized Multimedia Integration Language (SMIL, синхронизированный язык интеграции мультимедиа) и Scalable Vector Graphics (SVG, расширяемый язык векторной графики), реали- зовано несколько рекомендаций WAI. Руководство до доступности XML имеется на сайте www.w3.org /WAI/PF/xm!gl.htin. 21.11. Использование речевого синтеза и распознавания с помощью пакета VoiceXML Совместными усилиями AT&T, IBM, Lucent и Motorola был разработан словарь, размечающий информацию для использования речевыми синтезаторами— инструментальными средствами, позволяющими компьютерам "разговаривать" с пользователями. Данная технология, названная VoiceXML, обеспечивает невероятные пре- имущества для людей с нарушениями зрения, а также для неграмотных. Программные приложения, применяю- щие технологию VoiceXML, считывают Web-страницы для пользователя, после чего применяют методику рас- познавания речи для понимания произнесенных в микрофон слов. Примером механизма распознавания речи является программа ViaVoice от корпорации IBM (www-4.ibm.com/software/speech). Более подробно материал о распознавании и синтезу речи изложен в главе 13. Интерпретатор VoiceXML и браузер, поддерживающий VoiceXML, обрабатывают данные VoiceXML. В буду- щем Web-браузеры смогут объединять в себе эти преобразователи. VoiceXML является производным ot^XML, поэтому механизм VoiceXML не зависит от системной платформы. При загрузке документа VoiceXML речевой сервер отправляет сообщение в браузер VoiceXML и активизирует вербальную коммуникацию пользователя с компьютером. WebSphere Voice Server SDK 1.5 от корпорации IBM — это интерпретатор, используемый для проверки доку- ментов VoiceXML. Загрузить VoiceServer SDK можно с Web-сайта www.alphaworks.ibm.com/tech/voiceserversdk. Примечание_______________ Для запуска программы VoiceXML, представленной в листинге 21.4, загрузите Java 2 Platform Standard Edition (Java SDK) 1.4 с сайта www.java.sun.com/j2se/1.4. Инструкции по установке VoiceServerSDK и Java SDK разме- щены на сайте Deitel & Associates, Inc. — www.deitel.com. В листингах 21.4 и 21.5 приведены примеры VoiceXML, которые можно включить в программы сайта. Компью- тер проговаривает текст документа пользователю, и текст, встроенный в теги VoiceXML, обеспечивает речевую коммуникацию между пользователем и браузером. Выходные данные, представленные в листинге 21.5, демон-
Обеспечение доступности программных приложений 809 стрируют разговор, который мог бы произойти между пользователем и компьютером после загрузки данного документа. Г""’ ж м « г- -т. - - ------------------- - wr—т—:ГГ--7~г------------------------------------------тага»-—ж—-------------------------------------- 1 Листинг 21.4. Домашняя страница, написанная на языке VoiceXML i ....«««w.w «««<• •» ’ а «мама .и'<й/«ии • . w- ava aviVaWav ^waVtwaw» «4* VWM>*e*i»M»№iWaWI№M1M<i*VMM»MMMVMW№W*^M«i>&«^VMMMi«WMHIMWVaM«*V«i*aVM»mvA 1 <?xml version = "1.0"?> 2 <vxml version = "1.0,'?> 3 4 <!— Листинг 21.4: main.vxml —> 5 <!— Речевая страница —> 6 7 <link next = "#home"> 8 <grammar>home</grammar> 9 </link> 10 11 </link next = "#end"> 12 <gpammar>exit</grammar> 13 </link> 14 15 <var name = "currentOption" expr = ,"home,"/> 16 17 <form> ( 18 <block> 19 <emp>Welcome</emp> to the voice page of Deitel and 20 Associates. To exit any time say exit. 21 To go to the home page any time say home. 22 </block> 23 24 <subdialog src = ”#home,,/> 25 </form> 26 27 <menu id = "home"> 28 <prompt count = "1" timeout = "10s"> 29 You have just entered the Deitel home page. 30 Please make a selection by speaking one of the 31 following options: 32 <break msecs = "1000" /> 33 <enumerate/> 34 </prompt> 35 36 <prompt count - "2"> 37 Please say one of the following. 38 <break msecs = "1000" /> 39 <enumerate/> 40 </prompt> 41 42 cchoice next = "#about">About us</choice> 43 <choice next = "#directions">Driving directions</choice> 44 <choice next = "publications.vxml">Publications</choice> 45 </menu> 46 47 <form id = "about"> 48 <block> 49 About Deitel and Associates, Inc. 50 Deitel and Associates, Inc. is an internationally 51 recognized corporate training and publishing 52 organization, specializing in programming languages, 53 Internet and World Wide Web technology and object 54 technology education. Deitel and Associates, Inc. is a 55 member of the World Wide Web Consortium. The company 56 provides courses on Java, C++, Visual Basic, C, Internet 57 and World Wide Web-programming and Object technology.
810 Глава 21 58 <assign name = "currentOption" expr = about'"/> 59 <goto next = "#repeat"/> 60 x/block> 61 </form> 62 63 </form id = "directions’^ 64 <block> 65 Directions to Deitel and Associates, Inc. 66 We are located on Route 20 in Sudbury, 67 Massachusetts, equidistant from route 68 <sayas class = "digits">128</sayas> and route 69 <sayas class - "digits’’>495</sayas>. 70 <assign name « "currentoption" expr = "'directions'"/> 71 <goto next e "#repeat"/> 72 </block> 73 </form> 74 75 <form id = "repeat"> 76 <field name = "confirm" type = "Boolean"> 77 <prompt> 78 To repeat say yes. To go back to home, say no. 79 > </prompt> 80 81 <filled> 82 <if cond = "confirm — true"> 83 <goto expr = "+ currentoption"/> 84 <else/> 85 <goto next = "#home"/> 86 </if> 87 </filled> 88 89 </field> 90 </form> 91 92 <form id - "end"> 93 <block> 94 Thank you for visiting Deitel and Associates voice page. 95 Have a nice day. 96 <exit/> 97 </block> 98 </form> 99 100 </vxml> ............ • • Листинг 21.5. VoiceXML-страница публикаций Deitel and Associates 101 <?xml version = "1.0"?> 102 <vxml version • "1.0"?> 103 104 <!— Листинг 21.5: publications.vxml —> 105 <!— Речевая страница для разных публикаций —> 106 107 <link next = "main.vxml#ho!me"> 108 <grammar>home</grammar> 109 </link> 110 111 <link next = "main.vxml#end"> 112 <grammar>exit</grammar> 113 </link> 114 115 <link next = "#publication"> 116 <grammar>menu</grammar> 117 </link> 118
Обеспечение доступности программных приложений 811 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 <var name = "currentoption" expr = ’”home,"/> <menu id = "publication’^ <prompt count = "1" timeout = "12s"> Following are some of our publications. For more information visit our web page at www.deitel.com. To repeat the following menu, say menu at any time. Please select by saying one of the following books: <break msecs = "1000" /> <enumerate/> </prompt> <prompt count = "2"> Please select from the following books. \ <break msecs = "1000" /> <enumerate?> </prompt> <choice next = "#java">Java.</choice> <choice next = "#c">C.</choice> <choice next = "#cplus">C plus plus.</choice> </menu> <form id = "java"> <block> Java How to program, third edition. The complete, authoritative introduction to Java. Java is revolutionizing software development with multimedia-intensive, platform independent, object-oriented code for conventional, Internet, Intranet and Extranet-based applets and applications. This Third Edition of the world's most widely used university-level Java textbook carefully explains Java's extraordinary capabilities. <assign name = "currentoption" expr = "'java'"/> <goto next = "#repeat"/> </block> </form> <form id = "c"> <block> C How to Program, third edition. This is the long-awaited, thorough revision to the world's best-selling introductory C book! The book's powerful "teach by example" approach is based on more than 10,000 lines of live code, thoroughly explained and illustrated with screen captures showing detailed output. World-renowned corporate trainers and best-selling authors Harvey and Paul Deitel offer the most comprehensive, practical introduction to C ever published with hundreds of hands-on exercises, more than 250 complete prograibs written and documented for easy learning, and exceptional insight into good programming practices, maximizing performance, avoiding errors, debugging, and testing. New features include thorough introductions to C++, Java, and object-oriented programming that build directlt on the C skills taught in this book; coverage of graphical user iriterface development and C library functions; and many new, substantial hands-on projects. For anyone who wants to learn C, improve their existing C skills, and understand how C serves as the foundation for C++, Java, and object-oriented development.
812 Гпава 21 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 <assign name = "currentOption" expr = ",c"’/> <goto next = "#repeat"/> </block> </form> <form id = "cplus"> <block> The C++ how to program, second edition. With nearly 250,000 sold, Harvey and Paul Deitel's C++ How to Program is the world's best-selling introduction to C++ programming. Now, this classic has been thoroughly updated! The new, full-color Third Edition has been completely revised to reflect the ANSI C++ standard, add powerful new coverage of object analysis and design with UML, and give beginning C++ developers even better live code examples and real-world projects. The Deitel's C++ How to Program is the most comprehensive, practical introduction to C++ ever published with hundreds of hands-on exercises, roughly 250 complete programs written and documented for- easy learning, and exceptional insight into good programming practices, maximizing performance, avoiding errors, debugging, and testing. This new Third Edition covers every key concept and technique ANSI C++ developers need to master: control structures, functions, arrays, pointers and strings, classes and data abstraction, operator overloading, inheritance, virtual functions, polymorphism, I/O, templates, exception handling, file processing, data structures, and more. It also includes a detailed introduction to Standard Template Library containers, container adapters, algorithms, and iterators. <assign name = "currentOption" expr = '"cplus"7> <goto next = "#repeat"/> </block> </form> <form id = "repeat"> <field name = "confirm" type = "boolean"> <prompt> To repeat say yes.Say no, to go back to home. </prompt> <filled> <if cond = "Confirm == true"> <goto expr = + currentoption"/> <else/> <goto next = "#publication"/> </if> </filled> </field> </form> </vxml> Результат работы программы таков. Компьютер: Welcome to the voice page of Deitel and Associates. To exit any time say exit. To go to the home page any time say home. Пользователь: Ноте
Обеспечение доступности программных приложений 813 Компьютер: You have just entered the Deitel home page. Please make a selection by speaking one of the following options: About us, Driving directions, Publications. Пользователь: Driving directions Компьютер: Directions to Deitel and Associates, Inc. We are located on Route 20 in Sudbury, Massachusetts, equidistant ’from route 128 and route 495. To repeat say yes. To go back to home, say no. Документ VoiceXML содержит серию диалогов и поддиалогов, образующих подобие разговора между пользо- вателем и компьютером. Диалоги реализуются тегами <form> и <menu>. Элемент form передает информацию пользователю и получает от него данные. Элемент menu предоставляет пользователю список опций с последую- щей передачей управления другому диалогу, в ответ на выбор пользователя. В строках 7—9 (листинг 21.4) используется элемент link для создания активной ссылки на домашнюю страни- цу. Атрибутом next задается URL, на который направляется браузер при выборе пользователем данной ссылки. Элемент grammar размечает текст, который пользователь должен произнести для выбора ссылки. В элементе link осуществляется переход к элементу, содержащему элемент id home при произнесении пользователем слова "home". В строках 11—13 элемент link используется для создания ссылки на id end при произнесении пользо- вателем слова "exit". В строках 17—25 с помощью элемента form создается диалог, в котором собирается пользовательская инфор- мация. В строках 18-—22 представлен текст введения. Элемент block, существующий только в рамках элемента form, группирует элементы, выполняющие операцию или событие. Элемент ешр указывает, что конкретная часть текста должна выделяться интонацией. Если уровень эмфазы (фразового выделения) не задан, тогда использует- ся уровень по умолчанию — ровный (текст произносится обычным тоном). В приведенных примерах по умол- чанию используется ровный уровень. Примечание______________________________________________________________________________________ Для задания уровня фразового выделения используется атрибут level, принимающий следующие значения: strong (сильное выделение), moderate (обычный тон), попе (отсутствие выделения), reduced (понижен- ный тон). Элемент menu в строке 27 дает пользователю возможность выбора страницы, на которую он хочет выйти. Эле- мент choice, всегда являющийся частью либо элемента menu, либо form, представляет имеющиеся опции. Атри- бут next указывает страницу, которая будет загружаться после того, как пользователь сделает выбор. Пользова- тель выбирает элемент choice произнесением в микрофон размещенного между тегами текста. В данном при- мере первый и второй элементы choice в строках 42 и 43 передают управление в локальный диалог (т. е. jb место в этом же документе) после выбора этих элементов. Третий элемент choice переносит пользователя в документ publications.vxml. В строках 28—34 используется элемент prompt для напоминания пользователю о необходимо- сти выбора. Атрибут count ведет учет количества раз произнесения подсказки prompt (т. е. всякий раз при про- изнесении компьютером подсказки атрибут count увеличивается на единицу). Атрибут count передает управле- ние другой подсказке по достижении определенного лимита. Атрибутом timeout задается отрезок времени ожи- дания программой ответа пользователя после произнесения подсказки. Если пользователь не ответил до истечения срока ожидания, в строках 36—40 компьютером произносится подсказка о необходимости выбора на секунду короче. При выборе пользователем опции publications файл publications.vxml (листинг 21.5) загружается в браузер. В строках 107—113 определяются элементы link, предоставляющие ссылки на main.vxml. В строках 115—117 предоставляются ссылки на элемент main (строки 121—141), запрашивающие пользователя о выборе одной из следующих публикаций: Java, С или C++. Элементы form в строках 143—217 описывают книги, соответствую- щие указанным темам. После произнесения браузером описания управление передается элементу form с атрибу- том id, значение которого равно repeat (строки 219—234). В табл. 21.3 представлено краткое описание каждого тега VoiceXML, использованного в предыдущем примере (см. листинг 21.5).
814 Глава 21 Таблица 21.3. Теги VoiceXML Тег VoiceXML Описание <assign> Присвоение значения переменной <block> Представление информации пользователям без взаимодействия между пользователем и компьютеров (т. е. компьютер не ожидает ввода данных) <break> Команда компьютеру прервать "речь" на заданный отрезок времени <choice> Задание опции в элементе menu <enumerate> Перечисление всех доступных для пользователя опций <exit> Выход из программы <filled> Содержит элементы, исполняющиеся при получении компьютером входных данных от пользователя в элемент form <form> Получение информации от пользователя для набора переменных <goto> Передача управления от одного диалога другому <grammar> Задание элемента grammar для входных данных, ожидаемых от пользователя <if>, <else>, <elseif> Указание оператора управления используемого для логических решений <link> Осуществление передачи управления, сходного с тегом <goto>, но элемент link может выполняться в любое время исполнения программы <menu> Предоставление пользовательских опций с последующей передачей управления другим диалогам, исходя из выбранной опции <prompt> Задание текста для прочтения пользователям после выбора <subdialog> Вызов другого диалога. После исполнения поддиалога управление возобновляется вы- зывающим диалогом <var> Объявление переменной <vxml> Тег верхнего уровня, указывающий, что документ должен быть обработан интерпретато- ром VoiceXML 21.12. CallXML Другой передовой технологией, обеспечивающей преимущества доступа людям с физическими недостатками, является технология поддержки речи CallXML, разработанная и сопровождаемая Voxeo (www.voxeo.com). С помощью CallXML создаются программные приложения, обеспечивающие связь с сетью по телефону, управ- ляющие входящими и исходящими телефонными звонками. В число примеров приложений CallXML входят голосовая почта, интерактивные системы голосовых ответов, а также ожидание вызова Интернета. VoiceXML позволяет компьютерам начитывать содержимое Web-страниц пользователям с нарушениями зрения; система CallXML выполняет ту же операцию по телефону Технология CallXML является незаменимой для людей, у которых нет компьютера, но есть телефон. При пользовательском доступе к приложениям CallXML механизм преобразования текста в речь (text-to- speech, TTS) переводит текст в автоматический голос, после чего система TTS начитывает пользователям ин- формацию, содержащуюся в элементах CallXML. Приложения, поддерживающие технологию CallXML, специ- ально разработаны для ответов на вводимые запросы телефонных абонентов. Примечание ______________,___________________________________________________________ Для доступа к системе CallXML необходимо наличие телефонного аппарата с тоновым набором. Обычно приложения CallXML проигрывают в качестве выходных данных заранее записанные аудиоролики с запросом ответов в качестве входных данных. В аудиоролике может содержаться приветствие, представляющее абонентов приложению, либо перечисление опций меню с запросом ввода записи с наборной панели телефона. В некоторых приложениях, например голосовой почте, требуется как речевой, так и наборный ввод. При полу- чении приложением входных данных оно отвечает активизацией элементов CallXML (например, text), содер- жащих информацию, которую механизм TTS произносит для пользователя. Если программа не получит вход- ных данных в рамках заданного отрезка времени, то пользователь получает подсказку о необходимости ввода данных.
Обеспечение доступности программных приложений 815 При доступе пользователя к приложению CallXML входящий телефонный звонок называется сеансом. Прило- жение CallXML может поддерживать несколько сеансов, т. е. одновременно обрабатывать несколько телефон- ных вызовов. Каждый сеанс не зависит от других, и для идентификации ему присваивается уникальный иденти- фикатор сеанса — sessionlD. Сеанс прекращается, когда пользователь кладет трубку на рычаг аппарата (нажи- мает кнопку выключения), либо при активизации приложением CallXML элемента hangup. В первом приложении CallXML продемонстрирован классический пример "Hello World” (листинг 21.6). В пер- вой строке содержится необязательное объявление XML. Значение version указывает версию XML, которой поддерживается документ Текущая рекомендация — версия 1 0. Значение encoding указывает тип кодирования стандарта Unicode, поддерживаемый данным приложением. Для рассматриваемого примера применяется коди- ровка UTF-8, требующая восемь битов для передачи и получения данных. Подробности о стандарте Unicode описаны в приложении 7. Тег <callxmi> в строке 6 объявляет о том, что содержимым является документ CallXML. В строке 7 содержится текст (text) "Hello World". Весь текст для начитывания системой TTS должен быть заключен между тегами <text>. I Листинг 21.6. CallXML-пример "Hello World** (предоставлено компанией Voxeo Corporation 2000—2002) ... ......... .....Jn ...... 1 <?xml version = "1.0" encoding = "UTF-8"?> 2 3 <!— Листинг 21.6: hello.xml —> 4 <!— Классический пример Hello World —> 5 6 <callxml> 7 <text>Hello World.</text> 8 </callxml> Работа приложения продемонстрирована на рис. 21.14. Рис. 21.14. Приложение, поддерживающее CallXML Для развертывания приложения CallXML следует зарегистрироваться в сообществе Voxeo (community.voxeo.com) — на Web-pecypce, упрощающем процессы создания, отладки и развертывания программных приложений, под- держивающих телефонную связь. По большей части ресурсы Voxeo предоставляются бесплатно, однако при коммерческом внедрении приложений CallXML с компаний взимается плата. Сообщество Voxeo присваивает каждому приложению CallXML уникальный телефонный номер для того, чтобы внешние пользователи могли получить доступ к этому приложению и взаимодействовать с ним. Примечание_________________________________________________________________________________ Voxeo выделяет телефонные номера только для приложений, размещенных в Интернете. При наличии доступа к Web-серверу (IIS, PWS или Apache) его можно использовать для регистрации приложения CallXML Если такой возможности нет, откройте учетную запись в Интернете в любой компании-провайдере (www.geocities.com, angelfire.lycos.com, www.stormpages.com,www.freewebsites.com или www.brinkster.com). В этих компаниях имеется возможность размещения отдельными пользователями документов в Интернете с помощью Web- служб.
816 Глава 21 В листинге 21.6 также представлена функция регистрации программы Voxeo Account Manager, доступной для зарегистрированных пользователей. С помощью данной функции "разговор” пользователя с приложением запи- сывается и отображается. В первой строке функции регистрации введен URL приложения CallXML и глобаль- ные переменные, относящиеся к данному сеансу. При открытии сеанса приложение создает и присваивает зна- чения глобальным переменным, к которым оно имеет доступ и может модифицировать. В оставшихся строках отображается "разговор”. Данный пример демонстрирует одностороннюю связь (т. е. "монолог": программа не получает от пользователя данных ввода), где система TTS произносит фразу "Hello World". В последней строке выведено сообщение end of session, извещающее о прекращении телефонного вызова. Функция регистрации помогает разработчиками отладке приложений. При просмотре "разговора" CallXML программист может опре- делить точку прекращения исполнения приложения. При внезапном (аварийном) закрытии программы функция регистрации отображает информацию о типе и местоположении ошибки, направляя разработчика в проблемную часть программы. z В следующем примере (листинг 21.7) представлено приложение CallXML, сообщающее номера ISBN трех книг издательства Deitel — "Internet and World Wide Web How to Program: Second Edition", "XML How to Program" и "Java How to Program: Fourth Edition", — в зависимости от набранных пользователем входных данных. Примечание________________________________________________________________________________ Для наглядности код отформатирован. • --ЪЮГЛ Листинг 21.7. Пример приложения CallXML, считывающего три номера ISBN (предоставлено компанией Voxeo Corporation 2000—2002) 1 <?xml version = "1.0" encoding » "UTF-8"?> 2 3 <!— Листинг 21.7: isbn.xml —> 4 <!— Прочтение трех номеров ISBN книг издательства Deitel —> 5 б <callxml> 7 <block> 8 <text> 9 Welcome. To obtain the ISBN of the Internet and World 10 Wide Web How to Program: Second Edition, please enter 1. 11 To obtain the ISBN of the XML How to Program, 12 please enter 2. To obtain the ISBN of the Java How 13 to Program: Fourth Edition, please entor 3. To exit the 14 application, please enter 4. 15 </text> 16 17 <!— Получение числового значения, введенного пользователем, 18 <!— и его сохранение в переменной ISBN. Йользователь имеет 19 <!— 60 секунд для ввода одного числового значенЛз 20 <getDigits var = "ISBN" 21 maxDigits = "1" 22 termDigits = "1234" 23 maxTime = "60s" /> 24 25 <!— Запрос пользователя на ввод действующего —> 26 <!— значения по истечении 60 секунд —> 27 <onMaxSilence> 28 <text> 29 Please enter either 1, 2, 3 или 4. 30 </text> 31 32 <getDigits var = "ISBN" 33 termDigits = "1234"• 34 maxDigits = "1" 35 maxTime = "60s" /> z 36 37 </onMaxSilence> 38
Обеспечение доступности программных приложений 817 39 <onTermDigit value = "1"> 40 <text> 41 The ISBN for the Internet book is 0130308978. 42 Thank you for calling our CallXML application. 43 Good-bye. 44 </text> 45 </onTermDigit> 46 47 conTermDigit value = "2"> 48 _ <text> 49 The ISBN for the XML book is 0130284173. 50 Thank you for calling our CallXML application. 51 Good-bye. 52 </text> 53 </onTermDigit> 54 55 <onTermDigit value = ”3"> 56 <text> 57 The ISBN for the Java book is 0130341517. 58 Thank you for calling our CallXML application. 59 \ Good-bye. 60 </text> 61 </onTermDigit> 62 63 <onTermDigit value = "4"> 64 <text> 65 Thank you for calling our CallXML application. 66 Good-bye. 67 </text> 68 </onTermDigit> 69 </block> 70 71 <!— Обработчик событий, прерывающий вызов —> 72 conHangup /> 73 </callxml> Ter <block> (строка 7) инкапсулирует другие теги CallXML. Обычно наборы тегов, выполняющих сходные за- дачи, заключены в <block>.. .</Ь1оск>. В данном примере элемент block инкапсулирует теги <text>, <getDigits>, <onMaxSilence> и <onTermDigit>. Элемент block также можно вкладывать в другие элементы block. В строках 20—23 содержатся некоторые атрибуты тега <getDigits>. Элемент getDigits получает введенный пользователем на наборной клавиатуре телефонного аппарата ответ и сохраняет его в переменной, объявленной атрибутом var (т. е. ISBN). Атрибут maxDigits (строка 21) указывает максимальное количество цифр, прини- маемое программой. Данная программа принимает только один символ. Если не указан максимум, тогда в при- ложении используется значение по умолчанию — nolimit (без ограничений). Атрибут termDigits (строка 22) содержит список символов, заканчивающих входные данные пользователя. При вводе им символа из этого списка программа уведомляется о том, что она получила последний приемлемый символ; любой символ, введенный после него, является недействительным. Данные символы не прерывают те- лефонного вызова: они просто уведомляют программу о переходе к следующей команде, потому что все необ- ходимые входные данные уже получены. В данном примере значения для атрибута termDigits— 1, 2, 3 и 4. Значение по умолчанию для атрибута termDigits — пустая строка ('"’)• Атрибут maxTime (строка 23) указывает максимальное время, которое программа будет ожидать ответа пользо- вателя. Если пользователь не введет данные в заданных временных рамках, тогда приложение CallXML активи- зирует обработчик события onMaxSilence. Значение по умолчанию для этого атрибута — 30 секунд. Элемент onMaxSilence (строки 27—37) является обработчиком события, активизируемым по истечении атрибу- та maxTime (или maxSilence). Обработчик уведомляет приложение о выполнении соответствующей операции, если пользователь не ответит. В таком случае программа будет требовать от пользователя ввода значения, по- тому что действие атрибута maxTime истекло. При получении входных данных тег <getDigits> (строка 32) со- храняет введенное значение в переменной isbn. Элемент onTermDigit (строки 39—68) является обработчиком событий, уведомляющим программу о выполне- нии соответствующей операции при выборе пользователем одного из символов termDigits. К элементу 52 Зак. 3333
818 Глава 21 getDigits должен относиться минимум один тег <onTermDigit> (т. е. должен появиться после элемента), даже если используется значение по умолчанию (""). Предлагаются четыре операции, которые программа может вы- полнить в ответ на определенное значение termDigits, введенное пользователем. Например, если пользователь вводит 1, то программа считывает номер ISBN для книги "Internet and World Wide Web How to Program: Second Edition”. В строке 72 содержится обработчик событий <onHangup/>, прерывающий телефонный вызов, когда пользова- тель опускает телефонную трубку на рычаг аппарата. В данном случае обработчик события <onHangup> является пустым тегом (т. е. при его активизации никаких операций не выполняется). Функция регистрации (рис. 21.15) отображает "разговор" программы с пользователем. Как в предыдущем при- мере, в первой строке указан URL программы и глобальные переменные сеанса. В последующих строках ото- бражается "разговор": Программа спрашивает пользователя о том, какой номер ISBN следует зачитать; вызы- вающий вводит цифру 1 ("Internet and World Wide Web How to Program: Second Edition"), и приложение считы- вает соответствующий номер. Сообщение end of session (конец сеанса) указывает, что работа программы прекращена. Рис. 21.15. Пример приложения CallXML, считывающего три номера ISBN В табл. 21.4 приведены краткие описания различной логики и операций элементов CallXML. Логические эле- менты присваивают значения переменным сеанса и удаляют из них значения; элементы операций выполняют определенные задачи, например, ответы на телефонные вызовы во время сеанса и их прерывание. Полный спи- сок элементов CallXML доступен на сайте www.oasis-open.org/cover/callxmlv2.html. Таблица 21.4. Элементы CallXML Элементы Описание assign Присвоение значения value переменной var clear Удаление содержимого атрибута var £ clearDigits Удаление всех введенных пользователем цифр goto Переход к другому разделу текущего приложения CallXML или к другому приложению CallXML. Атри- бут value задает URL активизированного приложения В атрибуте submit перечислены переменные, передаваемые в активизированное приложение. Атрибут method указывает либо использование типа запроса HTTP get, либо post при отправке или получении информации. С помощью запроса get данные извлекаются из Web-сервера без изменения их содержимого; при запросе типа post извле- каются измененные данные
Обеспечение доступности программных приложений 819 % Таблица 21.4 (окончание) Элементы Описание run Запуск нового сеанса CallXML для каждого телефонного вызова. Атрибут value указывает извлекае- мое приложение CallXML. В атрибуте submit перечислены переменные, передаваемые в активизи- рованное приложение. Атрибут method указывает либо использование типа запроса HTTP get, либо post. В атрибуте var сохранен идентификационный номер сеанса sendEvent Обеспечение обмена сообщениями между сеансами. В атрибуте value сохраняется сообщение, а атрибут session указывает идентификационный номер сеанса, принимающего сообщение answer Ответ на входящий телефонный вызов call Вызов URL, заданного атрибутом value. В атрибуте callerID содержится номер телефона, отобра- жаемый на устройстве CallerID. Атрибутом maxTime задается отрезок времени ожидания ответа на вызов до отключения conference Подключение нескольких сеансов так, что пользователи могут участвовать в телефонной конферен- ции. Атрибут targetsessions задает идентификационные номера сеансов, а атрибут termDigits указывает клавиши, прерывающие вызов wait Ожидание ввода данных пользователем. Атрибут value указывает время ожидания. Атрибут termDigits определяет клавиши, прерывающие исполнение элемента wait Play i Воспроизведение аудиофайлов, либо произнесение значения, сохраненного в виде цифры, даты или денежной суммы и указанного атрибутом format. Атрибут value содержит информацию (местополо- жение аудиофайла, даты или денежной суммы), соответствующую атрибуту format. Атрибут clearDigits задает удаление или не удаление введенных ранее данных. Атрибут termDigits ука- зывает клавиши для остановки аудиофайла и т. д. recordAudio Запись аудиофайла и его сохранение в URL, заданного атрибутом value. Атрибут format указывает расширение файла аудиоролика. В число других атрибутов входят termDigits, clearDigits, maxTime и maxSilence. , 21.13. JAWS для Windows JAWS (Job Access with Sound) — один из ведущих механизмов считывания информации с экрана на современ- ном рынке. Данное программное приложение создано компанией Henter-Joyce— филиалом Freedom Scientific — в помощь людям с нарушениями зрения. Демонстрационная версия пакета JAWS имеется на сайте www.freedomscieiitific.com; она полнофункциональна и включает в себя расширенную и настраиваемую оперативную справочную систему. Пользователи могут вы- бирать голос, который будет "считывать" содержимое Web-страницы, а также скорость произнесения текста. Также здесь можно задавать "горячие" клавиши. Демо-версия — англоязычная; в полной версии пользователь может выбрать любой из поддерживаемых языков. В программе JAWS имеются специальные клавишные команды для таких популярных приложений, как Internet Explorer и Microsoft Word. К примеру, при просмотре Web-страниц в Internet Explorer возможности программы JAWS выходят за рамки простого озвучивания материалов с экрана. При активизированной программе JAWS после нажатия комбинации клавиш <Insert>+<F7> в Internet Explorer откроется диалоговое окно Links List с отображением всех имеющихся на Web-странице ссылок. Подробная информация о пакете JAWS и других про- дуктах компании Hehter-Joyce имеется на сайте www.freedomscientific.com. 21.14. Другие инструменты доступа Существует множество различных программных продуктов, облегчающих доступ к Web людям с различными физическими недостатками. Одна из таких технологий — Active Accessibility от корпорации Microsoft — задает протокол, по которому средство обеспечения доступности получает согласованную информацию о пользова- тельском интерфейсе приложения. Средствам обеспечения доступности требуется такая информация, как назва- ние, местоположение тех или иных элементов GUI в приложении, чтобы средство доступности передавало ее надлежащим образом заинтересованной аудитории пользователей. Программа Active Accessibility дает разра- ботчикам программного обеспечения и средств доступности возможность проектирования программ, совмести- мых между собой. Более того, программа Active Accessibility разделена на два компонента, что дает возмож- ность пользоваться ею как программистам, так и людям, применяющим специальные средства обеспечения дос- тупности. Компонент Software Development Kit (SDK) предназначен для программистов: в него входят инструменты для тестирования, библиотеки и заголовочные файлы. Компонент Redistribution Kit (RDK) предна-
820 Глава 21 значен для пользователей специальных средств доступности: в операционную систему Microsoft устанавливает- ся компонент исполнения. Средства доступности используют компонент исполнения программы Active Accessibility для взаимодействия с любым программным приложением и получения из него информации. Под- робности о программе Active Accessibility имеются на сайте www.msdn.microsoft.com/iibrary/default.asp7url- /nhp/Default.asp?contentid=28000544. Другим важным средством доступности для пользователей с нарушением зрения является клавиатура Брайля. Помимо наличия клавиш с изображениями букв, на таких клавиатурах имеется соответствующий символ на шрифте Брайля. Довольно часто клавиатуры Брайля совмещены с синтезатором речи или монитором Брайля, с помощью которого пользователи могут проверять правильность набора. Синтез речи также помогает пользователям с физическими недостатками. Речевые синтезаторы уже много лет применяются для общения с людьми, не имеющими возможности говорить. Однако возрастающая популяр- ность Web еще больше стимулировала интерес к речевому синтезу и распознаванию речи. Теперь подобные технологии обеспечивают возможность более интенсивного использования компьютеров людьми с физически- ми недостатками. Создание речевых синтезаторов также обусловило развитие других технологий, таких как VoiceXML и AuralCSS (www.w3.org/TR/REC-CSS2/aural.html). Эти инструментальные средства обеспечивают доступ к Web-сайтам людям с физическими недостатками и не умеющим читать. Несмотря на существование адаптивных программных и аппаратных средств для пользователей с нарушениями зрения, доступность компьютеров и Интернета по-прежнему "тормозится" высокими ценами, быстрым мораль- ным устареванием и неоправданной сложностью действующих технологий. Более того, практически все совре- менное программное обеспечение требует установки видящим специалистом. Ocularis — это проект, запущен- ный сообществом разработчиков открытых программных средств, направленный на помощь в решении выше- означенных проблем. (Уже существуют открытые программные средства для слабовидящих; несмотря на их преимущества перед аналогичными закрытыми патентованными продуктами, они еще далеко не полностью раскрыли свой потенциал.) С помощью системы Ocularis слабовидящие пользователи получают доступ ко всем средствам операционной системы Linux и могут ими пользоваться. В число программных продуктов, интегри- рующихся с Ocularis, входят текстовые редакторы, калькуляторы, несложные финансовые программы, интер- нет-браузеры и клиентские продукты работы с электронной почтой. Кроме того, для использования программ с интерфейсом командной строки в Ocularis входит устройство считывания экрана. Официальный Web-сайт Ocu- laris: ocuiaris.sourceforge.net. Попытки совершенствования языков разметки приносят пользу не только слабовидящим пользователям. Слабо- слышащие люди также имеют набор инструментальных средств для интерпретации аудиоинформации, переда- ваемой по сети. Одна из них — язык Synchronized Multimedia Integration Language (SMIL, синхронизированный язык интеграции мультимедиа) разработан для добавления дополнительных треков (слоев информации в одном аудио- или видеофайле) в содержимое мультимедиа. Дополнительные треки могут содержать закрытые субтитры. В настоящее время разрабатываются технологии в помощь людям, страдающим более серьезными физическими недостатками, например, общим параличом. Одна из таких технологий — Eagle Eyes — разработана исследова- телями Бостонского колледжа (www.bc.edu/eagleeyes); это система, преобразующая движения глаз в перемеще- ния курсора мыши. Пользователь передвигает курсор движениями глаз или головы, управляя, таким образом, компьютером. Компании GW Micro, Henter-Joyce и Adobe Systems, Inc. работают над созданием программных средств в по- мощь людям с физическими недостатками. Пакет Adobe Acrobat 5 0 соответствует программному интерфейсу приложения (Application Programming Interface, API) корпорации Microsoft с целью распространения деловой информации более широкой аудитории пользователей. JetForm Corp, также старается удовлетворять потребно- сти людей с физическими недостатками разработкой программного обеспечения XML на базе сервера. Новые программные средства дают пользователям возможность загрузки информации в самых удобных для них фор- матах. В WWW имеется очень много служб, помогающих компаниям электронного бизнеса в проектировании Web- сайтов, доступных для людей с физическими недостатками. Дополнительная информация широко представлена Министерством юстиции США (www.usdoj.gov) в ресурсах с подробным освещением юридических и техниче- ских аспектов доступности программных средств для людей с ограниченными физическими возможностями. 21.15. Доступность в операционной системе Microsoft Windows 2000 Из-за чрезвычайно широкого распространения операционной системы Windows особую важность приобретает вопрос ее доступности для людей с физическими недостатками. Начиная с Windows 95, корпорация Microsoft начала включать функции универсальной доступности в свои операционные системы и многие приложения,
Обеспечение доступности программных приложений 821 в том числе MS Office 97, Office 2000 и Netmeeting. В операционной системе Windows 2000 эти функции были усовершенствованы. Все они доступны в мастере Accessibility Wizard системы с подробным описанием и кон- фигурацией пользовательских компьютеров в соответствии с выбранными спецификациями. В данном разделе Accessibility Wizard используется для описания механизма конфигурации функций доступности операционной системы Windows 2000. Для доступа к Accessibility Wizard на пользовательском компьютере ^должна быть установлена система Microsoft Windows 2000. Нажмите кнопку Start и выберите последовательность команд Programs | Accessories | Accessibility | Accessibility Wizard. При запуске мастера откроется окно Welcome. Нажмите кнопку Next. В следующем окне (рис. 21.16) пользователю направляется предложение о выборе размера гарнитуры шрифтов. Измените, при необходимости, размер и нажмите кнопку Next. На рис. 21.16 показано диалоговое окно с разделом Display Settings. В нем пользователь может применить на- стройки размера шрифта, сделанные в предыдущем окне, изменить разрешение монитора, активизировать Microsoft Magnifier (программу, отображающую в отдельном окне увеличенную часть экрана), а также отклю- чить персонализированные меню. В последних скрыты редко используемые программы из меню запуска, кото- рые могут мешать людям с ограниченными физическими возможностями. Сделайте нужный выбор и нажмите кнопку Next. В разделе Set Wizard Options (рис. 21.17) пользователям задаются вопросы об их физических недостатках; от- веты на них являются основой, позволяющей мастеру Accessibility Wizard настроить систему Windows точно под требования того или иного пользователя. В целях демонстрации авторы выбрали все типы физических не- достатков, включенные в диалоговое окно. Для продолжения нажмите кнопку Next. Рис. 21.16. Раздел Display Settings в окне Accessibility Wizard Рис. 21.17. Опции инициализации Accessibility Wizard 21.15.1. Инструментальные средства доступности для слабовидящих пользователей При выборе всех опций в окне, показанном на рис. 21.17, мастер начинает конфигурировать систему Windows так, чтобы она стала доступной для людей с физическими недостатками. Диалоговое окно, показанное на рис. 21.18, дает пользователям возможность увеличивать размеры полос прокрутки и границ окна для лучшей видимости. Для перехода к следующему диалоговому окну нажмите кнопку Next. На рис. 21.19 показано диалоговое окно, в котором пользователи с нарушениями зрения и с проблемами чтения могут увеличивать размеры пиктограмм. При нажатии кнопки Next открывается диалоговое окно с разделом Display Color Settings (рис. 21.20). С по- мощью этих настроек пользователи могут изменять цветовую палитру Windows, а также размеры различных элементов экрана. Нажмите кнопку Next для открытия диалогового окна настройки параметров курсора мыши (рис. 21.21). Лю- бой, кому доводилось пользоваться ноутбуком, знает, насколько сложно бывает увидеть курсор на экране. Для слабовидящих пользователей эта проблема усугубляется. Для ее решения мастер доступности предлагает воз- можности изменения размера курсора, его цвета (с белого на черный), а также курсоры, при наведении которых на объекты цвет последних меняется. Нажмите кнопку Next.
822 Глава 21 Рис. 21.18. Диалоговое окно изменения размеров полос прокрутки и границ окна Рис. 21.19. Настройка размеров элементов окна Рис. 21.20. Опции настройки цветов экрана в разделе Display Color Settings Рис. 21.21. Инструменты настройки параметров курсора мыши мастера Accessibility Wizard 21.15.2. Инструментальные средства доступности для слабослышащих пользователей Данный раздел, посвященный доступности программных продуктов для слабослышащих пользователей, откры- вается разделом SoundSentry (рис. 21.22). SoundSentry— это инструмент, создающий визуальные сигналы, которые сообщают пользователям о событиях, происходящих в системе. Например, люди с нарушениями слуха не могут слышать предупреждающие звуковые сигналы; SoundSentry преобразует их в визуальные сигналы, видимые на экране. Для перехода к следующему диалоговому окну нажмите кнопку Next. Следующее диалоговое окно открывает раздел ShowSounds (рис. 21.23). В нем добавляются титры к произне- сенному тексту и прочим звукам, создаваемым современными богато насыщенными мультимедийными эффек- тами программными средствами. Обратите внимание, что для использования ShowSounds в отдельных прило- жениях разработчики должны создавать титры и произнесенный текст непосредственно в рамках их программ- ных средств. Сделайте нужный выбор и нажмите кнопку Next. 21.15.3. Инструментальные средства доступности для пользователей без возможности работы с клавиатурой В следующем диалоговом окне описывается программа Sticky Keys (рис. 21.24) облегчающая задачу одновре- менного нажатия нескольких клавиш для пользователей, испытывающих затруднения при работе с клавиатурой. Многие важные команды компьютеру можно подать только нажатием определенной комбинации клавиш.
Обеспечение доступности программных приложений 823 Рис. 21.22. Раздел SoundSentry диалогового окна Accessibility Wizard Рис. 21.23. Раздел ShowSounds диалогового окна Accessibility Wizard Например, для подачи команды перезагрузки компьютера необходимо нажать комбинацию клавиш <Ctrl>+ +<Alt>+<Delete>. Программа StickyKeys позволяет выполнить ввод комбинации последовательным нажатием нужных клавиш (по одной), а не одновременно. Для перехода к разделу BounceKeys (рис. 21.25) нажмите кнопку Next. Рис. 21.24. Раздел StickyKeys диалогового окна Accessibility Wizard Рис. 21.25. Раздел BounceKeys диалогового окна Accessibility Wizard Другой распространенной проблемой, с которой сталкиваются некоторые пользователи с ограниченными физи- ческими возможностями, является случайное многократное нажатие одной клавиши. Обычно это происходит при слишком долгом удержании клавиши. С помощью программы BounceKeys компьютер игнорирует повто- ряющиеся нажатия клавиш. Нажмите кнопку Next. Программа ToggleKeys (рис. 21.26) предупреждает пользователя о нажатии одной из клавиш блокировки (<Caps Lock>, <Num Lock> или <Scroll Lock>) подачей звукового сигнала. Сделайте нужный выбор и нажмите кнопку Next. Далее откроется раздел Extra Keyboard Help (рис. 21.27). В нем пользователь может активизировать инстру- мент, отображающий информацию о "горячих" клавишах и всплывающих подсказках, если такая информация доступна. Как и ShowSounds, данный инструмент требует от разработчиков предоставления содержимого. При нажатии кнопки Next откроется окно настройки MouseKeys (рис. 21.28). MouseKeys— это инструмен- тальное средство, использующее клавиатуру для имитации передвижений курсора мыши. Клавиши со стрелка- ми обозначают направление курсора, а клавиша с цифрой 5 — один щелчок. Для двойного щелчка пользователь должен нажать клавишу с символом <+>; для моделирования удержания кнопки мыши пользователь нажимает клавишу <Insert>, а для отпускания— клавишу <Delete>. Определитесь с активизированием программы MouseKeys и нажмите кнопку Next.
824 Глава 21 Рис. 21.26. Раздел ToggleKeys диалогового окна Accessibility Wizard Рис. 21.27. Раздел Extra Keyboard Help диалогового окна Accessibility Wizard Рис. 21.28. Раздел MouseKeys диалогового окна Accessibility Wizard Accessibility Wizaid Mouse Button Setting* You can change how the mouse buttons function. E* jjandeS rXeft+unded Youmaj WJMTI nounrtown mill you Rrefei. if^ittoawu pteieito vs«y~ir<wi’ ’ Left button. • Hotmel select. •Normal drag ' Rightbutton . - Context menu -Special drag <fiack J ЦЫ» I Cancel | Рис. 21.29. Раздел Mouse Button Settings диалогового окна Accessibility Wizard Практически все существующие компьютерные инструменты, включая манипуляторы типа "мышь", спроекти- рованы для их использования правшами. Корпорация Microsoft приняла во внимание эту проблему и добавила в мастер Accessibility Wizard раздел Mouse Button Settings (рис. 21.29). Данное инструментальное средство обеспечивает пользователей возможностью создания виртуальной мыши для левшей путем перестановки функ- ций кнопок. Нажмите кнпку Next. Скорость срабатывания кнопок мыши и перемещения курсора задается в разделе Mouse Speed (рис. 21.30) мас- тера Accessibility Wizard. Скорость изменяется при перемещении виртуального ползунка на полосе прокрутки. При нажатии кнопки Next задается нужная скорость, и открывается раздел Set Automatic Timeouts мастера (рис. 21.31) Несмотря на всю важность средств обеспечения доступности к программам людей с физическими недостатками, эти средства могут быть помехой для тех, кто в них не нуждается. В случаях необходимости на- личия определенных инструментов доступности важно предусмотреть возможность их включения и отключе- ния. В разделе Set Automatic Timeouts задается тайм-аут для активизации или отключения инструментов дос- тупности. Тайм-аут активизирует или отключает выполнение определенной операции после простоя компьюте- ра в течение заданного отрезка времени. Хранители экрана — типичный пример программы с определенным временем простоя. В данном случае тайм-аут задается для активизации инструментальных средств доступности. После нажатия пользователем кнопки Next откроется раздел Save Settings to File (рис. 21.32). В нем задается возможность использования настроек инструментов доступности в качестве настроек по умолчанию, приме- няющихся после перезагрузки компьютера или по истечении определенного лимита времени. Если средства доступности используются часто, то настройки этих средств следует применить по умолчанию. Пользователи также могут сохранить настройки множественных видов доступности: создать файл с расширением acw, выбор
Обеспечение доступности программных приложений 825 которого активизирует и сохраняет настройки средств повышенной доступности на любом компьютере с опера- ционной системой Windows 2000. Рис 21.30. Раздел Mouse Speed Рис. 21.31. Раздел Set Automatic Timeouts Рис. 21.32. Сохранение новых настроек доступности 21.15.4. Microsoft Narrator Microsoft Narrator — это программа речевого воспроизведения текста, предназначенная для людей с наруше- ниями зрения. С ее помощью можно прослушивать текст, описания текущей настольной среды, а также полу- чать предупреждения о тех или иных событиях, происходящих в Windows. Пакет Narrator может оказать по- мощь при конфигурации системы Microsoft Windows. В сущности, это — хранитель экрана, работающий в среде Internet Explorer, Wordpad, Notepad и большинства программ, имеющихся в панели управления (Control Panel)1. Несмотря на ограниченность функциональности вне указанных приложений, Narrator может стать незаменимым средством прохождения среды Windows. Для ознакомления с функциональностью программы Narrator рассмотрим ее использование совместно с не- сколькими приложениями Windows. Нажмите кнопку Start, выберите раздел Programs. В открывающемся спи- ске выберите Accessories | Accessibility | Narrator. После открытия программы в ней описывается текущее окно на переднем плане. Затем программа "произносит" для пользователя текст данного окна. При нажатии кнопки ОК открывается диалоговое окно, показанное на рис. 21.33. При выборе первой опции программа Narrator получает команду на описание меню и новых окон при их откры- тии. Вторая опция передает команду на произнесение вводимых пользователем символов. При выборе третьей опции курсор мыши перемещается в область, считываемую в данный момент программой. При нажатии на кнопку Voice... пользователь может изменять высоту, громкость голоса рассказчика и скорость произнесения им слов (рис. 21.34).
826 Гпава 21 Рис. 21.33. Окно Narrator Рис. 21.34. Окно Voice Settings Продемонстрируем работу программы Narrator в разных приложениях. После ее запуска откройте приложение Notepad и щелкните кнопкой мыши в меню File. Программа Narrator объявляет открытие программы и начинает описывать элементы меню File. По мере перемещения пользователя вниз по списку программа произносит на- звание строки, на которую указывает курсор мыши. Введите какой-либо текст и нажмите комбинацию клавиш <Ctrl>+<Shifl>+<Enter>: программа прочтет его "вслух" (рис. 21.35). При отмеченной опции Read typed characters (см. рис. 21,33) программа произносит символы по мере их ввода. Для озвучивания текста можно пользоваться клавишами со стрелками. При нажатии клавиш <Т> и <i> программа Narrator произносит строки, примыкающие к текущему положению курсора мыши, а при нажатии клавиш <->> и <<—> Narrator произносит символы, примыкающие к текущему положению курсора мыши. "Hello, I am the Narrator utility. This is a test. Notice how Narrator follows punctuation and does not pause on carriage returns." Рис. 21.35. Программа Narrator, считывающая текст в приложении Notepad 21.15.5. Экранная клавиатура Microsoft Некоторые пользователи ограничены в действиях при работе с клавиатурой, но могут использовать указываю- щий манипулятор, например, мышь. Для таких пользователей хорошим подспорьем станет так называемая эк- ранная клавиатура (On-Screen Keyboard) Для доступа к экранной клавиатуре нажмите кнопку Start и последо- вательно выберите команды Programs | Accessories) Accessibility) On-Screen Keyboard. На рис. 21.36 пред- ставлена раскладка экранной клавиатуры от корпорации Microsoft. Рис. 21.36. Экранная клавиатура от Microsoft Те, кому сложно пользоваться экранной клавиатурой, могут приобрести более сложные программные продукты, например пакет Clicker 4 компании Inclusive Technology. Данная программа специально разработана для людей,
Обеспечение доступности программных приложений 827 не имеющих физической возможности эффективного использования клавиатуры. Самой важной ее особен- ностью является полная пользовательская настройка. Клавиши могут обозначать буквы, цифры, целые слова или даже графические изображения. Подробная информация о программе Clicker 4 имеется на сайте www.inclusive.cp.uk/catalog/clicker.shtml. 21.15.6. Функции доступности в Internet Explorer 6.0 Internet Explorer 6.0 предлагает целый набор опций, повышающих доступность к Web-содержимому. Для досту- па к этим функциям запустите программу, и в меню Tools выберите команду Internet Options.... После этого в меню Internet Options нажмите кнопку Accessibility... для открытия функций доступности (рис. 21.37). Рис. 21.37. Функции доступности Internet Explorer 6.0 Функции доступности в IE6.0 спроектированы с целью помощи в доступе к Интернету людям с ограниченными физическими возможностями. Имеется возможность игнорирования цветов, гарнитур шрифтов и тегов размеров гарнитур. При этом устраняются проблемы доступности, являющиеся результатом дилетантского проектирова- ния Web-страниц, и пользователи могут настраивать работу Web-браузера в соответствии со своими потребно- стями. Здесь даже имеется возможность задания таблицы стилей, которая будет форматировать каждый посе- щаемый Web-сайт в соответствии с личными пристрастиями пользователя. В диалоговом окне Internet Options щелкните кнопкой мыши на вкладке Advanced. Откроется диалоговое ок- но, показанное на рис. 21.38. Первая доступная опция обозначена как Always expand ALT text for images (Все- гда показывать весь текст, отмеченный тегами <alt>). По умолчанию IE6.0 скрывает часть текста, ограниченно- го тегами <alt>, если размер текста превышает размер изображения, описываемого этим текстом. Данная опция IE6.0 показывает весь текст. Вторая опция гласит: Move system caret with focus/selection changes (Перемеще- ние системного знака вставки при изменении фокуса/выбора). Она предназначена для повышения эффективно- сти считывания текста с экрана. В некоторых программах считывания используется системный знак вставки (мигающий вертикальный курсор, относящийся к редактированию текста), определяющий текст для произнесе- ния. Если данная опция не отмечена, тогда считывание Web-страниц может быть некорректным. При разработке сайтов Web-дизайнеры часто забывают о необходимости их доступности для людей с ограни- ченными физическими возможностями и, стремясь ввести как можно больший объем информации, часто ис- пользуют слишком мелкие шрифты Во многих программах-агентах эта проблема решается предоставлением пользователям возможности изменения размеров шрифтов. Откройте меню View и выберите команду Text Size для изменения размера шрифта на страницах, открытых в IE6.0. По умолчанию размер гарнитуры установлен на значение Medium. В данной главе представлены многие технологии, помогающие людям с различными физическими недостатка- ми пользоваться компьютерами и Интернетом. Надеемся, что читатели будут поддерживать идею важности и необходимости таких средств в их учебных заведениях и на рабочих местах.
828 Глава 21 Рис. 21.38. Подробные настройки функций доступности в Internet Explorer 6.0 21.16. Резюме В 1997 году Консорциум World Wide Web (W3C) запустил Инициативу доступа во Всемирную сеть (WAI). WA1 — это попытка сделать Web и Интернет более доступными; миссия подробно описана на сайте www.w3.org/WAL Одним из важных требований WAI является необходимость снабжения каждого графическо- го изображения, кинофильма или звука на Web-сайте описанием, четко формулирующим их предназначение; такое описание задается атрибутом alt. Доступностью называется степень удобства и простоты использования программного приложения или Web- сайта людьми с ограниченными физическими возможностями. Абсолютной доступности добиться сложно, по- тому что существует множество физических недостатков, разных языков, а также несовместимость аппаратных и программных средств. Специализированные пользовательские агенты, например, механизмы считывания текста с экрана (программы, дающие пользователям возможность слышать текст, отображенный на экране) и мониторы Брайля (устройства получения данных с программ считывания и вывода этих данных на шрифте Брайля) обеспечивают слабовидя- щих людей возможностью доступа к текстовой информации, отображенной на экране. Иногда интерпретировать Web-страницы с большими объемами мультимедийной информации для пользова- тельских агентов бывает сложно из-за некорректного проектирования таких страниц. Устройства считывания не могут интерпретировать изображения, кинофильмы и большинство объектов, представленных не в формате XHTML. При создании Web-страницы для обычной аудитории важно учитывать удобочитаемость и понятность разме- щаемых на ней материалов; для этой цели Web-дизайнеры могут пользоваться короткими словами; некоторым пользователям может быть непонятна просторечная и разговорная лексика. Для одновременного отображения на экране нескольких файлов XHTML Web-дизайнеры часто пользуются фреймами. К сожалению, фреймам часто не хватает точных описаний, и это закрывает содержимое тех или иных Web-сайтов для доступа пользователей с текстовыми браузерами или людей с нарушениями зрения. VoiceXML — технология распознавания и воспроизведения речи — имеет чрезвычайно важное значение для людей с нарушениями зрения, а также для тех, кто испытывает трудности с чтением. Система "наговаривает" пользователям содержимое Web-страниц и "понимает" слова, произнесенные ими в микрофон. С помощью CallXML — языка, созданного и сопровождаемого компанией Voxeo, — разрабатываются прило- жения связи с Интернетом по телефону. Такие программы самостоятельно "подстраиваются" под вводимые пользователями данные. Клавиатуры Брайля похожи на обычные клавиатуры с той разницей, что, помимо букв, клавиши таких клавиа- тур также обозначают определенный символ шрифта Брайля. Чаще всего клавиатуры Брайля объединяются
Обеспечение доступности программных приложений 829 с синтезатором речи или монитором Брайля с тем, чтобы пользователи, вводящие текст, могли проверить свои ошибки. Попытки совершенствования языков разметки приносят пользу не только слабовидящим пользователям Слабо- слышащие люди также имеют набор инструментальных средств для интерпретации аудиоинформации, переда- ваемой по сети. Существуют открытые программные средства для слабовидящих; которые часто превосходят по своей эффек- тивности аналогичные закрытые патентованные продукты. Слабослышащие пользователи скоро получат в свое распоряжение язык SMIL. Этот язык разметки создан для добавления дополнительных треков— слоев информации в одном аудио- или видеофайле. Дополнительные треки могут содержать закрытые субтитры. Все функции доступности, предоставляемые операционной системой Windows 2000, размещены в приложении Accessibility Wizard, в котором подробно описан каждый инструмент и процессы конфигурации компьютера, в соответствии с выбранными спецификациями. 21.17. Ресурсы Интернета и WWW В Интернете и Web имеется множество ресурсов, посвященных программным средствам обеспечения доступ- ности. В данном разделе представлены некоторые из них. 21.17.1. Общая информация, руководства и определения □ www.w3.org/WAI На сайте инициативы WAI, разработанной Консорциумом World Wide Web, продвигаются идеи проектиро- вания универсально доступных Web-сайтов. Здесь содержатся действующие руководства и будущие стан- дарты доступности Web. П www.w3.org/TR/xhtmll XHTML 1 0 Recommendation (Рекомендация XHTML 1.0) содержит общую информацию об XHTML 1.0 Здесь рассматриваются аспекты совместимости, информация об определении типов документов, определе- ния, терминология и многое другое. □ www.abledata.com/text2/icg_hear.htm На странице содержится руководство для потребителя, в котором рассматриваются технологии проектиро- вания программных продуктов для слабослышащих пользователей. □ www.washington.edu/doit На сайте DO-IT (Disabilities (физические недостатки), Opportunities (возможности), Internetworking (работа в Интернете) и Technology (технология)) Университета Вашингтона представлена информация и ресурсы Web-разработок для создания универсально доступных Web-сайтов. □ www.webable.com Сайт WebABLE содержит ссылки на множество интернет-ресурсов, посвященных людям с физическими не- достатками; здесь особое внимание уделено разработке специальных технологий. □ www.webaim.org На сайте WebAIM представлено множество учебных материалов, статей, моделей и других полезных ресур- сов, демонстрирующих разработку универсально доступных Web-сайтов. Представлены модели устройства считывания текста с экрана. □ deafness.about.com/health/deafness/msubvib.htm Информация о вибротактильных устройствах, обеспечивающих прослушивание аудиоматериалов слабослы- шащими пользователями посредством вибраций. 21.17.2. Разработка приложений доступности по существующим технологиям □ wdvl.com/Authoring/Languages/XML/XHTML Виртуальная библиотека Web-разработчиков представляет введение в XHTML. Статьи, примеры и ссылки на другие технологии.
830 Глава 21 □ www.w3.org/TR/1999/xhtml-modularization-19990406/DTD/doc Сайт документации по XHTML 1.0 DTD; ссылки на документацию DTD по теме строгих, переходных и фреймовых определений типов документов. □ www.webreference.com/xml/reference/xhtml.html Список наиболее часто используемых тегов XHTML: заголовочные теги, табличные теги, фреймовые теги и теги форм. Представлено описание каждого типа тегов. О www.w3.org/TR/REC-CSS2/aural.html На сайте обсуждаются слуховые таблицы стилей (Aural Style Sheets) с обозначением целей и областей при- менения новой технологии. П www.trill-home.com/lynx/public_lynx.html Предоставление пользователям возможности просмотра Web-сайтов с помощью браузера Lynx. Пользовате- ли видят представление Web-страниц без применения самых последних технологий. □ java.sun.com/products/java-media/speech/forDevelopers/JSML На данном сайте представлены спецификации JSML — речевого языка разметки Java от Sun Microsystems. Данный язык, похожий на VoiceXML, обеспечивает доступность к Web-содержимому людям с нарушениями слуха. □ ocfo.ed.gov/coninfo/clibrary/software.htm Сайт Министерства образования США, на котором изложены требования к разработчикам доступных про- граммных продуктов и вспомогательные материалы. j www.speech.cs.cmu.edu/comp.speech/SpeechLinks.html Страница "Speech Technology Hyperlinks" включает свыше 500 ссылок на сайты, имеющие отношение к ин- струментальным средствам распознавания и воспроизведения речи. П www.islandnet.com/accessibility.html Список подсказок и советов по разработке универсально доступных Web-страниц. □ www.chantinc.com/technology Страница представляет собой сайт Chant; подробное обсуждение технологии воспроизведения речи. Chant также предоставляет программное обеспечение речевого синтеза и воспроизведения речи. □ www.searchWebServices.com На данном сайте представлены определения и информация по нескольким темам, включая CallXML. Исчер- пывающее описание CallXML с отличиями от VoiceXML — другой технологии, разработанной в компании Voxeo. Ссылки на опубликованные статьи, рассматривающие CallXML. □ www.oasis-open.org/cover/callxmlv2.html Полный список тегов CallXML, дополненный описаниями каждого тега. Краткие примеры применения те- гов в различных программных приложениях. □ www.freedomscientific.com Henter-Joyce — подразделение корпорации Freedom Scientific, предоставляющей программные продукты для слабовидящих пользователей. Домашняя страница JAWS (Job Access with Sound). □ ww-3.ibm.com/able/ Домашняя страница корпорации IBM по вопросам доступности. Расширенная информация о продуктах IBM, их доступности для людей с ограниченными физическими возможностями, а также описание доступности аппаратных, программных средств и содержимого Web. □ www.w3.org/TR/voice-tts-regs На данном сайте изложены требования к языкам разметки при разметке программ речевого синтеза. □ www.cast.org CAST (центр прикладной технологии) предлагает программные средства, включая программу контроля дос- тупности в помощь людям с физическими недостатками при работе с компьютерами. Программа контроля доступности осуществляет проверку доступности Web-сайтов. □ www.cs.cornell.edu/home/raman/emacspeak/emacspeak.html Сайт Emacspeak — экранного интерфейса, повышающего качество доступа в Интернет пользователей с ог- раниченными физическими возможностями.
Обеспечение доступности программных приложений 831 i 21.17.3. Информация о физических недостатках □ deafness.about.com/health/deafness/msubmenu6.htm Домашняя страница сайта deafness.about.com. Представлен большой объем информации об истории глухо- ты, текущее состояние медицинских исследований, а также другие ресурсы по данной теме. □ www.trainingpost.org/3-2-insthtm Учебные материалы по Gunning Fog Index — методу классификации текста по его удобочитаемости. □ laurence.canlearn.ca/English/learn/accessibility2001/neads/index.shtmI Аббревиатура INDIE расшифровывается как Integrated Network of Disability Information and Education (интег- рированная сеть информации и учебных материалов по физическим недостаткам). Данный сайт является до- машней страницей механизма поиска информации по различным физическим недостаткам.
ГЛАВА 22 Mobile Internet Toolkit С момента сотворения мира было предопределено, что определенные приметы предсказывают определенные события. Марк Туллий Цицерон Не обращайте внимания на моду! В двадцать раз лучше, когда человек чувствует собственный стиль. Маргарет Олифант Темы данной главы: □ знакомство с Mobile Internet Toolkit (MIT); □ использование ASP.NET и C# для создания мобильного Web-содержимого; □ мобильные элементы управления Web; □ создание файлов фонового кода; □ создание сценариев для мобильных Web-форм; □ использование таблиц стилей и шаблонов в Web-форме; О доступ к Web-службам из мобильных программных приложений. 22.1. Введение Беспроводные технологии обеспечивают пользователей во всем мире устойчивой связью и доступом в Интер- нет. Беспроводная связь оказывает самое непосредственное влияние на многие аспекты общественного разви- тия, включая управление бизнесом и деловыми операциями, повышение производительности труда сотрудников компаний, покупательское поведение заказчиков, маркетинговые стратегии и персональное взаимодействие. Помимо Web- и Windows-приложений программистам необходимо разрабатывать программы для беспровод- ных платформ. В данной главе представлен Mobile Internet Toolkit (MIT)1 от корпорации Microsoft, обеспечивающий разработ- ку мобильных Web-приложений. MIT значительно расширяет функциональность Visual Studio .NET и использу- ет .NET Framework для создания Web-содержимого мобильных устройств на таких языках, как C# и Visual Basic .NET. В процессе создания беспроводных программных приложений с помощью ASP.NET, XML и C# в главе подробно рассматривается MIT, включая такие аспекты, как обработка пользовательских входных данных, ото- бражение графики, доступ к Web-службам, мобильный Web-дизайн, а также обеспечение доступности Web- страниц для многочисленных устройств, поддерживающих MIT. 22.2. Клиентские устройства MIT В 1997 году ведущими компаниями-производителями сотовых телефонов — Nokia, Ericsson, Motorola и др. — был разработан специальный протокол беспроводных приложений (Wireless Application Protocol, WAP) для уп- рощения обеспечения и стандартизации беспроводного доступа в Интернет. WAP представляет собой набор протоколов связи, предназначенных для доступа в Интернет с помощью беспроводных устройств. 1 Инструкции по установке программных пакетов Mobile Internet Toolkit 1.0, Microsoft Mobile Explorer (MME) 3.0 и Pocket PC 2002 имеются на странице Downloads/Resources по адресу www.deitel.com.
Mobile Internet Toolkit 833 Язык Wireless Markup Language (WML, язык разметки для беспроводных систем), являющийся словарем XML, представляет собой язык разметки для описания содержимого для WAP-устройств. Микробраузеры — браузеры с ограниченной пропускной способностью и памятью — могут осуществлять доступ в Web посредством бес- проводного доступа в Интернет. Для передачи содержимого на микробраузеры протокол WAP поддерживает язык WML. Технологии WAP и WML имеют как сторонников, так и противников. Протокол WAP рассматривается как кратковременное решение для обеспечения беспроводного доступа в Интернет. Противники WAP ссылаются на различные недостатки, связанные с самим протоколом, включая появление слабых мест в системе защиты, ог- раниченную пропускную способность и ненадежность1. Другим упоминаемым недостатком является то, что стандарт WAP был достаточно "небрежно" реализован для разных микробраузеров, имеющихся на рынке. Это означает, что определенные микробраузеры поддерживают только небольшой набор тегов WML, и, следова- тельно, их функциональность ограничена. Совет по повышению переносимости________________________________________________________ Разные микробраузеры WAP могут иметь несогласованные реализации WML. Этим затрудняется равномерное использование WML в многочисленных микропроцессорах WAP. Скорость расширения сообщества приверженцев беспроводной связи привела к разработке других стандартов, некоторые из которых будут рассмотрены далее. Многообразие стандартов значительно осложнило жизнь соз- дателей беспроводных приложений. Имея дело с сонмом клиентов, пытающихся взаимодействовать между со- бой посредством несовместимых технологий, разработчикам весьма сложно создать универсальную программу, которая бы работала одинаково стабильно на разнородных беспроводных устройствах. В данной главе пред- ставлена технология MIT, помогающая программистам управлять такими устройствами. Перед началом обсуж- дения функций MIT кратко рассмотрим типы потенциально целевых устройств для MIT. MIT поддерживает клиентов трех основных типов: устройства HTML, устройства WAP и устройства iMode. Устройства HTML отображают разметку HTML, например, Internet Explorer (IE), Pocket PC и браузеры на базе Palm. WAP-устройства поддерживают микробраузеры WAP, визуализирующие разметку WML. В число WAP- устройств входят сотовые телефоны, оснащенные микробраузерами WAP от Nokia или Openwave. Взаимосвязь между браузерами WAP и WML напоминает взаимосвязь между IE и HTML. Проектирование WML отвечает потребностям небольших устройств с маленьким объемом памяти и ограниченным разрешением дисплея. Наконец, устройства iMode поддерживают содержимое формата cHTML (compact Hypertext Markup Language, сжатый язык гипертекстовой разметки). cHTML соответствует подмножеству спецификации HTML 2.0. В отли- чие от HTML, cHTML не поддерживает карта изображений, таблицы, фреймы или изображения в формате JPEG. Страницы cHTML не могут иметь фоновою цвета или изображения. Использовать с cHTML таблицы стилей (например, CSS) и сценарии (например, JavaScript) нельзя. При рассмотрении мобильных видов связи важно понимать, что процесс, по которому мобильные устройства осуществляют связь и взаимодействие с Интернетом, — разный для каждого типа приложения. Например, при- ложения WAP взаимодействуют со шлюзом WAP с помощью WAP, а шлюз WAP взаимодействует с Web- сервером посредством протокола HTTP; приложения iMode взаимодействуют с Web-сервером напрямую по HTTP. MIT снимает задачу понимания различных протоколов и языков. Вместо создания разметки WML для устройств WAP, разметки cHTML для устройств iMode и разметки HTML для устройств HTML, разработчик может создать всего одно приложение, генерирующее нужную разметку для множества различных устройств. Список портативных устройств, поддерживаемых MIT, представлен на сайте msdn.imcrosoftcom/vstudio /device/mitdevices.asp. 22.3. Введение в понятия MIT и мобильной Web-формы MIT расширяет функциональность ASP.NET (см. главу 17) для включения мобильных клиентов. При запросе входным устройством (т. е. мобильным устройством) ASPX-страницы MIT генерирует HTML для IE 6.0 и , Pocket IE, cHTML для^Клиентов iMode и WML для клиентов WAP. При создании мобильного Web-приложения в Visual Studio .NET генерируется страница ASPX (файл с расши- рением aspx), содержащий разметку для пользовательского интерфейса страницы. Бизнес-логика (т. е. обработ- чики событий кнопок) приложения может встраиваться в файл ASPX в виде сценария, либо в файл фонового кода (code-behind file). Бизнес-логика приложения исполняется на Web-сервере. В данной главе используют оба метода включения кода. 1 Подробная информация о WAP размещена на Web-сайте форума WAP: www.wapforum.org. 53 Зак. 3333
834 Гпава 22 Замечание по технологии программирования_________________________________________________ Большинство разработчиков использует файл фонового кода, особенно когда приложение содержит значитель- ный объем кода. Применение такого файла упрощает задачу сопровождения, отладки и модификации программ, потому что интерфейс отделен от реализации. В данном разделе представлены два мобильных Web-приложения, и вместе с читателями авторы рассматривают шаги, необходимые для их создания. В листинге 22.1 представлена Web-страница welcome.aspx. Разметка для нее сгенерирована Visual Studio .NET при создании пользовательского интерфейса в режиме проектирования. Далее с этой разметкой будут выполнены определенные манипуляции для включения кода C# и аппаратной функциональности. В строках 6 и 7 используется директива Раде для задания языка (посредством атрибута Language), на котором пишутся файлы фонового кода для C# и для Welcome.aspx.cs (атрибут CodeBehind). Под- робно данный файл фонового кода (листинг 22.2) рассматривается далее. Атрибут inherits указывает класс, из которого наследует страница. В приведенном примере наследование осуществляется из класса welcome.WelcomePage, определенного в файле фонового. Г ' ...... • ’’ .г-. .. ......г---- W ....... Листинг 22.1. Множественные формы в странице мобильной Web-формы I. •' . j - • < S . ' - ' И • ' ' ’ ” - . - .< ” ......... ... . ... ..... — ........................Л.... . .. ..... .. ....Л. .. • ... • 1 <%— Листинг 22.1: Welcome.aspx —%> 2 <%— Простая Web-форма —%> 3 4 <%— .директива задает файл, в котором сохранен код, —%> 5 <%— и язык программирования, на котором написан код —%> 6 <@ Page language3”с#" Codebehind="Weicome.aspx.сз" 7 Inherits=”Welcome.WelcomePage" AutoEventWireup="false" %> 8 9 <@ Register TagPrefix="mobile" 10 Namespace="System.Web.UI.MobileControls" 11 Assembly="System.Web.Mobile, Version=l.0.3300.0, Culture=neutral, PublicKeyToken=b03f5f7flld50a3a" %> 12 13 <meta name="GENERATOR" content="Microsoft Visual Studio 7.0"> 14 <meta name="CODE_LANGUAGE" content="C#"> 15 <meta name="vs_targetSchema" 16 content="http://schemas.microsoft.com/Mobile/Page> 17 18 <body Xmlns :mobile="http: //schemas .microsoft. com/Mobile/WebForm"> 19 ’ 20 <mobile:Form id="startForm" runat="server"> 21 22 <mobile:Label id="startLabel" runat=,,server"> 23 Click Start! 24 </mobile: Label> 25 26 <%— Элемент управления командой —%> 27 <%— при активизации вызывается метод resultForm_Activate —%> 28 <mobile:Command id="startCommand" runat="server"> 29 Start 30 </mobile:Command> 31 </mobile:Form> 32 33 <%— Форма resultForm —%> 34 <%— при активизации вызывается метод resultFormActivate —%> 35 <mobile:Form id3"resultForm" runat="server"> 36 <mobile: Label id="resultLabel;" runat="server"x/mobile:Label> 37 </mobile:Form> 38 39 </body> Директива Register (строки 9—И) задает пространство имен и компоновочный блок, к которому принадлежат элементы управления данной мобильной Web-формой, а также префикс тега, квалифицирующий имена тегов мобильных элементов управления. Префикс тега присваивается атрибуту Tagprefix директивы Register. Значе- ние по умолчанию данного атрибута— mobile. Значение атрибута Namespace (System.Web.UI.MobileControls)
Mobile Internet Toolkit 835 указывает, что любой элемент управления с префиксом mobile определяется в пространстве имен System.Web.Ui.MobileControls. Значение атрибута Assembly (System.Web.Mobile) задает библиотечный файл, в котором размещены мобильные классы библиотеки FCL. Мобильные элементы управления Web-формой (или мобильные элементы управления} представляют собой объ- екты, используемые для создания мобильных программных приложений. В число основных мобильных элемен- тов управления входят метки и кнопки. Открывающий тег каждого мобильного элемента управления имеет ат- рибут runat, имеющий значение "server" для указания, что данный элемент управления должен исполняться на сервере. В строках 20—31 определяется первый мобильный элемент управления — Form. Значение атрибута id (строка 20) предоставляет программный идентификатор для элемента управления. В данном случае выбран идентификатор start^orm, потому что это — первая форма, отображенная для мобильных клиентов. Посредст- вом свойства (ID) программисты могут присвоить атрибуту id практически любое значение, при условии, что оно является допустимым идентификатором С#. Программисты получают доступ к этому элементу управления и могут манипулировать им в файлах фонового кода с помощью идентификатора startForm. Данная форма содержит элементы управления Label и Control. Подобно элементам Label Web-формы; мобиль- ные элементы Label отображают текстовую информацию. При установке свойства Text элемента Label на click start!, и свойства (ID) — на startLabel в режиме проектирования, в разметке создается элемент управления Label: <mobile:Label id="startLabel" runat="server"> Click Start! </mobile:Label> Замечания о "впечатлениях и ощущениях"_____________________________________________________ В режиме проектирования элементы управления Command выглядят как кнопки. Однако некоторые микробраузе- ры, например Openwave, визуализирует элементы управления Command в виде гиперссылок. При щелчке пользователем кнопкой мыши на элементе управления Command вызывается метод startcommand ci ick, обрабатывающий событие Click. Данный метод определяется в файле фонового кода (листинг 22.2). Об- ратите внимание, что в элемент Command визуализируется под элементом Label (рис. 22.1). В режиме проектиро- вания элементы по умолчанию размещаются форме друг под другом. Далее будут рассмотрены способы изме- нения расположения элементов управления формой. В большинстве мобильных устройств размеры экрана ог- раничены, поэтому содержимое, как правило, делится на несколько форм, что устраняет необходимость прокрутки страницы. В строках 35—37 листинга 22.1 представлена вторая форма, эквивалентная другой Web-странице. Когда данная форма становится активной (т. е. отображается), вызывается метод resultForm Activate файла фонового кода для обработки события Activate. Примечание_________________________________________________________________________________ Отображаемая форма называется активной. В некоторый момент времени активной может быть только одна форма. _ у Распространенная ошибка программирования___________________________________________________ Страницы Web-форм должны содержать минимум одну форму. Результатом появления страницы без формы является ошибка компиляции В листинге 22.2 представлен файл фонового кода welcome.aspx.cs для файла ASPX из листинга 22.1. В стро- ке 18 добавляется ссылка на пространство имен System.Web.UI.MobileControls, включающее базовый класс MobilePage. Он необходим для мобильных Web-форм. В строке 24 открывается класс WelcomePage, являющийся базовым классом, заданным в строке 7 листинга 22.1. В строках 46—52 определяется метод startCommand_ Click, управляющий событием Click, имеющим место при нажатии кнопки Start. В строке 50 атрибут resultForm задается как активная форма его присвоением свойству ActiveForm. Формы должны активизиро- ваться таким образом потому, что ASPX-файлы способны содержать несколько форм, но только одна из них может быть активной. Метод resultForm Activate (строки 55—62) присваивает в строках 59 и 60 свойству Text атрибута resuitLabei значение приветственного сообщения. В данном примере текст, отображенный атрибутом resuitLabei, — "Welcome to the Microsoft Mobile Internet Toolkit!". Мобильный проводник Microsoft (Microsoft Mobile Explorer, MME) представляет собой телефонный эмулятор, имеющий две программируемые клавиши (рис. 22.1). Эти клавиши обеспечивают взаимодействие пользователя с приложением. Например, на рис. 22.1, в показана выделенная (подсвеченная) кнопка Start и левая программи- руемая клавиша, отображающая ОК. При нажатии левой программируемой клавиши нажимается кнопка Start. Клавиши прокрутки обеспечивают просмотр пользователями Web-страниц.
836 Гпава 22 Листинг 22.2. Файл фонового кода для мобильного приложения ’ £gr' 4» 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 // Листинг 22.2: Welcome.aspx.cs // Фоновый файл для файла Welcome.aspx using System; using System.Collections; using System.ComponentModel; using System.Data; using System.Drawing; // содержит классы, управляющие клиентскими запросами и // ответами сервера using System.Web; using System.Web.Mobile; using System.Web.Sessionstate; // определения для графических элементов управления в Web-формах using System.Web.UI; using System.Web.UI.MobileControls; using System.Web.UI.WebControls; using System.Web.UI.HtmlControls; namespace Welcome { public class WelcomePage : System.Web.UI.MobileControls.MobilePage { // формы protected System.Web.UI.MobileControls.Form startForm; protected System.Web.UI.MobileControls.Form resultForm; // метки protected System.Web.UI.MobileControls.uabel startLabel; protected System.Web.UI.MobileControls.Label resultLabel; // кнопка protected System.Web.UI.MobileControls.Command startcommand; private void Page_Load(object sender, System.EventArgs e) { // Ввод пользовательского кода для инициализации страницы } // код, сгенерированный Visual Studio .NET // resultForm — активная форма при нажатии кнопки Start private void startCommandClick( object sender, System.EventArgs e) { // изменение текущей формы на resultForm ActiveForm = resultForm; } // конец startcommand Click // отображение текста при активизации resultForm private void resultForm_Activate( object sender, System.EventArgs e) { // установка значения resultLabel.Text = "Welcome to the Microsoft Mobile Internet Toolkit!";
Mobile Internet Toolkit 837 61 62 63 64 65 66 } // конец resultForm_Activate } // конец класса WelcomePage I // конец пространства имен Welcome б Рис. 22.1. Демонстрация работы мобильного приложения: а — форма в Internet Explorer; б — сообщение после нажатия кнопки Start; в — ММЕ; г — сообщение после нажатия кнопки Start в ММЕ Обратите внимание, что представлен ASPX-файл и файл фонового кода1, т. е. описан процесс создания данного приложения. Для его создания выполните следующие шаги:1 2 1. Создание проекта. Откройте Visual Studio .NET и выберите File | New | Project... для открытия окна New Project (рис. 22.2). В левой части окна выберите папку Visual C# Projects, а в правой — Mobile Web Application3. Обратите внимание, что поле имени проекта заблокировано. Вместо использования этого поля название и местоположение проекта указывается в поле Location. Необходимо, чтобы проект был размещен в http://localhost, являющимся URL для корневого каталога IIS (обычно c:\InetPub\wwwroot). Имя localhost указывает на то, что клиент и сервер размещены на одной машине. Если бы серверное приложение было размещено на другой машине, тогда имя localhost было бы замещено соответствующим IP-адресом или именем хоста. Visual Studio .NET по умолчанию присваивает проекту имя MobileWebApplicationl, которое мы изменили на welcome. Ниже поля Location отображается описание "Project will be created at 1 Для запуска примеров данной главы, представленных на сайте www.deitel.com, необходимо создать в IIS виртуальный каталог. Инструкции по созданию виртуального каталога имеются в ссылке Downloads/Resources сайта www.deitel.com. 2 Для успешного создания проекта необходим запуск IIS (информационный сервер Интернета). Данный сервер запускается файлом inetmgr.exe, щелчком правой кнопки мыши на строке Default Web Site и выбором команды Start. Для отображения Default Web Site может потребоваться развертывание узла, представляющего компьютер. 3 Проект Mobile Web Application не появится в диалоговом окне New Project до установки МГГ.
838 Гпава 22 http://localhost/Welcome". Этим указывается, что папка проекта размещена в корневом каталоге на Web- сервере При нажатии кнопки ОК создается проект и виртуальный каталог, связанный с папкой проекта. По- сле этого открывается диалоговое окно Create New Web (рис. 22.2), a Visual Studio .NET создает Web-сайт на сервере. * Рис. 22.2. Создание Mobile Web Application в Visual Studio .NET Рис. 22.3. Visual Studio NET создает виртуальный каталог для папки проекта Welcome и связывает его с ней ASPX-файл Файл фонового кода Показать все файлы Рис. 22.4. Окно Solution Explorer для проекта Welcome 2. Просмотр вновь созданного проекта. На следующих рисунках представлено содержимое проекта: начнем с окна Solution Explorer (рис. 22.4). При создании нового проекта Mobile Web Application Visual Studio .NET создает несколько файлов. MobileWebForml.aspx— мобильная Web-форма, включающая файл фонового кода. Для просмотра файла фонового кода для ASPX-файла щелкните правой кнопкой мыши на ASPX-файле и выберите команду View Code. Либо можно щелкнуть кнопкой мыши на значке для отображения всех фай- лов, после чего развернуть узел страницы ASPX, как показано на рис. 22.4. На рис. 22.5 показан Mobile Web Form Designer для ASPX-страницы MobileWebForml.aspx в режиме проек- тирования. Он состоит из формы, на которую программисты перетягивают элементы управления (метки и кнопки) из панели Toolbox Форма Вкладка HTML L Вкладка Design Рис. 22.5. Режим проектирования Mobile Web Form Designer
Mobile Internet Toolkit 839 На рис. 22.6 показан Mobile Web Form Designer в режиме HTML, который позволяет просматривать размет- ку, представляющую пользовательский интерфейс. Разработчик может перейти в режим HTML щелчком кнопкой мыши на вкладке HTML в левом нижнем углу Mobile Web Form Designer. При щелчке по вкладке Design (слева от вкладки HTML) пользователь возвращает в режим проектирования. Рис. 22.6. Режим HTML Mobile Web Form Designer На рис. 22.7 показан файл фонового кода MobileWebForml.aspx.cs для файла MobileWebForml.aspx. Следует помнить, что Visual Studio .NET генерирует данный фоновый файл при создании проекта; в целях наглядно- сти авторы переформатировали содержимое фонового файла. Рис. 22.7. Файл фонового кода для файла MobileWebForml.aspx, созданный Visual Studio .NET
840 Глава 22 3. Переименование файла ASPX. Итак, мы показали содержимое файла ASPX и файла фонового кода. Теперь переименуем эти файлы. Щелкните правой кнопкой мыши на файле ASPX в окне Solution Explorer и выбе- рите команду Rename. Введите новое имя файла и нажмите клавишу <Enter>. При этом имя файла ASPX и файла фонового кода обновятся. В данном примере использовано имя Welcome.aspx. 4. Проектирование страницы. Процесс проектирования мобильной Web-формы — такой же, как и процесс про- ектирования простой Web-формы. Для добавления на страницу мобильных элементов управления перетяни- те их из панели Toolbox на мобильную Web-форму. Подобно самой Web-форме, каждый элемент управления представляет собой объект, обладающий свойствами, методами и событиями. Программисты могут задавать свойства и события в окне Properties. Для просмотра свойства мобильной Web-формы выберите объект Document в раскрывающемся списке окна Properties. На рис. 22.8 представлен набор элементов управления Mobile Web Forms в панели элементов. Многие из них аналогичны элементам управления Windows и Web, представленным ранее. .. AdRotator Рис. 22.8. Группа Mobile Web Forms в панели Toolbox Fy RequiredFieldValdatOr brnoaw atoato- . kange«aidator § Г*} ReaiferExpresstorValidatcr. ' ^.CustomVakdator'S й ? Data’I Рис. 22.9. Файл Welcome.aspx после добавления двух элементов Label и задания их свойств — i_Щ__/4 Свойству (ID) формы устанавливается значение startForm. После этого в форму добавляются элементы Label и Command. Свойство (ID) элемента Label устанавливается на startLabel, а свойство Text — на Click start!. Обратите внимание, что элемент Command появляется под элементом Label. В Mobile Web Form Designer элементы управления располагаются друг под другом — вертикально. Изменение визуализации формы клиентом осуществляется с помощью, например, свойства BreakAfter (см. далее). Изменением зна- чения свойства в режиме проектирования меняется соответствующая этому элементу разметка в документе ASPX. Теперь добавим вторую форму, свойство (ID) которой установим на resuitForm, и добавим Label. Свойство (ID) элемента Label установлено на resuitLabei. В режиме проектирования значение по умолчанию для свойства Text элемента resuitLabei удаляется, потому что значение свойства Text задается программным способом в файле фонового кода. Если свойство Text элемента Label не содержит текста, тогда название элемента Label отображается в квадратных скобках в Mobile Web Form Designer. Во время исполнения при- ложения данный текст не виден. Результирующая страница ASPX показана на рис. 22.9. 5. Добавление логики страницы. После того как спроектирован пользовательский интерфейс, в файл фонового кода можно ввести код С#. В данном примере строки 46—62 листинга 22.2 добавляются в файл фонового кода. При нажатии командной кнопки в startForm вызывается метод startCommand_click (строки 46—52) для обработки события Click. Оператор в рамках этого метода делает resuitForm активной, в результате че- го вызывается метод resulForm Activate (строки 55—62). Данный метод задает и отображает содержимое resuitLabei.
Mobile Internet Toolkit 841 6. Запуск программы1. Для построения решения выберите команды Build | Build Solution. После этого выбери- те File | Browse With.... В списке браузеров в левой части диалогового окна отметьте строку Microsoft Mo- bile Explorer Emulator и нажмите кнопку Browse.... Откроется окно ММЕ и загрузится Web-страница (файл ASPX). Обратите внимание на URL: http://localhostAVelcomeAVelcome.aspx (см. листинг 22.2). Он указыва- ет на то, что файл ASPX размещен й каталоге Welcome. После создания мобильной Web-формы программист может просмотреть ее открытием ММЕ вне среды Visual Studio .NET и вводом URL Web-страницы в комбинированном окне эмулятора. При тестировании приложения ASP.NET на одном компьютере введите http://\oca\host/ProjectFolder/PageName.aspx, где ProjectFolder — папка, в которой размещена страница (обычно с именем проекта), a PageName — название страницы ASP.NET. В следующем примере (листинг 21.3) разрабатывается механизм расчета чаевых на базе суммы в долларах и процента чаевых. Здесь также демонстрируется процесс внедрения сценария C# в файл ASPX. Сценарий начи- нается в строке 18 с тега <script>, где указывается язык (посредством атрибута Language), используемый в сце- нарии. В строках 21—38 определяется метод calculateCommand_dick, рассчитывающий сумму чаевых на осно- ве введенной пользователем информации о проценте чаевых. Данный метод вызывается при нажатии пользова- телем кнопки Calculate Tip. Информация вводится в два элемента TextBox (строки 61—62 и 75—76). Примечание______________________________________________________________________________________ Для простоты введенные пользователем данные не проверяются на наличие в них цифровых значений. Под каждым элементом TextBox введен контролирующий элемент управления. Эти элементы управления про- веряют введенные пользователем данные. Например, если пользователь вводит некорректную информацию или упустил нужную, то большинство приложений работать не будет, потому что введенная информация не соот- ветствует нужному формату, либо отсутствует. В данном примере используется элемент управления RequiredFieldValidator, обеспечивающий наличие данных в другом элементе при попытке пользователя пред- ставления страницы. В этом примере требуется, чтобы данные были введены в два текстовых поля, потому что для выполнения расчета необходимо знать процент чаевых и общую сумму. Первый из контролирующих эле- ментов управления (tipvalidator) определен в строках 64—68. Атрибут ControlToValidate (строка 65) опреде- ляет элемент, достоверность которого проверяется. В данном случае ревизор настроен на проверку текстового поля с именем tipTextBox. Если это текстовое поле не имеет данных при нажатии пользователем кнопки Calcu- late Tip, тогда RequredFieldValidator выдает сообщение об ошибке, заданное в свойстве Text (в данном случае "Please enter the tip percentage"). Поведение второго ревизора (строки 78—82) аналогично, но им проверяется достоверность текстового поля totalTextBox. При вызове метода calculateCommand_Click проверяется условие Page.isValid в строке 24. Данное условие определяет корректность страницы (т. е. ни один ревизор не обна- ружил ошибок). Если страница корректна, тогда исполнится оставшаяся часть метода calculateCommand_ciick. В противном случае (т. е. одно или оба текстовых поля пустые) страница перезагружается и генерируется одно или несколько сообщений об ошибках. ....................... ................ - •............................................. . .................. .............. ..... . Листинг 22.3. Web-форма со встроенным кодом C# ..............: ................... ;.......................................±....Ji»_____________________________________________________________*fc»...a..»L.; ».......................................................Л 1 <%— Листинг 22.3: ТLpCalculator.aspx —%> 2 <%— Web-форма для расчета чаевых —%> 3 4 <@ Page language="c#" Codebehind="TipCalculator.aspx.cs" 5 Inherits="TipCalculator.TipCalculator" 6 AutoEventWireup="false” %> 7 8 <@ Register TagPrefix="mobile" 9 Namespace="System.Web.UI.MobileControls" 10 Assembly="System.Web.Mobile, Version=l.0.3300.0, Culture=neutral, PublicKeyToken=b03f5f7flld50a3a" %> 11 12 <meta name="GENERATOR" content="Microsoft Visual Studio 7.0"> 13 <meta name="CODE_LANGUAGE" content="C#”> 14 <meta name="vs_targetSchema" 15 content="http://schemas.microsoft.com/Mobile/Page> 16 1 Перед выполнением данного шага необходимо установить ММЕ. Данный программный продукт можно загрузить с сайта msdn.microsoft.com/downloads/defaulLasp?url=/downloads/sample.asp?url=/msdn-files/027/001/706/msdncompositedoc.xmi
842 Глава 22 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 <%— встроенный сценарий, содержащий код C# —%> <script runat=,’server" language="C#’*> // расчет чаевых protected void calculateCommand_Click( object sender, System.EventArgs e) { if (Page.IsValid) ( decimal tip; string dollarAmount; tip « Decimal.Parse(totalTextBox.Text) * Decimal.Parse(tipTextBox.Text) / (decimal) 100; // изменение чаевых в сумму в долларах dollarAmount = St ring. Format ("{0:0", tip); , resultLabel.Text = "Tip Amount: " + dollarAmount; } } // конец calculateCommand_Click // очистить форму protected void clearConmand_Click( object sender, System. EventArgs e) tipTextBox.Text = ""; totalTextBox.Text = ""; resultLabel.Text - } // конец clearCommand_Click </script> <body Xmlns:mobile="http://schemas .microsoft. com/Mobile/WebForm> <mobile: Form id ="tipCalculatorForm" runat="server"> ^mobile:Label id="instructionLabell" xunat="server" BreakAfter«"False"> Enter Tip Percentage'^ — </mobile:Label> <mobile:TextBox id="tipTextBox" runat="server"> </mobile:TextBox> <mobile:RequiredFieldValidator id="tipValidator" runat®"server" ControlToValidate="tipTextBox" ErrorMessage="RequiredFieldValidator"> Please enter the tip percentage </mobile:RequiredFieldValidator> <mobile:Label id="instructionLabel2" runat="server" <BreakAfter="False"> Enter Total Amount: </mobile:Label> <mobile:TextBox id="totalTextBox" runat="server"> </mobile:TextBox> <mobile:RequiredFieldValidator id="totalValidator" runat®"server" ControlToValidate®"totalTextBox" ErrorMessage="RequiredFieldValidator">
Mobile Internet Toolkit 843 81- Please enter the total amount 82 </mobile:RequiredFieldValidator> 83 84 <mobile:Label id="resultLabel" runat="server"> 85 </mobile:Label> 86 87 <mobile:Command id="calculateCommand" 88 OnClick="calculateCommand_Click" runat="server" 89 BreakAfter="False"> 90 Calculate Tip 91 </mobile:Command> 92 93 <mobile:Command id="clearCommand" runat="server" 94 OnClick="clearCommand_Click"> 95 Clear 96 </mobile:Command> 97 98 </mobile:Form> <%— конец формы tipCalculatorForm —%> 99 100 </body> Результат представлен на рис. 22.10. Рис. 22.10. Расчет чаевых (изображения предоставлены Openwave Systemsjnc.): а — ввод процента чаевых в IE; б _ ВВод общей суммы; в — результат; г — ввод процента чаевых в симуляторе; д — ввод общей суммы; е — результат
844 Гпава 22 Как только метод calculateCommand_click рассчитывает и отображает сумму чаевых, пользователь получает возможность ввода новых значений. Однако обратите внимание, что существуют различия между тем, как дан- ная программа визуализируется в браузере IE и в браузере Openwave. В последующих примерах будет проде- монстрирован механизм написания программ, выполняющих различные операции, в зависимости от выбранно- го браузера. Например, в IE текстовые поля отображаются рядом с соответствующими им метками, тогда как в браузере Openwave они отображаются на разных строках. Присвоением свойству BreakAfter элемента управле- ния значения False элементы можно разместить на одной строке. MIT определяет, поместится элемент управ- ления на той же строке или его придется разместить на следующей строке изменением устройства размера эк- рана. Информация о размере экрана каждого устройства, а также о поддержке им цветной графики и способно- сти к созданию телефонного вызова сохранена в файле machine.config. Например, в листинге 22.4 представлена часть файла machine. config с перечислением функций, поддерживаемых ММЕ. Разметка в листинге 22.4 ука- зывает, что ММЕ поддерживает разметку HTML (строка 6) и изображения в формате GIF (строка 7). Файл machine.config размещен в каталоге C:\Windows\Microsoft.NET\Framework\v1.0.3705\Config. Примечание_______________________________________________________________________________________ Номер версии (v1.0.3705) может изменяться. ........................----——------------------------------------------------------------------—........................................—------—-----------............................— Листинг 22.4. Часть файла machine.config 4 - , -г , > _______________________________________________________________________________________..“_____________________?...._лй,____:___ 1 <%— Возможности HTML-браузеров HTML ММЕ —%> 2 <filter 3 match»".+" 4 with="${httpRequest}"> 5 preferredRenderingType = "html32" 6 preferredRenderingMime = "text/html" 7 preferredlmagMime = "image/gif" 8 supportsImageSubmit = "true" 9 supportsBold = "true” 10 supportsltalic = "true" 11 supportsFontSize = "true" 12 supportsFontName = "true" 13 supportsFontColor = "true" 14 supportsBodyColor = "true" 15 supportsDivAlign = "true" 16 supportsDivNoWrap = "true" 17 </filter> 22.4. Усовершенствованные элементы управления мобильными Web-формами В данном разделе рассматривается более сложный пример — популярная игра в кости. Ее принцип заключается в том, что игрок выигрывает или проигрывает, в зависимости от того, как "ляжет" пара брошенных им костей. Основные правила игры представлены как часть примера. В листингах 22.5 и 22.6 представлены ASPX-файл и файл фонового кода соответственно. Страница ASPX (листинг 22.5) содержит две формы: в одной описаны правила игры (rulesForm), а в другой представлен пользовательский интерфейс (gameForm). Инструкции формы rulesForm отображены в элементе управления Label. j Листинг 22.5. Страница ASPX для мобильной программы игры в кости f .. * ... .< .. . ... . .... .. 1 <%— Листинг 22.5: Craps.aspx —%> 2 <%— Интерактивная мобильная игра в кости —%> 3 4 <@ Page language="c#" Codebehind="Craps.aspx.cs" 5 Inherits="Craps.CrapsGame" AutoEventWireup="false" %> 6 7 <@ Register TagPrefix="mobile" 8 Namespace="System.Web.UI.MobileControls" 9 Assembly="System.Web.Mobile, Version=l.0.3300.0, 9a Culture=neutral, PublicKeyToken=b03f5f7flld50a3a" %> 10
Mobile Internet Toolkit 845 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 <meta content="Microsoft Visual Studio 7.0" namee"GENERATOR"> <meta content="C#" name="CODE_LANGUAGE"> <meta content="http://schemas.microsoft.com/Mobile/Page" name="vs__targetSchema"> <body Xmlns :mobile="http: //schemas .microsoft.com/Mobile/WebForm"> <mobile:Form id="rulesForm" runat="server"> , <%— метка, выровненная по центру —%> <mobile:Label id="titleLabel" runat="server" Alignment="Center"> —Craps Rules— </mobile:Label> < %— правила игры для пользователя —%> <mobile:Label id ="rulesLabel" runat="server"> A player rolls two dice. Each die has six faces. These faces contain 1, 2, 3, 4, 5 and 6 spots. After the dice have come to rest, the sum of the spots on the two upward faces is calculated. If the sum is 7 or 11 on the first throw, the player wins. If the sum is 2 (snake eyes), 3 (trey) or 12 (boxcars) oh the first throw (called craps), the player loses (i.e. the house wins). If the sum is 4, 5, 6, 8, 9 or 10 on the first throw, then that sum becomes the player's 'point'. To win, you must continue rolling the dice until you make your point. The player loses by rolling a 7 before making the point. </mobile:Label> <mobile:Command is="newGameCommand" runat="server"> New Game </mobile:Command> </mobile:form> <%— end rulesForm —%> <mobile:Form id="gameForm" runat»"server"> statusLabel отображает 'point' или —%> < %— сообщение о выигрыше или проигрыше пользователя —%> <mobile:Label id="statusLabel" runat="server"> </mobile:Label> < %— объект Panel группирует Label и два Images —%> cmobile:Panel id="dicePanel" runat="server"> <mobile:Label id="rollValueLabel" runat="server"> </mobile:Label> <mobile:Image id="firstImage" runat="server" BreakAfter="False"> </mobile:Image> <mobile:Image id="secondlmage" runat="server">' </mobile:Image> </mobile:Panel> <mobile:Command id="playCommand" runat="server" BreakAfter="False"> Play < /mobile:Command>
846 Глава 22 14 <mobile:Command id="rollCommand" runat="server" 75 BreakAfter="False"> 76 Roll Again 77 </mobile:Command> 78 79 «mobile:Command id="quitCommand" runat="server"> 80 Quit 81 </mobile:Command> 82 83 «/mobile:Form> <%— конец gameForm —%> 84 85 </body> Вторая форма (gameForm) активизируется при начале пользователем новой игры. В объекте statusLabel формы gameForm (строка 53) отображается информация о состоянии текущей игры. В строках 57—67 вводятся два но- вых элемента управления: Panel и Image. Элемент Panel группирует связанные между собой элементы. При присвоении свойству Visible элемента Panel значения false каждый элемент панели Panel скрывается. В дан- ном примере элемент Panel (dicePanel) содержит элемент управления Label (rollValueLabel) и два элемента управления Image (firstlmage и secondlmage). Элемент rollValueLabel выводит результат броска играющего, а два элемента image отображают кости, соответствующие броску пользователя. Здесь же имеются кнопки, даю- щие пользователю возможность продолжать игру, еще раз бросить кости или выйти из игры (строки 69—81). В листинге 22.6 представлен файл фонового кода (Craps.aspx.cs). (Результат работы приложения представлен на рис. 22.11.) Данный файл определяет несколько методов, в ТОМ числе newGameCoirauand_Click, gameForm_Activate, playCommand_Click, Roll_Click, quitCommand_Click и RollDice. При нажатии кнопки New Game на странице правил игры вызывается метод newGameCommand_Click (строки 72—80), определяющий .форму gameForm (стро- ка 75) как активную и присваивающий свойству visible (строка 78) объекта dicePanel значение false. *л - — •' —•••:• •. •. - » — Листинг 22,6 Файл Ценового кора для игры в кости L................. „'..-.-их; ..............*...'-...---. 1 // Листинг 22.6: Craps.aspx.cs 2 // Файл фонового кода для приложения игры в кости 3 4 using System; 5 using System.Collections; 6 using System.ComponentModel; 7 using System.Data; 8 using System.Drawinf; 9 using System.Web; 10 using System.Web.Mobile; 11 using System.Web.Sessionstate; 12 using System.Web.UI; 13 using System.Web.UI.MobileControls 14 using System.Web.UI.WebControls; 15 using System.Web.UI.HtmlControls; 16 17 namespace Craps 18 { 19 /// <summary> 20 /// Краткое описание игры CrapsGame 21 /// </summary> 22 'public class CrapsGame : 23 System.Web.UI.MobileControls.MobilePage 24 { 25 // первая форма 26 protected System.Web.UI.MobileControls.Form rulesForm; 27 28 // элементы управления в форме rulesForm 29 protected System.Web.UI.MobileControls.Label titleLabel; 30 protected System.Web.UI.MobileControls.Label rulesLabel; 31 protected System.Web.UI.MobileControls.Command 32 newGameCommand; 33
Mobile Internet Toolkit 847 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 ’ 85 86 87 88 89 90 91 92 93 94 95 11 вторая форма protected System.Web.UI.MobileControls.Form gameForm; // элементы управления на форме gameForm protected System.Web.UI.MobileControls.Label statusLabel; protected System.Web.UI.MobileControls.Panel dicePanel; protected System.Web.UI.MobileControls.Image firstlmage; protected System.Web.UI.MobileControls.Image secondlmage; protected System.Web.UI.MobileControls.Command playCommand; protected System.Web.UI.MobileControls.Command rollCommand; protected System.Web.UI.MobileControls.Command quitcommand; protected System.Web.UI.MobileControls.Label rollValueLabel; * private void Page_Load(object sender, System.EventArgs e) { // вставка пользовательского кода для инициализации страницы } // код, сгенерированный Visual Studio .NET // специальные значения enum Names { SNAKE_EYES = 2, TREY = 3, YO__LEVEN = 11, BOX CARS » 12 }; private void gameForm_Activate( object sender, System.EventArgs e) { // пользователь может начать игру playCommand.Visible » true; playCommand.Text = "Play"; rollCommand.Visible = false; statusLabel.Text = "press Play to start!"; } // конец gameForm_Activate private void newGameCommandjClick( object sender, System.EventArgs e) { ActiveForm = gameForm; // игра не началась, кости не показаны dicePanel.Visible = false; } // конец newGameCommand Click private void playCommand_Click( object sender, System.EventArgs e) { int sum; statusLabel.Text = sum - RollDice(); // отображение костей для данного броска, точка сохранения dicepanel.Visible = true; Session.Add("myPoint", sum. Tostring()); // анализ первого броска switch (sum)
848 Глава 22 96 { 97 98 // пользователь выиграл 99 case 7: 100 case (int) Names.YO_LEVEN: 101 102 // игра окончена; пользователь не может бросать кости 103 rollCommand.Visible = false; 104 statusLabel.Text = "*YOU WIN!!!*"; 105 playCommand.Text = "Play Again"; 106 break; 107 108 // пользователь проиграл 109 case (int) Names.SNAKE_EYES: , 110 case (int) Names.TREY: 111 case (int) Names.BOX_CARS: 112 rollcommand.Visible = false; 113 statusLabel.Text = *’*YOU LOSE.*"; 114 playCommand.Text = "Play Again"; 115 break; 116 117 // пользователи продолжают игру 118 // при ничье 119 default: 120 Session! "myPoint" ] = sum.ToString(); 121 122 statusLabel.text * "Point is " + 123 Session! "myPoint" ] + 124 playCommand.Visible = false; 125 126 // пользователь может бросить кости 127 rollcommand.Visible = true; 128 break; 129 130 } // конец switch 131 132 } // конец playCommand_Click 133 134 private void rollCommand_Click( 135 object sender. System.EventArgs e) 136 { J. 137 int roll; 138 int point = 139 Int32.Parse(Session! "myPoint" ].Tostring()); 140 141 roll = RollDice(); 142 143 //• анализ текущего броска: если оба условия ложны, 144 // пользователь делает бросок еще раз 145 if (roll ~ point) 146 { 147 statusLabel.Text = "*YOU WIN!!!*"; 148 playConunand.Text = "Play Again"; 149 rollCommand.Visible = false; 150 playCommand.Visible = true; 151 } 152 else if (roll == 7) 153 { 154 statusLabel.Text = "*SORRY. YOU LOSE.*"; 155 playCommand.Text «• "Play Again"; 156 rollCommand.Visible = false; 157 playCommand.Visible = true; 158 ) 159 160 } // конец rollCommand_Click
Mobile Internet Toolkit 849 161 162 private void quitCommand_Click( 163 object sender, System.EventArgs e> 164 { 165 ActiveForm = rulesGorm; 166 167 } // конец quitCommand_Click 168 169 private int RollDiceO 170 { 171 int diel, die2, dieSum; 172 Random randomNumber = new RandomO; 173 174 diel » 1 + randomNumber.Next(6); 175 die2 = 1 + randomNumber.Next(6); 176 177 // отображение значений костей 178 firstImage.ImageUrl = "redDie" + diel.ToStringO + 179 ".png"; 180 secondlmage.ImageUrl = "redDie" + die2.ToString() + 181 ".png"; 182 183 // расчет суммы значений костей 184 dieSum = diel + die2; 185 rollValueLabel.Text = "Your roll was " + 186 dieSum.ToStringO + 187 188 return dieSum; 189 190 } // конец RollDice 191 192 } // конец класса CrapsGame 193 194 } // конец пространства имен Craps Когда форма gameForm становится активной, свойству Visible кнопки Play присваивается значение true, а свойству visible кнопки Play Again — false (т. е. это — первый бросок, а не ситуация "точки"). При нажатии пользователем кнопки Play вызывается метод piayCommand_Ciick (строки 82—132) для обработки первого хода игрока. В строке 88 вызывается метод RollDice (строки 169—190), моделирующий бросок двух костей генери- рованием двух произвольных значений в диапазоне от 1 до 6. Данный метод также изменяет значения в dicePanel. В строках 178—181 изображения first image и secondlmage настраиваются для отображения костей. Обратите внимание, что здесь используется свойство ImageUrl, которому задается значение местоположения lo< dinost 'Сгар$/(<КеяЬ2/4ау2(те^5СКфп45в)гСгар«.М(1И Ffe Edt-. View .^favorites.-Jpob.. Help J . 4й Back - J] Д} ^Search "«j Favorites ^Media ’ Address http://locahost/Craps/(4teeb2z4ay2niM55f0ignK55)/G'aps.atpx?_</ps*_£] —Craps Rules— A player rolls two dice. Each die has six faces. These faces contain 1.2.3. ». 4. 5 and 6 spots After the dice have come to rest, the sum of the spots on the two upward faces is calculated. If the sum is 7 or 11 on the first throw, ™ j Ae player wins. If the sum is 2 (snake eyes), 3 (trey) or 12 (boxcars) on the first throw (called craps), the player loses (i. e., the house wins). If the sum is 4,5.6. 8, 9 or 10 on the first throw, then that sum becomes the player's 'point' To wm, you must continue rolling the dice until you make your point ~ < The player loses by rolling a 7 before making the pond. New Game | _____________________________ . __________________Jrt c 1 Done X " ® Local intranet Рис. 22.11. (Часть 1 из 2.) Игра в кости (изображение предоставлено компанией Openwave System, Inc.): а — правила игры в IE 54 Зак. 3333
850 Гпава 22 |A^W«j^jh^://tocaho^Cfapi j Press Play to start! Play | Quit | г д Ж з |http://192.168.1.113/aaps/(vm3(j |l ttp f/192.168.1.113/craps/(vm3cj *•] i *SORRY. YOU LOSE.* ' Your rol was 7: R. • Again [Qin' I Play Again ] | Quit | View Tod» 4 t-5 V? © B|* J К Рис. 22.11. (Часть 2 из 2) Игра в кости: б — начало игры в IE; в — очередной ход; г — игра проиграна; д — правила игры в симуляторе; е — очередной ход; ж— игра выиграна; з — правила игры в эмуляторе; и — очередной ход; к— игра проиграна
Mobile Internet Toolkit 851 "картинки" кости, которую нужно показать. В строках 184—186 значения костей складываются, и сумма сохра- няется в объекте dieSum. В строке 188 значение dieSum возвращается и присваивается переменной sum в стро- ке 88. В строке 92 создается пара "ключ/значение" для сохранения объекта Session. Данная пара состоит из зна- чения очков игрока, которое можно извлечь с помощью ключа "myPoint". Для определения количества очков первого броска (строки 95—131) используется оператор switch. Если игроку выпадает 7 или 11, то он выигрывает. В случае по умолчанию (default) сумма очков первого броска сохраняет- ся в объекте Session (строка 120) и отображается посредством свойства Text объекта statusLabel (стро- ки 122—123). После этого кнопка Play скрывается, и отображается кнопка Roll Again. При нажатии кнопки Roll Again вызывается метод rollCommand Click (строки 134—160) для повторного броска костей (строка 141) и анализа результата. Игрок выигрывает, если выпадают его "очки" (строка 145), и проигрывает, если выпадает 7 (строка 152). В противном случае, игра продолжается, и игрок может либо продолжать "бросать" кости, либо прекратить игру. Метод quitcommand click (строки 162—167) вызывается при нажатии кнопки Quit. Данный метод меняет активную форму на rulesForm— форму, в которой представлены правила игры. 22.5. Пример: беспроводной портал Deitel В данном примере (листинг 22.7) создается Web-сайт Deitel Wireless Portal (беспроводной портал Deitel). Дан- ный сайт содержит ссылки на многие сайты с доступом WAP. Ссылки разделены на четыре категории (новости, путешествия и питание, финансовая информация и контактная информация), каждая из которых отображается в отдельных элементах формы. Файл ASPX состоит из пяти форм и одного сценария С#. В первой форме отобра- жается приветствие к посетителям и заголовки категорий; в остальных формах находятся ссылки на каждую из них. 1 <%— Листинг 22.7: Portall.aspx —%> 2 <%— Простой мобильный Web-портал —%> 3 4 <@ Register TagPrefix="mobile" 5 Namespace="System.Web.UI.MobileControls" 6 Assembly="System.Web.Mobile, Version=l.0.3300.0, Culture=neutral, PublicKeyToken=b03f5f7flld50a3a" %> 7 <@ Page language="c#" Codebehind="Portall.aspx.cs" 8 Inherits="Portall.MobileWebForml" 9 AutoEventWireup="false" %> 10 11 <meta content="Microsoft Visual Studio 7.0” name="GENERATOR"> 12 <meta content="C#" name="CODE_LANGUAGE"> 13 <meta content="http://schemas.microsoft.com/Mobile/Page" 14 name="vs_targetSchema"> 15 16 <script runat="server" language="C#"> 17 18 ' // добавление пунктов в список 19 private void contactForm_OnActivate(object sender, 20 EventArgs e) 21 { 22 // очистка списка (от предыдущих исполнений) 23 // добавление трех телефонных номеров 24 contactList.Items.Clear(); 25 contactList.Items.Add("DWP Sales (555)555-1234"); 26 contactList.Items.Add("DWP Tech Support (555)555-2468"); 27 contactList.Items.Add("DWP Main (555)555-3696"); 28 29 } // конец contactForm_OnActivate 30 31 </script> 32 33 <body Xmlns:mobile="http://schemas.microsoft.com/Mobile/WebForm"> 34
852 Гпава 22 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 <mobile:Form id="welcomeForm" runat="server"> <mobile:Label id="welcomeLabel" runat="server"> Welcome to the Deitel Wireless Portal! </mobile:Label> < %— при нажатии ссылки пользователь направляется —%> < %— в URL, указанный NavigateUrl —%> < %— символ "#" обозначает внутреннюю ссылку —%> <mobile:Link id="newsLink" runat="server" NavigateUrl="#news Form"> News </mobile:Link> <mobile:Link id="financialLink” runat="server" NavigateUrl="#financialForm"> Financial </mobile:Link> <mobile:Link id="traveLFoodLink" runat="server" NavigateUrl="#travelFoodForm"> Travel/Food </mobile:Link> <mobile^Link id="contactLink" runat="server" NavigateUrl="#contactForm"> Contact </mobile:Link> </mobi1e:Form> _ <mobile:Form id="newsForm" runat="server"> <%— NavigateUrl содержит внешнюю ссылку —%> <%— пользователи отправляются на другой сайт —%> <mobile:Link id="bloombergLink" runat="server" NavigateUrl= "http://www.bloomberg.com/delivery/hdml/home.hdml> Bloomberg </mobile:Link> <mobile:Link id="cnetLink" runat="server" NavigateUrl="http://wap.cnet.com/> CNET News </mobile:Link> <mobile:Link id="nytLink" runat="server" NavigateUrl="http://www.nytimes.com/wap> New York Times </mobile:Link> </mobile: Form> <mobile:Form id="financialForm" runat="server"> <mobile:Link id="wsjLink" runat="server" NavigateUrl="http://wap.wsj.com/"> Wall Street Journal </mobile:Link> <mobile:Link id="finwinLink" runat="server" Navi gat eUrl="http: //finwin. com"> FinWin </mobile:Link>
Mobile Internet Toolkit 853 98 <mobile:Link id="stockPointLink" runat="server" 99 NavigateUrl= 100 "http://wml.stockpoint.com/index.wml> 101 StockPoint 102 </mobile:Link> 103 </mobile: Form> 104 105 <mobile:Form id="travelFoodForm" runat="server"> 106 <mobile:Link id="citiWizLink" runat="server" 107 NavigateUrl="http://wap.citiwiz.com/index.wml> 108 CitiWiz 109 </mobile:Link> r 110 111 <mobile:Link id="testBestLink" runat="server" 112 NavigateUrl="http://wap.lObest.ccm/> 113 lOBest 114 </mobile:Link> 115 116 <mobile:Link id="expediaLink" runat="server" 117 NavigateUrl="http://mobile.msn.com/hdml/travel.asp> 118 Expedia 119 </mobile:Link> 120 </mobile: Form> 121 122 <mobile:Form id="contactForm" 123 OnActivate="cintactForm OnActivate" runat="server"> 124 125 cmobile:Label id="contactLabel" runat="server"> 126 Contact Information 127 </mobile: Label> 128 129 <%— объект списка содержит телефонные номера —%> 130 <mobile:List id="contactList" runat="server"> 131 </mobile:List> 132 </mobile:Form> 133 134 </body> Сценарий C# в этом файле содержится метод contactForm_OnActivate (строки 19—29), добавляющий элементы в contactList — элемент управления класса List. Этот элемент визуализирует список пунктов в мобильной Web-странице. Каждому пункту списка присвоен тип MobileListltem. Класс List содержит свойство items, которое можно использовать для получения (get) и установки (set) пунктов MobileListltems. Процедура дос- тупа get возвращает пункты списка как объекты MobileListitemCollection. В отличие от класса List, класс MobileListitemCollection предоставляет функции для программных манипуляций списком пунктов. В стро- ках 24—27 вызываются методы Clear и Add класса MobileListitemCollection для обновления объекта contactList. С помощью метода clear (строка 24) все пункты удаляются из списка, а с помощью метода Add — добавляются. В строках 35—64 определяется форма welcomeForm, являющаяся главным меню сайта. Она состоит из элемента управления Label и элементов управления Link, создающих гиперссылки, которые направляют пользователей на другие формы и сайты в зависимости от значения свойства NavigateUrl. Например, в строке 56 свойство NavigateUrl имеет значение tttravelFoodForm. Свойство Text имеет значение Travel/Food. При запуске примера Web-сайт отображает значение свойства Text (Travel/Food) элемента Link как гиперссылку. При нажатии поль- зователями на данную ссылку Link осуществляется переход к значению, указанному в свойстве NavigateUrl. В данном случае пользователи переходят к форме travelFoodForm (определена в строках 105—120). Символ # в каждом значении свойства NavigateUrl указывает, что ресурс размещен в файле ASPX. Значения свойства NavigateUrl, начинающиеся с символа #, называются внутренними ссылками (иногда они еще называются яко- рями страницы). Позже будет рассмотрен вариант задания значений свойства NavigateUrl внешним ссылкам (т. е. местоположениям за пределами файла ASPX). Внешние ссылки не содержат символа #. В строках 66—120 определяются формы, содержащие различные категории Web-ссылок. Каждая форма имеет множественные внешние ссылки. В строках 122—132 определяется форма contactForm, отображающая пункты в элементе управления List (определен в строке 130). Результат выполнения приложения представлен на рис. 22.12.
854 Гпава 22 б в д Рис. 22.12. Демонстрация беспроводного портала (изображения предоставлены Openwave System Inc. и Screaming Media, Inc.): a — первая форма в IE; б — вторая форма; в — третья форма; г — четвертая форма; д — первая форма в симуляторе; е — вторая страница; ж— третья страница Многие из ссылок подключаются к WAP-сайтам при щелчке на них кнопкой мыши во время выполнения про- граммы, по этой причине они не могут отображаться в IE. (В следующем разделе рассматриваются способы управления MIT аппаратными функциями.)
Mobile Internet Toolkit 855 22.6. Аппаратно-независимый Web-дизайн с помощью таблиц стилей и шаблонов ' В данном разделе попробуем усовершенствовать внешние и внутренние признаки сайта беспроводного портала компании Deitel с помощью дополнительных элементов управления, например, AddRotator, который произ- вольно отбирает и отображает элементы управления. Здесь также рассматривается использование "шаблонов" и таблицы стилей для максимального расширения возможностей входного устройства. В разделе представлен важный аспект разработки мобильных Web-приложений: программирование сайтов на обработку запросов от неравноправных устройств. Начнем с рассмотрения файла ASPX (листинг 22.8) и файлов, к которым приложе- ние осуществляет доступ (листинги 22.9 и 22.10) с последующим переходом к файлу фонового кода (лис- тинг 22.11). Элемент управления stylesheet — это коллекция элементов style. Большинство элементов управления MIT имеют свойство styleReference, задающее гарнитуру шрифта, элемента управления, его цвет, выключку текста и т. д. Значение этого свойства задается атрибуту Name элемента style, на которое свойство делает ссылку. Эле- менты управления Stylesheet дают разработчику возможность задания стиля элементов страницы (заголовков, шрифтов, цветов и т. д.) отдельно от структуры документа (т. е. мобильных тегов), а также указать то или иное устройство (этот процесс называется аппаратно-зависимой визуализацией). Например, программист может на- писать код для повышения качества отображения Web-страниц браузерами на базе HTML, без ущерба для мо- бильной доступности. В данной главе для тестирования примеров используются браузеры IE и мобильный брау- зер Openwave. Впрочем, браузеры IE могут отображать графические объекты и изображения с большим разре- шением на "замысловатых" Web-страницах. Замечание по технологии программирования__________________________________ Отделение структуры от содержимого обеспечивает повышенную управляемость сайта и упрощает внесение изменений в его стиль. гд’”* «г - • • •• * - • , * НТ* • • X- . ' : •• - ..... • г • Листинг 22.8. Таблицы стилей и шаблоны в мобильной Web-фЩ'ме г “ ' Л 1 <%— Листинг 22.8: Portal2.aspx —%> 2 <%— Простой мобильный Web-портал —%> 3 4 <@ Register TagPrefix="mobile" 5 Namespace="System.Web.UI.MobileControls" 6 Assembly="System.Web.Mobile, Version=l.0.3300.0, Culture=neutral, PublicKeyToken=b03f5f7flld50a3a" %> 7 8 <@ Page, language»*'c#" Codebehind="Portal2.aspx.cs" 9 Inherits»"Portal2.WebPortal" 10 AutoEventWireup="false" %> 11 12 Cneta content="Microsoft Visual Studio 7.0" name="GENERATOR”> 13 Cneta content="C#" name="CODE_LANGUAGE"> 14 Cneta content="http://schemas.microsoft.com/Mobile/Page" 15 name="vs_targetSchema"> 16 17 <body Xmlns:mobile="http://schemas.microsoft.com/Mobile/WebForm"> 18 19 <%— объект Stylesheet, визуализированный для каждой —%> 20 <%— определяющей его формы —%> 21 cnobile:stylesheet id="Stylesheetl" runat="server"> 22 Cnobile:Style Name="DWPStyles"> 23 <DeviceSpecific> 24 25 <%— шаблоны, созданные для браузеров HTML —%> 26 <Choice Filter="isHTML32"> 27 <HeaderTemplate> 28 cnobile:Image runat="server" 29 ImageUrl="top.jpg"> 30 </mobile: Image> 31 </HeaderTemplate> 32 </Choice> 33
856 Гпава 22 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 <%— шаблоны, созданные для других браузеров —%> <Choice> <HeaderTemplate> <Mobile:Label runat="server" StyleReference="Title" Text="Deitel Wireless Portal" ID="Labell" /> </HeaderTemplate> </Choice> </DeviceSpecific> </mobile:Style> «/mobile:stylesheet> <%— конец таблицы стилей —%> <%— открытие страницы с изображением dwp —%> -«mobile:form id="welcomeForm" runat="serVer"> «mobile:Image id="welcomelmage" runat="server" Alternatetext="DeitelWireless" ImageUrl="logo.wbmp"> <DeviceSpecific> «Choice Filter="isHTML32" ImageUrl="logocolor.jpg"> </Choice> </D^viceSpecific> «/mobile:Image> «mobile:Link id="enterLink" runat="server Alignment=”Center" NavigateUrl="#mainForm"> Enter «/mobile:Link> «/mobile:form> <%— конец welcomeForm —»%> <%— задание объекта Stylesheet 'DWPStyles' —%> «mobile:form id="mainForm" runat="server" StyleReference="DWPStyles"> «mobile:Link id="newsLink" runat="server" NavigateUrl="#newsForm"> News «/mobile:Link> «mobile:Link id«"financialLink" runat="server" Navigate0rl="@financialForm"> Financial «/mobile:Link> «mobile:Link id="travelFoodLink" runat="server" NavigateUrl="#travelFoodForm"> Travel/Food «/mobile:Link> «mobile:Link id="contactLink" runat="server" NavigateUrl="@contactForm"> Contact «/mobile: LiTnk> «mobile:Label id="specialsLabel" runat="server" Font-Bold="True"> Specials: «/mobile:Label> «mobile:Label id="bookTitleLabel" runat="server" Font-Italic="True"> «/mobile:Label>
Mobile Internet Toolkit 857 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 «mobile:Label id="descriptionLabel" runat="server"> </mobile:Label> «mobile:AdRotator id="AdRotatorl" runat="server" AdvertisementFilr="adBanners. xml" Imagekey="WirelessImageUrl"> <Devi ceSpeci f ic> <Choice Filter=”isMME" ImageKey="JpgImageUrl"> </Choice> «Choice Filter="isHTML32" ImageKey="JpgImageUrl”> NavigateUrlKey="lENavigateUrl"> </Choice> «/DeviceSpeci f ic> «/mobile:AdRotator> ♦ «/mobile:form> <%— конец mainForm —%> «mobile:form id=" news Form" runat="server" StyleReference="DWPStyles"> «mobile:Link id="bloombergLink" runat="server" NavigateUrl= "http: //www.bloomberg. com/delivery/hdml/home/hdml> Bloomberg «/mobile:Link> «mobile:Link id="cnetLink" runat="server" NavigateUrl="http://wap/cnet.com/> CNET News «/mobile:Link> «mobile:Link id="nytLink" runat="server" NavigateUrl="http://www.nytimes.com/wap"> New York Times «/mobile: Link> «mobile:Link id="mainLinkl" runat="server" NavigateUrl="#mainForm"> Main «/mobile:Link> «/mobile: form> <%— конец newsForm —%> «mobile:form id="financialForm" runat="server" StyleReference="DWPStyles"> «mobile:Link id="wsLink" runat="server" NavigateUrl="http://wap.wsj.com/> Wall Street Journal «/mobile:Link> «mobile:Link id="finWinLink" runat="server" NavigateUrl="http//finwin.com"> FinWin «/mobile:Link> «mobile:Link id="stockPointLink" runat="server" NavigateUrl= "http://wml.stockpoint.com/index.wml>
858 Гпава 22 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 StockPoint </mobile:Link> <mobile:Link id="mainLink2" runat="server" NavigateUrl="#mainForm"> • Main </mobile:Link> </mobile:form> <%— конец financialForm —%> <mobile:form id="travelFoodForm" runat="server" StуleReference="DWPStyles"> <mobile:Link id="citiWizLink” runat="server" NavigateUrl=”http://wap.citiwiz.com/index.wml> CitiWiz </mobile:Link> <mobile:Link id="tenBestLink” runat="server" NavigateUrl="http://wap.lObest.com/> lObest </mobile:Link> <mobile:Link id="expediaLink" runat="server" NavigateUrl="http://mobile.msn.com/hdml/travel.asp> Expedia </mobile:Link> <mobile:Link id="mainLink3" runat="server" NavigateUrl="#mainForm"> Main </mobile:Link> <mobile: form> <%— конец travelFoodForm —%> <mobile:form id=ncontactForm"runat="server" StyleReference="DWPStyles"> <mobile:I.abel ids"contactLabel" runat*"server"> Contact Information </mobile:Label> <%— список, содержащий три созданных пункта —%> <mobile:List id="contactList" runat="server"> <Item Value="DWP Sales (555)-5551234" Text«"JDWP Sales (555)555-1234"> </Item> / » <Item Value="DWP Tech Support (555)555-2468" Text="DWP Tech Support (555)555-2468”> </Item> <Item Value="DWP Main (555)555-3696" Text="DWP Main (555)555-3696">, </Item> </mobile:List> <mobile:Link id="mainLink4" runat="server" NavigateUr1="#mainForm"> Main </mobile:Link> </mobile:fortm> <%— конец contactForm —%> </body>
Mobile Internet Toolkit 859 Начнем c ASPX-файла, представленного в листинге 22.8. В строках 21—44 определяется таблица стилей. Не- смотря на то, что элементы управления stylesheet могут содержать множественные объекты style, рассматри- ваемая таблица стилей содержит только один объект. Значение атрибута Name элемента style (строки 22—43) установлено на DWPStyles. Значение атрибута Name используется формами для ссылки на таблицу стилей (по- средством атрибута StyleReference). DWPStyles класса style содержит элемент Devicespecific, используемый в приложениях, предназначенных для исполнения на разных устройствах. Элемент Devicespecific содержит один или несколько элементов Choice, предоставляющих информацию о конкретном устройстве (или наборе устройств на базе функций входного уст- ройства). Элемент Choice имеет атрибут Filter, оцениваемый и сравниваемый с фильтрами Filters, заданными в рамках элемента DeviceFilters файла Web.config (листинг 22.9), для определения поддержки заданного свой- ства микробраузером, на котором исполняется приложение. Каждый элемент filter в файле web.config содер- жит три атрибута: name, compare и argument. Значение атрибута name выбирается программистом из предвари- тельно составленного списка в файле Web.config. Значением атрибута compare должно быть одно из нескольких свойств браузера, предварительно заданных в файле machine.config (см. листинг 22.4). Значение атрибута argument — значение свойства браузера. ^Листинг 22.9. «Файл Web.config для файла Portal2.aspx \ ' ............. - - ..... Аа . . аа аа . а а а а а а а а а а а а а а а а а а а а . ...... в . а к ..... a i а в ...... а в в . ~ .Ь а в ... а ..... в . к ... в в ..... в . а. ... а а а .. в a ..... в .................. М ...... 1.......... а «а.... ‘ в а. I... ...А ...а а А а А а а а'а . а а а а а а ^а 'а а а а . а а'а . < В » А А А А А А а • А а а А А Г. 1 <?xml version="1.0" encoding="utf-8" ?> 2 ' 3 <configuration> 4 <system.web> 5 <deviceFilters> 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 <!—• Языки разметки —> <filter name="isHTML32" compare=”PreferredRenderingType" argument="html32" /> <filter name="isWMLll" compare="PreferredRenderingType" argument="wmlll" /> <filter name="isCHTML10" compare="PreferredRenderingType" argument="chtmllO" /> <!— Браузеры устройств —> <filter name="isGoAmerica" compare="Browser" argument="Go.Web" /> <filter name="isMME" compare="Browser” argument="Microsoft Mobile Explorer" /> <filter name="isMyPalm" compare="Browser" argument="MyPalm" /> <filter name="isPocketIE" compare="Browser" argument="Pocket IE" /> <filter name="isUP3x" compare="Type" argument="Phone.com Зюч Browser" /> <filter name="isUP4x" compare="Type" argument="Phone.com 4юч Browser" /> <!— Особые устройства —> <filter name=’’isEricssonR380" compare="Type" argument="Ericsson R380" /> <fliter name="LsNokia7110" compare="Type" argument="Nokia 7110" /> <!— Возможности устройств —> <filter name="prefersGIF" compare="PreferredlmageMIME" argument="image/gif" /> <filter name="prefersWBMP" compare="PreferredlmageMIME" argument="image/vnd.wap.wbmp" />
860 Гпава 22 45 <filter name="supportsColor" compare="IsColor" 46 argument="true" /> 47 <filter name="supportsCookies" compare="Cookies' 48 argument="true" /> 49 <filter name="supportsJavaScript" 50 compare=”Javascript" argument="true" /> 51 <filter name="supportsVoiceCalls" 52 compare="CanInitiateVoiceCall" 53 argument="true" /> 54 55 </deviceFilters> 56 </system.web> 57 </configuration> Элементы Filter описывают свойства микробраузеров. Эти свойства позволяют разработчикам оценить вход- ное устройство программным способом. Для этого программист может вызвать метод HasCapabi lity (класса Mobil eCapabilities), получающий в качестве аргумента информацию о конкретном фильтре (Filter) и воз- вращающий значение true, если входное устройство поддерживает эти возможности. Выбранный элемент ви- зуализируется для пользователя. В строках 26—32 задается первый элемент Choice. Фильтр "isHTML32" указы- вает, что данный элемент предназначен для браузеров HTML и определен в строках 8—10 листинга 22.9. Атри- бут name данного фильтра (строка 8) используется в целях идентификации. Атрибут compare установлен на значение свойства preferredRenderingType (строка 9), различающее возможные устройства Атрибут argument задает значение для устройства. Этим значением может быть все, что угодно — от булевой величины до типа MIME. В данном примере элемент Choice выбирается исходя из preferredRenderingType. Это свойство обозна- чает тип MIME изображения, поддерживаемый входным устройством. Тип визуализации задается атрибутом argument, в данном случае — html32. Этим указывается выбор Choice с данным фильтром Filter, если входное устройство ожидает изображений с типом MIME html32. В последующем коде (строки 28—30 листинга 22.8) отображается top.jpg — файл графического формата, поддерживаемого браузерами HTML. Мобильный браузер Openwave не поддерживает формат JPEG, поэтому отображается содержимое элемента HeaderTemplate (см. далее), расположенного в рамках элемента по умолчанию choice (строки 35--41). Элемент Choice принимается по умолчанию, потому что в нем не содержится атрибут Filter. Для HTML-браузеров исполняются строки 26— 32; для прочих браузеров — строки 35—41. Обратите внимание, что в каждом элементе Choice имеется шаблон элемента управления, содержащий набор стереотипных элементов (например, элементов HeaderTemplate). Шаблоны размечают содержимое для его про- смотра на входных устройствах. В примере вместо одного используется несколько шаблонов для поддержки различных устройств исполнения данного приложения. Однако для каждого из устройств применяется только один шаблон. Шаблоны в примере служат для создания заголовка, отображаемого на каждом устройстве. Для устройства HTML (например, 1Е и MIME) заголовок представляет собой полноцветное изображение; для клиен- тов, не поддерживающих HTML, заголовок отображается в виде текста. Место визуализации содержимого при- ложения определяется типом элемента шаблона. В строках 27—31 используется шаблон HeaderTemplate, визуа- лизируемый в верхней части элемента управления Form или List. В этом шаблоне отображается графический файл top.jpg. Для шаблона заголовка HeaderTemplate по умолчанию (строки 36—40) отображается текст "Deitel Wireless Portal". Для создания содержимого, отображаемого внизу элемента управления Form или List, исполь- зуется шаблон FooterTemplate. В строках 47—61 определяется форма welcomeForm, приветствующая посетителей Web-сайта. Она не делает ссылок на таблицу стилей DWPStyies. Вместо этого, для демонстрации, что аппаратно-независимую визуализа- цию можно осуществить без таблицы ссылок, используются элементы Devicespecific и Choice. Если входное устройство имеет функцию отображения цветов, тогда появляется крупное полноцветное изображение в форма- те jpeg. Устройство по умолчанию отображает растровую версию логотипа компании. В строках 57—60 пред- ставлен объект Link, отправляющий пользователя в форму mainForm. В строках 64—114 определяется форма mainForm. Ею и оставшимися формами документа используется атрибут StyleReference для ссылки на таблицу стилей DWPStyies. Форма mainForm содержит несколько ссылок Link (строки 67—85) и три метки, две из которых используются в методе AdRotatorl AdCreated для предоставления текстового описания рекламируемого товара или услуги. Сам метод AdRotator определен в строках 99—112. Рекламные объявления сохранены в файле XML, заданном в свойстве Advertisement File. Данный файл (AdBanners.xml) показан в листинге 22.10. Информация для трех разных объявлений размечена. Информация каждого рекламного объявления задана в элементе Ad. Любое объявление имеет пять элементов: ImageUrl, WirelessImageUrl, NavigateUrl, AlternateText и Impressions.
Mobile Internet Toolkit 861 Листинг 22.10. Файл AdBanners.xml дляфайлаРог1а12.а8рх СМЩи J_,_^™._„....„.._..b___...—.._...J 1 <?xml version="l.0"?> 2 3 <Advertisements> 4 <Ad> 5 <ImageUrl>bannerl.gif</ImageUrl> 6 <WirelessImageUrl>buyMe.wbmp</WirelessImageUrl> 7 <JpgImageUrl>buyMe.jpg</JpgImageUrl> 8 <NavigateUrlx/NavigateUrl> 9 <IENavigateUrl>http://www.amazon.com/exec/obidos/ASIN/ 013028419X/deitelassociatin/107-7206886-3349351</IENavigateUrl> 10 <ALtetnateText>bannerlText</AlternateText> 11 <Inpressions>80</Impressions> 12 </Ad> 13 <Ad> 14 <ImageUrl>bann§r2.gif</ImageUrl> 15 <WirelessImageUrl>buyMe.wbmp</WirelessImageUrl> 16 <JpgImageUrl>buyMe.jpg</JpgImageUrl> 17 <NavigateUrlx/NavigateUrl> 18 <IENavigateUrl>http://www.amazon.eom/exec/obidos/ASIN/ 0130284173/deitelassociatin/107-7206886-3349351</IENavigateUrl> 19 <AlternateText>banner2Text</AlternateText> 20 <Impressions>80</Impressions> 21 </Ad> 22 <Ad> 23 <ImageUrl>banner3.gif</ImageUrl> 24 <WirelessImageUrl>buyMe.wbmp</Wireless!mageUrl> 25 <JpgImageUrl>buyMe.jpg</JpgImageUrl> 26 <NavigateUrlx/NavigateUrl> 27 <IENavigateUrl>http://www.amazon.com/exec/obidos/ASIN/ 0130323640/deitelassociatin/107-7206886-3349351</IENavigateUrl> 28 <AlternateText>banner3Text</AlternateText> 29 <Impressions>80</Impressions> '• 30 </Ad> 31 </Advertisements> Каждое рекламное объявление в файле может включать несколько графических изображений. Одно из них вы- брано, исходя из возможностей входного устройства. Для задания этих возможностей опять используется эле- мент Devicespecific (строки 103—110 листинга 22,8), содержащий один или несколько элементов Choice. При- сваивается атрибут Filter элемента Choice для задания одного из фильтров (например, isHTML32) в файле Web.config. В данном примере представлен еще один атрибут— imageKey. Элемент wirelessImageUrl исполь- зуется для ссылки на изображение, указанное атрибутом ImageKey. Например, если значение ImageKey — JPEGimageUrl, тогда отображаемое изображение — одно из заданных в элементе jPEGimageUrl. В данном при- мере используемый фильтр— "isMME" (определен в файле Web.config), заданный в строке 104 листинга 22.8 и определенный в строках 21—22 и 24 листинга 22.9. Если входное устройство— ММЕ (версии 3.0), тогда ото- бражаемое изображение выбирается из элементов WirelessImageUrl в файле AdBanners.xml (строки 6, 15 и 24 листинга 22.10). Если входное устройство— не ММЕ, тогда программа просматривает следующий элемент Choice. Изображение по умолчанию используется для других микробраузеров, потому что дополнительных элементов Choice не представлено. Изображение по умолчанию задано в элементе ImageUrl. Если по какой- либо причине изображение не может быть отображено (например, при указании изображения, не поддерживае- мого существующим браузером, или при невозможности определения его местоположения), тогда в элементе AiternateText вместо изображения выдается текст. В каждом рекламном объявлении содержатся элементы NavigateUrl и Impressions. Элемент NavigateUrl раз- мечает местоположение, в которое направляются пользователи при щелчке на рекламном объявлении. В нашем примере данный элемент остается пустым (т. е. при щелчке кнопкой мыши на изображении ничего не происхо- дит). Элемент impressions размечает вес объявления. Объявления с большим весом отображаются более часто. Совет по повышению переносимости________________________________________________________________ При использовании элемента NavigateUrl программист должен следить за тем, чтобы указанный по URL Web- сайт мог визуализироваться на входном устройстве.
862 Глава 22 Формы newsForm, financialForm и travelFoodForm (строки 116—190) сходны с использованными в предыду- щем примере, но имеют одно существенное отличие: ими вызывается метод ResolveLinks, определяющий, на какой сайт переведен пользователь: HTML или WAP. Метод ResolveLinks рассматривается далее. В строках 192—218 определяется форма contactForm, содержащая объект List, заполненный телефонными но- мерами. В данном случае элементы списка List добавляются непосредственно в форму, а не в раздел <script>, как в последнем примере. Все формы содержат ссылку Link для выхода в главное меню, за исключением самой формы MainMenu. Рассмотрим файл фонового кода (листинг 22. И), содержащий два метода: AdRotatorl_AdCreated (строки 80— 111) и ResolveLinks (строки 114—135). Метод AdRotatorl_AdCreated представляет собой обработчик события AdCreated класса AdRotator, имеющего место при отображении рекламного объявления класса AdRotator. Дан- ное событие содержит свойство AiternateText, обеспечивающее доступ к значению элемента AiternateText в файле XML. Это свойство используется в строках 83, 92 и 101 метода AdRotatorlAdCreated для определения текущего рекламного объявления и для задания соответствующих значений свойствам Text классов BookTitleLabel и DescriptionLabel. Например, если выбранное изображение относится к книге "XML How to Program", тогда метод AdRotatorl_AdCreated отобразит заголовок этой книги формата XML в классе BookTitleLabel, а информацию О данной книге — в классе DescriptionLabel. Метод ResolveLinks (строки 114—135) представляет собой обработчик событий Activate форм newsForm, financialForm и travelFoodForm. Данный метод используется для определения места назначения ссылок фор- мы, исходя из возможностей входного устройства. Некоторые Web-сайты, например www.finwin.com, выявля- ют входное устройство и возвращают соответствующую разметку. Другие сайты, такие как Wall Street Journal, имеют отдельные URL для своих беспроводных сайтов. Для доступа к беспроводному сайту Wall Street Journal пользователям следует зайти на сайт wap.wsj.com, вместо www.wsj.com. Метод ResolveLinks определяет воз- можности входного устройства программными способами с использованием класса MobileCapabilities. Дан- ный класс наследует от класса HttpBrowserCapabilities, инкапсулирующего информацию о работающем в данный момент браузере. Обратите внимание на свойство Request. Browser (строка 118), являющееся частью класса HttpRequest и возвращающее объект HttpBrowsdrCapabilities. В строках 116—118 данный объект пе- редается в тип MobileCapabilities и присваивается экземпляру типа MobileCapabilities, на который сделана ссылка атрибутом capabilities. Доступом к свойству Browser атрибута capabilities можно определить, явля- ется ли входным устройством Internet Explorer. Если да, то пользователь направляется на соответствующие сай- ты, поддерживающие HTML. Результат работы приложения представлен на рис. 22.13. а б Рис. 22.13. (Часть 1 из 2.) Демонстрация работы беспроводного портала: а — заставка в IE; б — первая форма в IE
Mobile Internet Toolkit 863 в d New .York Times Man View Tools -fr! иН e Рис. 22.13. (Часть 2 из 2.) Демонстрация работы беспроводного портала: в — заставка в ММЕ; г — первая форма в ММЕ (начало); д — первая форма в ММЕ (окончание); е — заставка в Pocket PC Emulator; ж— первая форма в Pocket PC Emulator; з — вторая форма Листинг 22.11. Файд фононого кода для Deitel Wireless Portal 1 // Листинг 22.11: DeitelWirelessPortal2.aspx.cs 2 // Файл фонового кода для Deitel Wireless Portal (версия 2) 3 4 using System; 5 using System.Collections; 6 using System.ComponentModel; 7 using System.Data; 8 using System.Drawing; 9 using System.Web; 10 using System.Web.Mobile; 11 using System.Web.Sessionstate; 12 using System.Web.UI; 13 using System.Web.UI.MobileControls; 14 using System.Web.UI.WebControls; 15 using System.Web.UI.HtmlControls; 16
864 Глава 22 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 $ 80 81х namespace Portal2 { public class WebPortal : System.Web.UI.MobileControls.MobilePage ( // таблица стилей protected System.Web.UI.MobileControls.Stylesheet Stylesheetl; // форма welcome и ее элементы управления protected System.Web.UI.MobileControls.Form welcomeForm; protected System.Web.UI.MobileControls.Image welcomelmage; protected System.Web.UI.MobileControls.Link enterLink; // форма Main и ее элементы управления protected System.Web.UI.MobileControls.Form mainForm; protected System.Web.UI.MobileControls.Link newsLink; protected System.Web.UI.MobileControls.Link financialLink; protected System.Web.UI.MobileControls.Link travelFoodLink; protected System.Web.UI.MobileControls.Link contactLink; protected System.Web.UI.MobileControls.Label specialsLabel; protected System.Web.UI.MobileControls.Label bookTitleLabel; protected System.Web.UI.MobileControls.AdRotator AdRotatorl; protected System.Web.UI.MobileControls.Label descriptionLabel; // форма News и его элементы управления protected System.Web.UI.MobileControls.Form newsForm; protected System.Web.UI.MobileControls.Link bloombergLink; protected System.Web.UI.MobileControls.Link cnetLink; protected System.Web.UI.MobileControls.Link nytLink; protected System.Web.UI.MobileControls.Link mainLinkl; // форма financial и ее элементы управления protected System.Web.UI.MobileControls.Form financialForm; protected System.Web.UI.MobileControls.Link wsjLink; protected System.Web.UI.MobileControls.Link finWinLink; protected System.Web.UI.MobileControls.Link stockPoinfLink; protected System.Web.UI.MobileControls.Link mainLink2; // форма travel/food и ее элементы управления protected System.Web.UI.MobileControls.Form travelFoodForm; protected System.Web.UI.MobileControls.Link citiWizLink; protected System.Web.UI.MobileControls.Link tenBestLink; protected System.Web.UI.MobileControls.Link expediaLink; protected System.Web.UI.MobileControls.Link mainLink3; // форма contact и ее элементы управления protected System.Web.UI.MobileControls.Form contactFotm; protected System.Web.UI.MobileControls.Label contactLabel; protected System.Web.UI.MobileControls.List contactList; protected System.Web.UI.MobileControls.Link mainLink4; private void Page_Load(object sender, System.EventArgs e) { // ввод пользовательского кода для инициализации страницы } // код, сгенерированный Visual Studio .NET 11 отображение нужного текста для текущего объявления private void AdRotator l_AdCreated(object sender System.Web/UI.WebControls.AdCreatedEventArgs e)
Mobile Internet Toolkit 865 82 { 83 if (e.AlternateText = "bannerlText") 84 { 85 bookTitleLabel.Text = "e-Business and e-Commerce " + 86 "How To Program:"; 87 descriptionLabel.Text » "This book carefully " + 88 "explains how to program multi-tiered, " + 89 "client/server, database-intensive, Web-based," + 90 "e-Business and e-Commerce applications."; 91 } 92 else if (e.AlternateText “ "banner2Text") 93 { 94 bbokTitleLabel.Text = "XML How To Program:"; 95 descriptionLabel.Text = "Offers a careful explanation " + 96 "of XML-based systems development, for faculty " + 97 "students and professionals. Includes " + 98 "extensive pedagogic features, ibcluding " + 99 "Internet resources."; 100 } 101 else if (e.AlternateText — "banner3text") 102 { 103 bookTitleLabel.Text = "e-Business and e-Commerce " + 104 "For Managers:"; 105 desciu.pt ionLabel. Text = "For all managers, business " + 106 "owners and others who need a comprehensive " + 107 "overview of how to build and manage " + 108 "an e-Business,"; 109 } 110 111 } // конец AdRotator AdCreated 112 113 // изменение ссылок для браузеров IE 114 private void ResolveLinks(object sender, System.EventArgs e) 115 { 116 System.Web.Mobile.MobileCapabilities capability = 117 (System.Web.Mobile.MobileCapabilities) 118 Request.Browser;t 119 120 11 если название браузера содержит "IE”, изменить ссылки 121 if (capability.Browser.IndexOf("IE") != -1) 122 { 123 bloombergLink.NavigateUrl = 124 "http://www.bloomberg.com; 125 cnetLink.NavigateUrl = http://www.cnet.com"; 126 nytLink.NavigateUrl = "http://www.nyt.com"; 127 wsjLink.NavigateUfl = "http://www.wsj.com"; 128 stockPointLink.NavigateUrl = 129 "http://stockpoint.com/"; 130 citiWizLink.NavigateUrl = "http://www.citiwiz.com"; 131 tenBestLink.NavigateUrl = "http://www.10best.com"; 132 expediaLink.NavigateUrl » "http://www.expedia.com"; 133 ) 134 135 } // конец ResolveLinks 136 137 } // конец класса WebPortal 138 139 } // конец пространства имен Portal2 55 Зак. 3333
866 Гпава 22 22.7. Использование Web-служб в мобильном приложении В главе 18 приведен пример системы бронирования авиабилетов. В следующем примере демонстрируется дос- туп к той же Web-службе с мобильного устройства. Единственным отличием примера данного раздела от при- мера главы 18 является клиентское приложение, созданное с помощью MIT. Web-служба бронирования авиабилетов получает информацию о типе места, которое клиент хочет заброниро- вать, и осуществляет бронирование при наличии места заказанного типа. Логика службы бронирования имеет один Web-метод Reserve, осуществляющий просмотр базы данных имеющихся мест с целью нахождения зака- занного. При обнаружении места нужного типа метод Reserve обновляет базу данных, осуществляет брониро- вание и возвращает значение true; в противном случае, если бронирование не выполнено, метод возвращает значение false. Примечание__________________________________________________________________________________ Код Web-службы в листинге данного раздела не представлен: он приведен в главе 18. Метод Reserve принимает два аргумента’ строку, обозначающую нужный тип креста (т. е. у окна, в середине и у прохода), и строку, обозначающую нужный тип класса (экономический или бизнес-класс). Если результат запроса не пустой, то программа бронирует первый номер места, возвращаемый запросом, и метод Reserve воз- вращает значение true (бронирование успешно). Если бронирование не осуществлено, тогда метод Reserve воз- вращает значение false (ни одно из свободных мест не соответствует условиям запроса). В листинге 22.12 приставлен ASPX-файл для мобильной Web-формы, в которой пользователи могут выбрать нужные типы мест. Страница позволяет выполнить бронирование на основании информации о классе и место- положении в ряду доступных мест (кресел). Затем страница использует Web-службу бронирования для обработ- ки запросов пользователей. Если запрос базы данных не имеет результата, тогда пользователь получает сооб- щение о необходимости его изменения и повторе попытки. ... ..... . ...............- ’ ?т .. . г " Т Листинг 22.12. Страница ASPX, обеспечивающая доступ мобильного устройства к Web-службе ♦. . .л.,-*;.. Л* ^7-Л-------------------- «л < а .. ........-...........* -........... . ..«Жъ • 1 <%— Листинг 22.12: Reservation.aspx —%> 2 <%— Клиентское приложение бронирования авиабилетов —%> 3 <@ Page language="c#" Codebehind="Reservation.aspx.cs” 4 Inherits="Reservation.MakeReservation” 5 AutoEventWireup="false" %> 6 7 <@ Register TagPrefix="mobile" 8 Namespace="System.Web.UI.MobileControls" 9 Assembly=“"System.Web.Mobile, Version=l.0.3300.0, Culture=neutral, PublicKeyToken=b03f5f7flld50a3a" %> 10 11 <meta name="GENERATOR" content="Microsoft Visual Studio 7.0"> 12 <meta name="CODE_LANGUAGE"> content="C#"> 13 <meta name="vs_targetSchema" 14 content="http://schemas.micrdsoft.com/Mobile/Page"> 15 16 <body Xmlns:mobile="http://schemas.microsoft.com/Mobile/WebForm"> 17 18 <mobile:Form id=”reservationForm" runat="server"> 19 cmobile:Label id="instructionLabel" runat="server"> 20 Please select the type of seat and class 21 you wish to reserve: 22 </mobile:Label> 23 24 <mobile:Label id="seatLabel" runat="server"> 25 Font-Size="Large"> 26 Seat: 27 </mobile:Label> 28 29 <mobile:SelectionList id="seatList" runat="server"> 30 <Item Value="Aisle" Text="Aisle></Item>
Mobile Internet Toolkit 867 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 <Item Value="Middle" Text="Middlex/item> <Item Value="Window" Text="Windowx/Item> </mobileSelectionList> -cnobile:Label id="classLabel" runat=,,server"> Font-Si ze="Large"> Class: </mobile:Label> <mobile:SelectionList id="classList" runat="server’’> <Item Value="Economy" Text="Economyx/Item> <Item Value="First" Text="Firstx/ltem> </mobile': Select ionList> <mobile:Command id="reserveCommand" runat="server"> Reserve: </mobile:Command> </mobile: Form> <mobile:Form id=,,confirmationForm" runat="server"> <mobile:Label id="confirmationLabel" runat="server"> </mobile:Label> <mobile:Label id="confirmSeatLabel" runat="server"> </mobile:Label> <mobile:Label id="confirmClassLabel" runat="server"> </mobile:Label> </mobile:Form> </body> В данной странице определяются два объекта SelectionList, отображающие короткий список вариантов выбо- ра, и элемент Button. Один объект SelectionList отображает все типы мест, из которых пользователь может сделать свой выбор. Во втором объекте перечислены типы классов. Пользователи нажимают кнопку с именем reserveButton для представления запросов после выбора в объекте SelectionList. Файл фонового кода (лис- тинг 22.13) содержит обработчик события для этой кнопки. Листинг 22.13. Файл фонового кода для Web-приложения с доступом к Web-служЗе Я ч 1 // Листинг 22.13: Reservation.aspx.cs 2 // Бронирование авиабилетов с помощью Web-службы 3 4 using System; 5 using System.Collections; 6 using System.ComponentMode1; 7 using System.Data; 8 using System.Drawing; 9 using System.Web; 10 using System.Web.Mobile; 11 using System.Web.Sessionstate; 12 using System.Web.UI; 13 using System.Web.UI.MobileControls; 14 using System.Web.UI.WebControls; 15 using System.Web.UI.HtmlControls; 16 17 namespace Reservation 18 { 19 public class MakeReservation : 20 System.Web.UI.MobileControls.MobilePage 21 { 22 protected System.Web.UI.MobileControls.'Label 23 instructionLabel; 24
868 Гпава 22 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 8U 81 82 83 84 85 86 87 88 89 protected System.Web.UI.MobileControls.Label seatLabel; protected System.Web.UI.MobileControls.SelectionList seatList; protected System.Web.UI.MobileControls.Label classlabel; protected System.Web.UP.MobileControls.SelectionList classList; protected System.Web.UI.MobileControls.Command reservecommand; ''W protected System.Web.UI.MobileControls.Form conf i rmat ionForm; protected System.Web.UI.MobileControls.Form reservationForm; protected System.Web.UI.MobileControls.Label conf irmat ionLabe1; protected System.Web.UI.MobileControls.Label confirmSeatLabel; protected System.Web.UI.MobileControls.Label confirmClassLabel; private void Page_Load(object sender, System.EventArgs e) { // Ввод пользовательского кода для инициализации страницы } // код, сгенерированный Visual Studio .NET private localhost.Reservation agent = new localhost.Reservation(); private void reserverCommand_Click( object sender, System.EventArgs e) ( ActiveForm = confirmationForm; } // reserveCommand_Click private void confirmationForm_Activate( object sender, System.EventArgs e) { // если Web-метод возвращает true, сигнализировать успешное // бронирование if (agent.Reserve(seatList.Selection.Text, classList.Selection.Text)) { confirmationLabel.Text = "Your reservation has " + "been made. Thank You!"; confirmSeatLabel.Text = "Seat: " + seatList.Selection.Text; confirmClassLabel.Text = "Class: " + classList.Selection.Text; } else ( confirmationLabel.Text = "This seat is not " + "available, please hit your browser's Back " + "button and try again."; }
Mobile Internet Toolkit 869 90 91 } // confirmationForm_Active 92 93 } // конец класса MakeReservation 94 95 } // конец пространства имен Reservation а б View Tools , <312- '1*3 ittr//l92.168 1 113/ге в atl '/( ▼ Please select the type of seat and class you wish to reserve: Seat: |Middte -| Class' Riserve1 Рис. 22.14. Бронирование авиабилетов: a — форма для выбора места и класса в IE; б — сообщение о невозможности выполнить заказ; в — форма для выбора места и класса в микробраузере; г — сообщение об удачном заказе; д — форма для выбора места и класса в Pocket PC Emulator; е — сообщение об удачном заказе В строках 58 и 59 создается объект Reservation (т. е. экземпляр Web-службы). При нажатии пользователем кнопки Reserve! исполняется обработчик события reserveCommand_click, и страница перезагружается. Обра- ботчик события (строки 68—91) вызывает метод Reserve Web-службы и передает в него в качестве аргументов выбранное место и тип класса. Если метод Reserve возвращает значение true, тогда приложение отображает сообщения благодарности пользователю за осуществленное бронирование; в противном случае пользователь уведомляется об отсутствии места запрошенного типа, и ему предлагается повторить попытку. Результат работы приложения представлен на рис. 22.14. Ну, вот, кажется, и все... Авторы искренне надеются, что эта книга оказалась для вас интересной.
870 Гпава 22 22.8. Резюме Мобильный инструментарий для Интернета (MIT) от корпорации Microsoft обеспечивает разработку мобильных Web-приложений. MIT расширяет функциональность Visual Studio .NET, представляя собой мощный инстру- мент с применением .NET Framework для создания Web-содержимого для мобильных устройств на таких язы- ках, как C# и Visual Basic .NET. Протокол беспроводного приложения (WAP) — это набор протоколов связи, предназначенных для подключе- ния беспроводных устройств к Интернету. Язык разметки для беспроводных систем (WML) — это язык размет- ки, используемый для описания содержимого WAP-устройств. Для беспроводной передачи содержимого WAP поддерживает WML. MIT поддерживает клиентов трех типов: устройства HTML, устройства WAP и устройства iMode. Устройства HTML отображают разметку HTML, например, в Internet Explorer или PocketPC. При создании мобильного Web-приложения в Visual Studio .NET генерируется страница ASPX (с расширением файла aspx), содержащая разметку для ее пользовательского интерфейса. Бизнес-логика приложения может быть включена в файл ASPX в виде сценария, либо в файл фонового кода. Вся логика исполняется на Web- сервере. Мобильные элементы управления Web-формами (мобильные элементы) представляют собой объекты, приме- няемые для создания мобильных приложений. В число основных мобильных элементов входят метки и кнопки. Атрибут runat открывающего тега каждого мобильного элемента имеет значение "server" для указания на то, что элемент должен исполняться на сервере. MIT определяет возможность размещения нескольких элементов управления на одной линии проверкой размера экрана устройства. Данные о размере экрана устройства и прочая аппаратная информация (поддержка цветной графики, либо возможность создания телефонного вызова) сохраняются в файле machine .config. Элемент управления stylesheet представляет собой набор элементов style. Многие элементы MIT обладают свойством styleReference, задающим гарнитуру шрифта элемента управления, его цвет, выключку и т. д. Эле- менты управления stylesheet позволяют разработчикам задавать стиль элементов страницы (заголовки, шриф- ты, цвета и т. д.) отдельно от структуры документа (т. е. от мобильных тегов), а также конфигурировать прило- жения под определенные устройства (аппаратно-зависимая визуализация). Элемент Devicespecific используется при необходимости работы мобильного приложения на разных устройст- вах. Элемент Devicespecific содержит один или несколько элементов Choice. Элемент Choice содержит атри- бут Filter, сравниваемый с элементами Filter, заданными в элементе deviceFilters в файле web.config. Каж- дый элемент filter в файле Web.config содержит три атрибута: name, compare и argument. Значение атрибута name выбирается из предварительно составленного программистом списка в файле Web.config. Значением атри- бута compare может быть одним из предварительно заданных свойств браузера в файле machine.config. Атри- бут argument — ожидаемое значение свойства браузера. 22.9. Ресурсы Интернета и WWW □ msdn.microsoft.com/vstudio/nextgen/technology/mobilewebforms.asp На сайте в режиме online представлено введение в Web-формы, а также другие полезные ссылки. □ www.devx.com В данном ресурсе пока не содержится информации о MIT, но представлена информация о беспроводных технологиях и .NET, включая дискуссии, службу оперативной справки и коды. □ www.w3.org/TR/1998/NOTE-compactHTML-19980209 На сайте представлена подробная информация о cHTML и устройствах, поддерживающих данную технологию. □ msdn.microsoft.com/library/default.asp?url==/library/en-us/mwsdk/html/mwstartpage.asp Сайт документации, на котором представлена базовая информация о MIT, а также ссылки на документацию по более конкретным темам, например, о времени прогона MIT. □ www.asp.net/Defau!t.aspx?tabindex=4&tabid=44 Универсальный сайт по ASP.NET с ссылками на несколько загружаемых приложений и примеров с исполь- зованием MIT. Группы новостей: □ microsoft.public.dotnet.framework.aspnet.mobile На данном сайте группы новостей Microsoft часто размещаются вопросы и ответы по специфическим темам, связанным с MIT. Доступен для взаимодействия и обмена опытом программистов любых уровней профес- сионализма.
ПРИЛОЖЕНИЕ 1 Приоритет операций В табл. П1.1 операции представлены в нисходящем порядке приоритета сверху вниз; каждый уровень приорите- та выделен горизонтальной линией’. Таблица П1.1. Карта приоритета операций Операция/оператор Название Ассоциативность Доступ к элементу Слева направо 0 Скобочное выражение Слева направо [] Доступ к элементу Слева направо ++ Постфиксное инкрементирование Слева направо — Постфиксное декрементирование Слева направо new Операция создания объекта Слева направо typeof Операция получения объекта для указанного типа Слева направо checked Оператор включения проверки на переполнение при целочисленных операциях Слева направо unchecked Оператор выключения проверки на переполнение при целочисленных операциях Слева направо + Унарный плюс Слева направо - Унарный минус Слева направо i Операция логического отрицания Слева направо Операция побитового отрицания Слева направо ++ операнд Префиксное инкрементирование Слева направо — операнд Префиксное декрементирование Слева направо * Умножение Слева направо / Деление Слева направо % Деление по модулю Слева направо + Сложение Слева направо - Вычитание Слева направо « Сдвиг влево Слева направо » Сдвиг вправо Слева направо < Меньше Слева направо > Больше Слева направо 1 Данная карта приоритета операций основана на разд. 7.2.1. Подробная информация размещена на сайте msdn.microsoft.com /library/defaulLasp?url=/library/en-us/csspec/htmI/XCharpSpecStart.asp)
872 Приложение 1 Таблица П1.1 (окончание) Операция/оператор Название Ассоциативность <= Меньше или равно Слева направо >= Больше или равно Слева направо is Сравнение типов Слева направо == Равно Слева направо 1 к Не равно Слева направо & Побитовая операция логического "И" (AND) ~ Слева направо А Побитовая операция логического исключающего "ИЛИ" (XOR) Слева направо 1 Побитовая операция логического "ИЛИ" (OR) Слева направо && Условное "И" (AND) Слева направо II Условное "ИЛИ" (OR) Слева направо Условная операция Справа налево = Присвоение Справа налево Присвоение с умножением Справа налево /= Присвоение с делением Справа налево += Присвоение со сложением Справа налево ~= Присвоение с вычитанием Справа налево «= Присвоение со сдвигом влево Справа налево »= Присвоение со сдвигом вправо Справа налево &= Присвоение с логическим "И" Справа налево А= Присвоение с логическим исключающим "ИЛИ" Справа налево |в Присвоение с логическим "ИЛИ" Справа налево
ПРИЛОЖЕНИЕ 2 (ш Системы счисления Здесь только числа признают. Уильям Шекспир В природе существует определенного рода арифметико-геометрическая сис- тема координат, потому что в природе встречаются модели любых типов. Все знания о ней мы получаем в виде моделей, а все природные модели — изуми- тельны по своей красоте. Меня поразило то, что вся система природы, должно быть, и есть воплоще- ние красоты, потому что в химии мы обнаруживаем, что ассоциации всегда выражаются замечательными целыми числами, без дробей. Ричард Бакминстер Фуллер Темы данного приложения: П основные концепции систем счисления, такие как основание, значение позиции и значение символа, □ принципы работы с числами, представленными в двоичной, восьмеричной и шестнадцатеричной системах счисления; □ перевод двоичных чисел в восьмеричные или шестнадцатеричные числа; □ преобразование восьмеричных и шестнадцатеричных чисел в двоичные; □ прямое и обратное преобразования десятичных чисел в их двоичные, восьмеричные и шестнадцатеричные эквиваленты; □ понятие двоичной арифметики и механизма представления отрицательных двоичных чисел с помощью представления дополнения двойки. П2.1. Введение В данном приложении представлены основные системы счисления, используемые в программировании, осо- бенно при работе над проектами, требующими от разработчиков тесного взаимодействия с аппаратными сред- ствами "машинного уровня". В число подобных проектов входят операционные системы, сетевые программы, компиляторы, системы управления базами данных, а также программные приложения, требующие высокой производительности. При записи в программе целого числа, например, 227 или -63 считается, что оно находится в десятичной (ос- нование— 10) системе счисления. Цифры в десятичной системе счисления — 0, 1, 2,..., 9. Наименьшая циф- ра — 0, а наибольшая — 9 — на единицу меньше основания 10. В компьютерах используется двоичная (основа- ние — 2) система счисления. В ней используется только две цифры: 0 и 1. Наименьшая цифра — 0, а наиболь- шая — 1 — на единицу меньше основания 2. В табл. П2.1 в общем виде представлены цифры, используемые в двоичной, восьмеричной, десятичной и шестнадцатеричной системах счисления. Замечено, что двоичные числа имеют намного большую длину, нежели их десятичные эквиваленты. Програм- мисты, работающие с ассемблерными языками, а также на высокоуровневых языках, обеспечивающих дости- жение "машинного уровня", считают неудобным использование двоичных чисел. Именно поэтому стали попу- лярными восьмеричная (основание — 8) и шестнадцатеричная (основание — 16) системы счисления В восьмеричной системе счисления диапазон цифр — от 0 до 7. По причине того, что двоичная и восьмеричная системы счисления имеют меньше цифр, нежели десятичная, то их цифры соответствуют тем же цифрам в деся- тичной системе счисления.
874 Приложение 2 Таблица П2.1. Цифры двоичной, восьмеричной, десятичной и шестнадцатеричной систем счисления Двоичная цифра Восьмеричная цифра Десятичная цифра Шестнадцатеричная цифра 0 0 0 0 1 1 1 1 2 2 2 3 3 3 4 4 4 5 5 5 6 6 6 7 7 7 8 8 9 9 А (десятичное значение 10) В (десятичное значение 11) С (десятичное значение 12) D (десятичное значение 13) Е (десятичное значение 14) F (десятичное значение 15) Шестнадцатеричная система счисления — немного более сложная, потому что она требует шестнадцати цифр: наименьшая из них — 0, а наибольшая имеет значение, эквивалентное десятичному числу 15 (на единицу мень- ше основания 16). Традиционно используются буквы от А до F для обозначения шестнадцатеричных цифр, со- ответствующих значениям от 10 до 15. Таким образом, в шестнадцатеричной системе счисления можно полу- чить, например, число 876, полностью состоящее из цифр десятичной системы, число 8A55F, состоящее из цифр и букв, и такие числа, как FFE, полностью состоящие из букв. Иногда получается так, что шестнадцате- ричное число может выглядеть как полноценное английское слово, например, FACE или FEED; программистам, привыкшим работать с числами, это может показаться странным. В табл. П2.2 в общем виде представлена каж- дая из систем счисления. Таблица П2.2. Сравнение двоичной, восьмеричной, десятичной и шестнадцатеричной систем счисления Атрибут Система счисления - двоичная восьмеричная десятичная шестнадцатеричная Основание 2 8 10 16 Наименьшая цифра 0 0 0 0 Наибольшая цифра 1 7 9 F В каждой из них используется позиционное представление, т. е. каждая позиция, в которой записана цифра, имеет свое значение. Например, о десятичном числе 937 (9, 3 и 7 называются значениями символов) говорится, что цифра 7 записана в позиции единиц, 3 — в позиции десятков, а 9 — в позиции сотен. Следует обратить вни- мание, что каждая из этих позиций представляет собой степень основания 10, и эти степени начинаются с 0 и увеличиваются на единицу при перемещении к левой стороне числа (табл. П2.3). Для более длинных десятичных чисел следующими позициями влево будут тысячная (103), десятитысячная (104), стотысячная (10s), миллионная (10е), десятимиллионная (107) и т. д. О двоичном числе 101 говорится, что правая единица записана в позиции единиц, 0 — в позиции двоек, а левая единица — в позиции четверок. Каждая из этих позиций является степенью основания 2, и степени начинаются с 0 и увеличиваются на единицу при перемещении к левой стороне числа (табл. П2.4). Для более длинных двоичных чисел следующими позициями влево будут восьмая (23), шестнадцатая (24), тридцать вторая (2s), шестьдесят четвертая (2б) и т. д.
Системы счисления 875 Таблица П2.3. Значения позиций в десятичной системе счисления Характеристика Значение Десятичная цифра 9 3 7 Название позиции Сотни Десятки Единицы Значение позиции 100 10 1 Значение позиции как степень основания 10 102 101 10° Таблица П2.4. Значения позиций в двоичной системе счисления Характеристика Значение Двоичная цифра 1 0 1 Название позиции Четверки Двойки Единицы Значение позиции 4 2 1 Значение позиции как степень основания 2 22 21 2° О восьмеричном числе 425 говорится, что цифра 5 записана в позиции единиц, 2 — в позиции восьмерок, а 4 — в позиции шестьдесят четвертых. Каждая из этих позиций является степенью основания 8, и степени начинают- ся с 0 и увеличиваются на единицу при перемещении к левой стороне числа (табл. П2.5). Для более длинных восьмеричных чисел следующими позициями влево будут пятьсот двенадцатая (83), четыре тысячи девяносто шестая (84), тридцать две тысячи семьсот шестьдесят восьмая (85) и т. д. Таблица П2.5. Значения позиций в восьмеричной системе счисления Характеристика Значение Десятичная цифра 4 2 5 Название позиции Шестьдесят четвертые Восьмерки Единицы Значение позиции 64 8 1 Значение позиции как степень основания 8 82 81 8° О шестнадцатеричном числе 3DA говорится, что А записывается в позиции единиц, D — в позиции шестнадца- тых, а 3 — в позиции двести пятьдесят шестых. Каждая из этих позиций является степенью основания 16, и сте- пени начинаются с 0 и увеличиваются на единицу при перемещении к левой стороне числа (табл. П2.6). Для более длинных шестнадцатеричных чисел следующими позициями влево будут четыре тысячи девяносто шестая (163), шестьдесят пять тысяч пятьсот тридцать шестая (164) и т. д. Таблица П2.6. Значения позиций в шестнадцатеричной системе счисления Характеристика Значение Десятичная цифра 3 D А Название позиции Двести пятьдесят шестые Шестнадцатые Единицы Значение позиции 256 16 1 Значение позиции как степень основания (16) 162 161 16° П2.2. Перевод двоичных чисел в восьмеричные и шестнадцатеричные числа Основным предназначением восьмеричных и шестнадцатеричных чисел в вычислительной технике является сокращение длинных двоичных представлений. В табл. П2.7 показано, что длинные двоичные числа можно за- писывать короче в системах счисления с большими основаниями, чем в двоичной.
876 Приложение 2 Таблица П2.7. Десятичные, двоичные, восьмеричные и шестнадцатеричные эквиваленты Десятичное число Двоичное представление Восьмеричное представление Шестнадцатеричное представление 0 0 0 0 1 1 1 1 2 10 2 2 3 11 3 3 4 100 4 4 5 101 5 5 6 110 6 6 7 111 7 7 8 1000 10 8 9 1001 , 11 9 10 1010 12 А 11 1011 13 В 12 1100 14 С 13 Г 1101 15 D 14 1110 16 Е 15 1111 17 F 16 10000 20 10 Принципиально важная взаимосвязь восьмеричной и шестнадцатеричной систем с двоичной системой счисле- ния заключается в том, что основания восьмеричной и шестнадцатеричной систем (8 и 16 соответственно) являются степенями основания двоичной системы (2). Рассмотрим следующее двоичное число, состоящее из 12 цифр, и его восьмеричный и шестнадцатеричный эквиваленты. Смогут ли читатели определить, насколько удобнее сокращать двоичные числа в восьмеричной или шестнадцатеричной системах? Ответ следует за числами. Двоичное число Восьмеричный эквивалент Шестнадцатеричный эквивалент 100011010001 4321 8D1 Чтобы понять, как двоичное число легко преобразуется в восьмеричное, следует просто разбить 12-значное двоичное число на группы, состоящие из трех последовательных битов каждая, и записать эти группы над соот- ветствующими цифрами восьмеричного числа следующим образом: 100 011 010 001 4 3 2 1 Обратите внимание, что восьмеричная цифра, записанная под каждой группой из трех битов, точно соответст- вует восьмеричному эквиваленту 3-значного двоичного числа, как показано в табл. П2.7. Тот же тип взаимосвязи можно наблюдать при преобразовании чисел из двоичной в шестнадцатеричную систе- му счисления. В частности, разбейте 12-значное двоичное число на группы из четырех последовательных битов в каждой группе и запишите эти группы над соответствующими цифрами шестнадцатеричного числа следую- щим образом: 1000 1101 0001 8 D 1 Обратите внимание, что шестнадцатеричная цифра, записанная под каждой группой из четырех битов, точно соответствует восьмеричному эквиваленту 4-значного двоичного числа, как показано в табл. П2.7.
Системы счисления 877 П2.3. Преобразование восьмеричных и шестнадцатеричных чисел в двоичные В предыдущем разделе было продемонстрировано преобразование двоичных чисел в их восьмеричные и шест- надцатеричные эквиваленты путем образования групп двоичных цифр и простой записи этих групп как их экви- валентов восьмеричных или шестнадцатеричных значений. Данный процесс можно осуществлять обратно для получения двоичных эквивалентов восьмеричных или шестнадцатеричных чисел. Например, восьмеричное число 653 преобразуется в двоичное простой записью цифры 6 в виде 3-значного дво- ичного эквивалента ПО, цифры 5 — в виде 3-значного двоичного эквивалента 101, а цифры 3— в виде ее 3-значного двоичного эквивалента 011 для получения 9-значного двоичного числа 110101011. Шестнадцатеричное число FAD5 преобразуется в двоичное простой записью буквы F в виде 4-значного двоич- ного эквивалента 1111, буквы А— в виде 4-значного двоичного эквивалента 1010, буквы D— в виде ее 4-значного двоичного эквивалента 1101, а цифры 5 — в виде ее 4-значного двоичного эквивалента 0101 для по- лучения 16-значного числа 1111101011010101. П2.4. Преобразование двоичных, восьмеричных или шестнадцатеричных чисел в десятичные Из-за привычки пользоваться десятичными числами часто бывает удобнее преобразовывать двоичные, восьме- ричные или шестнадцатеричные числа в десятичные для получения наглядного представления о том, какое это число "в миру". В табл. П2.1—П2.6 значения позиций выражены в десятичных числах. Для преобразования лю- бого числа со своим основанием в десятичное число нужно умножить десятичный эквивалент каждой его циф- ры на значение позиции и сложить полученные произведения. Например, в табл. П2.8 показано, как двоичное число 110101 преобразовано в десятичное число 53. Для преобразования восьмеричного числа 7614 в десятичное число 3980 используется та же методика, но с со- ответствующими восьмеричными значениями позиций, как показано в табл. П2.9. Таблица П2.8. Преобразование двоичного числа в десятичное Характеристика Преобразование двоичного числа в десятичное Значения позиций 32 16 8 4 2 1 Значения символов 1 1 0 1 0 1 Произведения 1 х 32 = 32 1 х 16= 16 0x8 = 0 1x4 = 4 0x2 = 0 1x1 = 1 Сумма = 32+ 16 + 0 + 4 + 0 + 1= 53 Таблица П2.9. Преобразование восьмеричного числа в десятичное Характеристика Преобразование восьмеричного числа в десятичное Значения позиций 512 64 8 1 Значения символов 7 6 1 4 Произведения 7x512 = 3584 6 х 64 = 384 1x8 = 8 4x1=4 Сумма = 3584 + 384 + 8 + 4 = 3980 Для преобразования шестнадцатеричного числа AD3B в десятичное число 44347 используется тот же способ с соответствующими шестнадцатеричными значениями позиций, как показано в табл. П2.10. Таблица П2.10. Преобразование шестнадцатеричного числа в десятичное Характеристика Преобразование шестнадцатеричного числа в десятичное Значения позиций 4096 256 16 1 Значения символов А D 3 В Произведения А х 4096 = 40960 D х 256 = 3328 Зх 16 = 48 Вх 1 = 11 Сумма = 40960 + 3328 » 48 + 11 =44347
878 Приложение 2 П2.5. Преобразование десятичных чисел в двоичные, восьмеричные или шестнадцатеричные Рассмотренные в предыдущем разделе преобразования вытекают из правил позиционного представления. Пре- образование десятичных чисел в двоичные, восьмеричные и шестнадцатеричные также придерживается этих правил. Предположим, что необходимо преобразовать десятичное число 57 в двоичное Начнем с записи позиционных значений столбцов справа налево до столбца, значение позиции которого^5олыие десятичного числа. Этот стол- бец не нужен, поэтому он отбрасывается. Следовательно, для начала записываются позиции: 64 32 16 8 4 2 1 После этого столбец со значением позиции 64 отбрасывается, и остается 32 16 8 4 2 1 Начнем с крайнего левого столбца вправо. Разделим 32 на 57; получается 32 с остатком 25, поэтому в столбец 32 записывается единица. Разделим 16 на 25, получим 16 с остатком 9: в столбец 16 записывается единица. Раз- делим 8 на 9, получим 8 с остатком 1. Частное каждого из следующих двух столбцов при делении их значений позиции на 1 равно нулю, поэтому в 4-й и 2-й столбцы записывается 0. Наконец, при делении единицы на саму себя получается единица, поэтому в первый столбец записывается 1. Это дает: □ значения позиций: 32 16 8 4 2 1 □ значения символов: 1 110 0 1 Поэтому десятичное число 57 эквивалентно двоичному числу 111001. Для преобразования десятичного числа 103 в восьмеричное число начнем с записи значений позиций столбцов до столбца, значение позиции которого больше десятичного числа. Этот столбец не нужен, поэтому он отбра- сывается. Следовательно, для начала записываются позиции: 512 64 8 1 После этого столбец со значением позиции 512 отбрасывается, и остается. 64 8 1 Начнем с крайнего левого столбца вправо. Разделим 64 на 103; получается 64 с остатком 39, поэтому в столбец 64 записывается единица. Разделим 8 на 39, получим 4 с остатком 7: в столбец 8 записывается 4. Наконец, при делении единицы на 7 получается 7 без остатка, поэтому в первый столбец записывается 7. Это дает: □ значения позиций: 64 8 1 □ значения символов: 14 1 Поэтому десятичное число 103 эквивалентно восьмеричному числу 147. Для преобразования десятичного числа 375 в шестнадцатеричное число начнем с записи значений позиций столбцов до столбца, значение позиции которого болыпе десятичного числа. Этот столбец не нужен, поэтому он отбрасывается. Следовательно, для начала записываются позиции: 4096 256 16 1 После этого столбец со значением позиции 4096 отбрасывается, и остается: 256 16 1 Начнем с крайнего левого столбца вправо. Разделим 256 на 375; получается 256 с остатком 119, поэтому в стол- бец 256 записывается единица. Разделим 16 на 119, получим 7 с остатком 7: в столбец 16 записывается 7. Нако- нец, при делении единицы на 7 получается 7 без остатка, поэтому в первый столбец записывается 7. Это дает: П значения позиций: 256 16 1 □ значения символов: 1 7 1 Поэтому десятичное число 375 эквивалентно шестнадцатеричному числу 177
Системы счисления 879 П2.6. Отрицательные двоичные числа: запись дополнения двойки До сих пор в приложении рассматривались положительные числа. В данном разделе объясняется механизм представления компьютерами отрицательных чисел с помощью представления дополнения двойки. Для начала рассматривается образование дополнения двойки двоичного числа, а затем объясняется, почему оно обозначает отрицательное значение конкретного двоичного числа. Рассмотрим машину с 32-битовыми целыми числами. Предположим, что int number =13; 32-битовое представление числа number — 00000000 00000000 00000000 00001101 Для образования отрицательного значения числа number сначала образуется его дополнение единицы примене- нием оператора Л С#: pnesComplement = number л 0x7FFFFFFF; По сути дела, теперь onesComplement — число number, в котором каждый бит обращен: единицы превратились в нули, и наоборот: number 00000000 00000000 00000000 00001101 onesComplement: 11111111 11111111 11111111 11110010 Для образования дополнения двойки числа number просто добавим единицу к дополнении единицы числа number: Таким образом, дополнение двойки числа number равно 11111111 11111111 11111111 11110011 Теперь, если это число равно 13, тогда его можно прибавить к двоичному числу 13 и получить нулевой резуль- тат. Попробуем следующую схему: 00000000 00000000 00000000 00001101 +11111111 11111111 11111111 11110011 00000000 00000000 00000000 00000000 Двоичный разряд переноса из крайнего левого столбца отбрасывается, и в результате получается 0. Если приба- вить дополнение единицы любого числа к данному числу, то результатом будут одни единицы. Ключ к получе- нию результата, состоящего из одних нулей, заключается в том, что дополнение двойки на единицу больше, чем дополнение единицы. Добавление единицы обуславливает прибавление в каждом столбце к нулю с битом пере- носа, равным единице. Бит переноса продолжает перемещаться влево до тех пор, пока он не будет отброшен из крайнего левого бита; отсюда полученное число равно нулю. В сущности, компьютеры выполняют следующую операцию вычитания: х = а = number; прибавлением дополнения двойки числа number к а следующим образом: х = а + ( onesComplement + 1 ); Предположим, что а — 27, a number — 13, как представлено выше. Если дополнение двойки числа number явля- ется отрицательным числом числа number, тогда прибавление дополнения двойки числа number к а должно дать результат 14. Проверим следующее выражение: а (т. е. 27) 00000000 00000000 00000000 00011011 +( oneComplement + 1 ) + 11111111 11111111 11111111 11110011 00000000 00000000 00000000 00001110 которое, на самом деле, равно 14.
880 Приложение 2 П2.7. Резюме При записи в программе C# целого числа, например, 19,227 или -63 считается, что оно находится в десятичной (основание— 10) системе счисления. Цифры в десятичной системе счисления — 0,1, 2,..., 9. Наименьшая циф- ра — 0, а наибольшая — 9 (на единицу меньше основания 10). В компьютерах используется двоичная (основание — 2) система счисления. В ней есть только две цифры: 0 и 1. Наименьшая цифра — 0, а наибольшая — 1 (на единиц} меньше основания 2). Отрицательные числа представ- лены в компьютерах с помощью дополнения двойки. В восьмеричной системе счисления диапазон цифр — от 0 до 7. Шестнадцатеричная система счисления требует шестнадцати цифр: наименьшая из них — 0, а наибольшая имеет значение, эквивалентное десятичному числу 15 (на единицу меньше основания 16). Традиционно используются буквы от А до F для обозначения шестнадцате- ричных цифр, соответствующих значениям от 10 до 15.
ПРИЛОЖЕНИЕ 3 (Ш Возможности карьерного роста Что есть город, как не люди? Уильям Шекспир Великий город — тот, в котором живут великие мужчины и женщины; даже если это несколько потрепанных хижин,— все равно, это— величайший город в мире. \ Уолт Уитмен Чтобы понять истинные качества людей, необходимо понять, что у них на уме, изучить их симпатии и антипатии. Марк Аврелий JfyaOi создана для действия; пока в силах, она не ведает отдыха. Праздность развращает ее. Все напрасно, если душа не сможет просыпаться, думать и чувствовать. Томас Трэрн Темы данного приложения: П исследование различных интерактивных профессиональных служб трудоустройства; □ анализ преимуществ и недостатков поиска работы через Интернет; □ обзор основных Web-сайтов трудоустройства для соискателей рабочих мест; □ исследование основных интерактивных служб трудоустройства для работодателей. Д. П3.1. Введение i На сегодняшний день в Интернете насчитывается порядка 40 000 специализированных служб трудоустройства и карьерного роста. В их число входят как обширные универсальные порталы, такие как Monster.com, так и сай- ты по интересам, например JustJavaJobs.com Компании могут заметно сократить время, затрачиваемое на по- иск квалифицированных специалистов, путем создания на своих корпоративных сайтах рекрутинговых функ- ций, либо по договоренности с сайтами трудоустройства. Результатом таких действий становится более мощ- ный поток квалифицированных соискателей, поскольку интерактивные службы Интернета могут автоматически отбирать и отклонять резюме, исходя из критериев, определенных пользователями. Процесс приема на работу также значительно ускоряют проведение собеседований в режиме online, службы тестирования и другие ресурсы. Поиск работы через Интернет — сравнительно новый метод изучения карьерных возможностей. Сетевые служ- бы трудоустройства рационализируют данный процесс, давая соискателям возможность сосредоточения усилий на поиске действительно интересных для них видов деятельности. Соискатели могут рассматривать предложе- ния, исходя из географического положения, должности, зарплаты или пакетов льгот, предлагаемых работода- телями. Соискатели могут научиться правильно составлять резюме и сопроводительные письма, размещать их в сети, а также просматривать списки должностей в поиске максимально соответствующих их предпочтениям. В Интер- нете легко можно найти вакансии начального уровня, интересующие лиц, в первый раз ищущих работу в той или иной области, работу по контракту, руководящие должности и должности руководителей среднего звена. При поиске работы через Интернет соискатели обнаружат для себя множество функций, экономящих время. В их число входят сохранение и рассылка резюме в цифровом виде, сообщение о возможных вакансиях по элек- тронной почте, вычислители заработной платы и перераспределения, инструкции по поиску работы, средства самооценки и информация по повышению уровня профессиональной подготовки. 56 Зак. 3333
882 Приложение 3 В данном приложении авторы рассматривают службы трудоустройства с точки зрения работодателя и служаще- го. Здесь обсуждаются Web-сайты, на которых можно размещать заявления, просматривать списки вакансий и соискателей. Также в приложении рассматриваются службы, модифицирующие Web-страницы по трудоустрой- ству непосредственно в электронном бизнесе. П3.2. Интернет-ресурсы для соискателей Поиск работы через Интернет значительно сокращает время, необходимое для подачи заявления на ту или иную вакансию. Вместо просмотра газет и рассылки резюме соискатели могут делать запросы конкретных должно- стей в конкретных отраслях промышленности с помощью механизмов поиска. Некоторые Web-сайты предос- тавляют соискателям возможность настройки так называемых "умных посредников" для поиска подходящей работы. "Умные посредники" — это программы, осуществляющие просмотр и упорядочивание больших объе- мов данных с созданием ответов, основанных на этих данных. Обнаружив потенциальное соответствие, про- грамма-посредник отправляет его на почтовый ящик соискателя. В них имеется возможность сохранения резю- ме в цифровом виде, оперативного внесения изменений для соответствия требованиям и мгновенной рассылки по электронной почте. Посетив корпоративный сайт компании, потенциальный кандидат может получить о ней более подробную информацию. Большинство сайтов трудоустройства бесплатны для доступа соискателей, но с компаний-работодателей взимается плата за размещение объявлений о вакансиях; дополнительный доход сай- там также приносит продажа рекламных площадей на их страницах (см. Monster.com). Monster.com_____________________________________________________________________ Реклама Суперкубка по американскому футболу сделала сайт Monster.com одной из самых узнаваемых торго- вых марок (рис. П3.1). Фактически, всего через сутки после финала Суперкубка XXXIV на сайте Monster.com появилось 5 млн соискателей работы. Сайт обеспечивает поиск рабочих мест с возможностью размещения ре- зюме, просмотр списков вакансий, предлагает рекомендации и информацию о процессе поиска работы и упреж- дающих действиях для карьерного роста. Для соискателей эти службы бесплатны. Работодатели могут разме- щать списки вакансий, просматривать базы данных резюме и получать соответствующие привилегии. Разместить бесплатно резюме на Monster.com очень просто. В Monster.com имеется компоновщик резюме, обеспечивающий пользователей возможностью размещения резюме в течение 15—30 минут. На сервере Monster.com каждый пользователь может сохранить до 5 резюме и сопроводительных писем. Некоторые компа- нии предлагают свои вакансии непосредственно через сайт Monster.com. Monster.com размещает объявления о вакансиях всех основных категорий в каждом штате. Будучи одним из ведущих Web-сайтов трудоустройства в Интернете, Monster.com — хорошее место для начала карьеры или для получения дополнительной информа- ции о процессе поиска. Рис. П3.1. Домашняя страница сайта Monster.com Для нахождения нужных вакансий такие службы трудоустройства, как FlipDog.com, осуществляют просмотр списка сайтов работодателей. При просмотре ссылок на сайты работодателей сайт FlipDog.com может иденти-
Возможности карьерного роста 883 фицировать должности компаний любых размеров. Данная функция позволяет соискателям находить вакансии, которые могут не размещаться за пределами корпоративных сайтов. Соискатели могут войти на сайт FlipDog.com и выбрать по штатам регион, в котором необходимо найти вакан- сию. Также доступен всемирный поиск. После выбора пользователем региона сайт FlipDog.com запрашивает категорию нужной должности, в которую входят несколько пунктов. После пользовательского выбора открыва- ется список местных работодателей. Можно указать работодателя сразу, либо запросить просмотр сайтом FlipDog.com баз данных трудоустройства для поиска должностей, предлагаемых всеми работодателями (рис. П3.2). Рис. П3.2. Поиск вакансий на сайте FlipDog.com Другие службы, например сетевые системы трудоустройства, также полезны при поиске нужных вакансий. Та- кие сайты, как Vault.com и WetFeet.com, дают соискателям возможность задавать вопросы в специализирован- ных чатах или электронных досках объявлений о работодателях и предлагаемых ими вакансиях. Vault.com: поиск нужной вакансии в Интернете____________________________________________________ Сайт Vault.com (www.vault.com) обеспечивает потенциальных соискателей возможностью нахождения незави- симой информации о более чем 3000 компаниях. На странице Insider Research (мнение сотрудников) пользова- тели получают доступ к описанию профиля выбранной компании в любое время, пока данная информация вклю- чена в базу данных. Кроме описания профиля Vault.com здесь имеется ссылка на мнения и высказывания сотрудников компании. Чаще всего анонимные, такие сообщения снабжают перспективных кандидатов потенци- ально ценной информацией, необходимой для принятия правильного решения. Пользователи, впрочем, должны учитывать достоверность источника. Например, недовольный чем-либо сотрудник может оставить информацию, которая будет расходиться с понятиями корпоративной этики его компании. Electronic Watercooler (букв. — электронный водоохладитель) сайта Vault.com представляет собой интерак- тивную доску объявлений, на которой посетители могут размещать свои истории поиска работы, вопросы, инте- ресные факты, а также советы работодателям и соискателям. Кромегэтого, на сайте представлены электронные бюллетени и тематические статьи по поиску работы. На сайте Vault.com также имеется информация для инте- ресующихся бизнесом, юриспруденцией и обучением в аспирантурах. Размещение объявлений о трудоустройстве и службы трудоустройства для соискателей тематически разделены по функциям. В число таких служб входят VaultMatch— служба рассылки запрашиваемых объявлений, SalaryWizard, помогающая соискателям определить уровень заработной платы, на который имеет смысл пре- тендовать. На сайте также имеются оперативные руководства по совершенствованию карьерных возможностей. Сайтом могут пользоваться и работодатели. Функция HR Vault предоставляет работодателям бесплатный сайт для размещения объявлений о вакансиях. Здесь же предлагаются рекомендации по профессиональной ориен- тации сотрудников, их взаимоотношениям с работодателями, а также дополнительные интерактивные ресурсы трудоустройства.
884 Приложение 3 ПЗ.З. Интерактивные возможности для работодателей Наем сотрудников через Интернет обладает преимуществами, по сравнению с традиционным способом приема на работу. Например, объявление о вакансии в Интернете охватывает куда большую аудиторию потенциальных кандидатов, нежели объявление в местной газете. При многообразии служб, предоставляемых большинством Web-сайтов трудоустройства, затраты на размещение объявления в сети значительно ниже затрат на публика- цию информации о вакансиях в традиционных СМИ. Даже газеты, существование которых в большой степени зависит от рекламы объявлений под рубрикой "Требуются", создают свои сайты по трудоустройству. Е-факт_________________________________________________________________________________ Согласно исследованию Форрестера, 33% от фонда найма новых сотрудников современной компании среднего размера выделяются на интерактивные службы трудоустройства, а оставшиеся 66% используются в традицион- ных механизмах трудоустройства. К 2004 году ожидался рост затрат на использование интерактивных служб до 42% и снижение до 10% затрат на использование традиционных механизмов. Как правило, объявления о вакансиях, размещенные в Интернете, просматриваются большим количеством со- искателей, нежели размещенные в традиционных СМИ. Однако при этом важно не упустить преимуществ объ- единения интерактивного донесения информации с человеческим общением. Многие соискатели еще просто не привыкли к поискам работы в Интернете. Интерактивный прием на работу часто используется как средство экономии времени на процесс собеседования и принятия решения об окончательном выборе. Е-факт_________________________________________________________________________________ Компания Cisco Systems сообщает о снижении связанных с наймом затрат на 39% и о сокращении на 60% объе- ма времени, затрачиваемого на процесс приема на работу новых сотрудников. П3.3.1. Размещение в сети объявлений о вакансиях При поиске в сети кандидатов на замещение вакансии работодателям следует учитывать многие аспекты. Ин- тернет— удобное и полезное средство найма, но для получения наилучших результатов инструмент— тре- бующий тщательного планирования. В глобальной сети предоставлены вспомогательные инструменты, но ее нельзя рассматривать как абсолютную панацею для заполнения вакантных должностей. Такие Web-сайты, как WebHire (www.webhire.com), расширяют поиск компаниями подходящих кандидатов. WebHire________________________________________________________________________________ Разработанный специально для агентов по найму и работодателей, сайт WebHire (webhire.com) представляет собой многоаспектную службу, обеспечивающую работодателей сквозными решениями о найме. Службой пред- лагаются как объявления о вакансиях, так и функции поиска кандидатов. Наиболее полная служба — WebHire Enterprise — определяет местоположение и категории кандидатов, найденных системами просмотра резюме. Клиенты получают отчет с указанием наиболее подходящих ресурсов для поиска. В число других служб, доступ- ных в сети служб трудоустройства (Employment Services Network) сайта WebHire, входят предварительное от- сеивание кандидатов, инструментальные средства оценки их профессиональных навыков, а также информация о компенсационных пакетах. Юристы-консультанты оказывают помощь организациям в составлении вопросов для собеседований. WebHire Agent— это "умный посредник", осуществляющий поиск квалифицированных кадров на основании требований к работе. При обнаружении программой WebHire Agent потенциального кандидата ему направляет- ся электронное сообщение для стимулирования заинтересованности. Затем программа классифицирует соиска- телей, исходя из информации, полученной в результате поиска; информация сохраняется так, что новые соиска- тели отделяются от тех, кто уже получил электронное сообщение с сайта. Yahoo! Resumes — вспомогательная программа сайта WebHire, обеспечивающая поиск потенциальных сотруд- ников путем ввода ключевых слов в поисковый механизм Yahool Resumes. За фиксированную сумму работода- тели могут приобрести годовой абонемент на программное решение, облегчающее процесс поиска нужных кан- дидатов; плата за каждый факт использования не взимается. Существует множество сайтов, на которых работодатели могут размещать объявления о вакансиях. Услуги не- которых из них не бесплатны (взносы составляют от 100 до 200 долларов). Обычно объявления сохраняются на сайте в течение 30—60 дней. Работодатели должны следить за тем, чтобы их информация размещалась на сай- тах, которые, скорее всего, будут просматриваться подходящими кандидатами. В предыдущем разделе упоми- налось наличие множества интерактивных служб трудоустройства целевой направленности (по отраслям) и многих крупных, более подробных сайтов с базами данных, сгруппированными по категориям вакансий. При составлении объявления работодатель должен учитывать наличие в сети огромного количества объявлений подобного рода. Определение и выделение аспектов, делающих данную вакансию уникальной, включая инфор- мацию о льготах, заработной плате и т. д., могут стимулировать квалифицированного кандидата на дальнейшее, более подробное изучение предложения (табл. П3.1).
Возможности карьерного роста 885 Таблица ПЗ. 1. Список критериев соискателя Критерии соискателя Должность (обязанности) Заработная плата Местоположение Льготы (здравоохранение, стоматология, фондовые опционы! Возможности карьерного роста Временные обязательства Возможности обучения Возмещение расходов на обучение Корпоративная культура На объявления вакансий на сайте HotJob.com сделано множество ссылок с других сайтов, что повышает коли- чество потенциальных служащих. Подобно сайтам Monster.com и jobfind.com, на сайте HotJobs.com за разме- щение объявления взимается плата. При желании работодатели могут стать зарегистрированными членами сай- та HotJobs.com и смогут получать доступ к корпоративным доскам объявлений о трудоустройстве (Private Label Job Board — сайты вакансий, принадлежащие отдельным компаниям), размещенным на сайте HotJob.com, интерактивным технологиям трудоустройства и к ярмаркам вакансий. За размещение объявлений о вакансиях на сайте Job Find газеты "Бостон Херальд" (www.jobfind.com) с рабо- тодателей также взимается плата. Первоначальный взнос дает работодателю право размещения трех объявле- ний. Ограничений по длине объявлений не существует. В число других Web-сайтов, на которых работодатели могут получить службы найма, входят CareerPath.com, America's Job Bank (www.ajb.dni.us/employer), CareerWeb (www.cweb.com), Jobs.com и Career.com. П3.3.2. Недостатки трудоустройства через Интернет Большое количество кандидатур представляет определенные сложности как для соискателей, так и работодате- лей. На многих сайтах трудоустройства резюме, подходящие под требования тех или иных вакансий, обрабаты- ваются программами фильтрации резюме. Такие программы сканируют базу данных резюме по ключевым сло- вам, соответствующим описанию работы. Несмотря на то, что такие программные средства увеличивают коли- чество резюме, на которые посетители сайтов обращают внимание, они не защищены от неумелого обращения. Например, система фильтрации резюме может пропустить соискателя с профессиональными навыками, тре- бующимися на той или иной должности, либо кандидата, способности которого позволяют обучиться навыкам, необходимым для занятия данной вакансии. Цифровая передача также может создавать проблемы, потому что некоторые системные платформы не поддерживают программы организации трудоустройства. Иногда результа- том этого становится передача неформатированной информации или сбой передачи. Недостаток конфиденциальности— еще один недостаток интерактивных служб трудоустройства. Во многих случаях соискатели предпочитают искать работу анонимно. Данный факт увеличивает вероятность того, что кандидат не будет понят работодателем. Размещение резюме в Интернете повышает вероятность того, что оно попадется на глаза подходящему работодателю при найме им новых сотрудников. Традиционный способ рас- сылки резюме и сопроводительных писем потенциальным работодателям не связан с таким риском. Судя по последним исследованиям, число лиц, ищущих работу с помощью традиционных средств — по реко- мендациям, через газеты или временные агентства— намного превышает количество соискателей, пользую- щихся Интернетом. Впрочем, оптимисты считают, что такое расхождение обусловлено начальной стадией раз- вития служб электронного бизнеса. Со временем возможности размещения резюме и поиска интерактивных служб трудоустройства усовершенствуются, и время на поиск соискателями подходящих вакансий, а работода- телями — новых сотрудников, значительно сократится. ПЗ.З.З. Разнородность рабочих мест Каждая вакансия неизбежно создает собственную культуру. Список сфер ответственности, планы, сроки и про- екты так или иначе влияют на рабочую среду. Наиболее определяющими элементами корпоративной культуры, вероятно, являются сами сотрудники. Например, если бы все работники обладали одинаковыми навыками, зна-
886 Приложение 3 ниями и предлагали бы одинаковые идеи, тогда рабочей среде недоставало бы разнородности. При этом неиз- бежно пострадают творческая сторона дела и энтузиазм. Одним из способов повышения динамики организации является прием на работу людей с разными уровнями базовой подготовки и принадлежащих к разным куль- турам. Для работодателей, стремящихся к повышению разнородности рабочих мест, в Интернете существует множест- во сайтов, выделенных по демографическим признакам. При найме людей с разными базовыми навыками воз- никают новые идеи и перспективы, что помогает предприятиям и организациям охватывать более обширную аудиторию потенциальных потребителей. „ В число демографических Web-сайтов входят blackvoices.com и hirediversity.com. Сайт BlackVoices, преиму- щественно работающий как портал (новости, спорт, сводки погоды, поисковые службы), обладает возможно- стями поиска работы, а также позволяет потенциальным служащим размещать свои резюме. Сайт HireDiversity разделен на несколько категорий, включающих возможности трудоустройства для афро-американцев, латино- американцев и женщин. В других интерактивных службах трудоустройства размещаются рекламные баннеры со ссылками на этнические сайты для компаний, стремящихся к разнородности своей рабочей силы. Каталог разнородности (www.mindexchange.com) предоставляет возможности международного трудоустройст- ва. Выбрав сайт Diversity, пользователи могут искать работу, получать информацию и дополнительные ресурсы по вопросам трудоустройства. Поиск на сайте может осуществляться по демографическим признакам (афро- американцы, латиноамериканцы, альтернативные стили жизни и т. д.), либо по темам (работодатель, вакансия и т. д.) с помощью сетей ссылок. В число сайтов с возможностью поиска по характерным признакам входят BiiingualJobs.com, Latin World и American Society for Female Entrepreneurs. На многих сайтах имеются разделы, посвященные соискателям с ограниченными физическими возможностями. Помимо предоставления возможностей трудоустройства, сюда входят дополнительные ресурсы, например, до- кументы равных возможностей и интерактивные доски объявлений. National Business and Disability Council (NBDC) обеспечивает работодателей информацией об интеграции и доступе людям с физическими недостатка- ми, здесь же представлены возможности для соискателей. П3.4. Службы трудоустройства В Интернете существует множество сайтов, помогающих работодателям находить подходящих кандидатов на нужные вакансии. Время, сэкономленное на предварительном поиске в Интернете, можно посвятить собеседо- ваниям с квалифицированными кадрами и для поиска наилучших соответствий. Компания Advantage Hiring, Inc. (www.advantagehiring.com) предоставляет работодателям службу просмотра резюме. При подаче резюме потенциальным кандидатом на ту или иную вакансию Advantage Hiring, Inc. пред- ставляет Net-Interview — небольшую анкету, дополняющую информацию, изложенную в резюме. На данном сайте также представлена служба SiteBuilder, помогающая работодателям в создании Web-сайтов трудоустрой- ства Интерактивная демо-версия имеется по адресу www.advantagehiring.com. В ней пользователь поэтапно проходит всю программу Net-Interview, а также несколько других служб, предлагаемых Advantage Hiring (рис. ПЗ.З). Recruitsoft.com — провайдер служб приложений (Application Service Provider, ASP), предлагающий корпораци- ям программные средства по трудоустройству по принципу "оплата при найме". (Recruitsoft получает комис- сионные по фактам трудоустройства через их службу). Recruiter WebTop — интерактивная служба трудоуст- ройства компании. В нее входят такие функции, как хостинг сайтов, программа направлений на работу, отбор резюме, исходя из профессиональных навыков соискателей, функция отслеживания кандидатов и возможность размещения резюме на интерактивной доске объявлений. Демонстрационная версия раздела Corporate Recruiting Solutions сайта Recruiter WebTop имеется по адресу www.recruitsoft.com/process. В число других рекрутинговых служб входят Hire.com и Futurestep.com. В Интернете для работодателей также представлены рентабельные средства тестирования перспективных со- трудников по таким категориям, как способности к принятию решений, решению проблем и личных качеств. Службы, подобные, например eTest, помогают снизить затраты на тестирование кандидатов на территории компании, а также повысить эффективность процесса собеседования. Результаты тестов, представленные в виде параграфов, обеспечивают работодателей информацией о сильных и слабых сторонах заинтересованных канди- датов. На основе этих результатов в отчете предлагаются методики собеседования, например, постановка во- просов, допускающих различные толкования, — требующих не только ответа "да" или "нет". Примеры отчетов и бесплатные пробные тесты имеются по адресу www.etest.net. Работодатели и соискатели также могут найти упражнения по процессам трудоустройства на сайте www.advisorteam.net/User/ktsintro.asp. Некоторые из этих служб платные. В тестах задается несколько вопро-
Возможности карьерного роста 887 сов, касающихся интересов кандидата и его стиля работы. Результаты помогают соискателям определить луч- ший для себя выбор. Рис. ПЗ.З. Служба Net-Interview компании Advantage Hiring, Inc. (Предоставлено Advantage Hiring. Inc.) П3.5. Web-сайты трудоустройства и вакансий Интерактивные сайты трудоустройства бывают как универсальные, так и посвященные отдельным отраслям промышленности. В следующих разделах рассматривается несколько сайтов, в которых объединены потребно- сти как соискателей, так и работодателей: технические вакансии, возможности внештатной работы, а также тру- доустройство на контрактной основе. ПЗ.5.1. Универсальные сайты трудоустройства Как упоминалось выше, в Интернете существует много сайтов, обеспечивающих соискателей возможностями трудоустройства в разных сферах деятельности. Крупнейшим из них является сайт Monster.com, привлекающий самое большое количество посетителей в месяц. В число других популярных сайтов трудоустройства входят JobsOnline.com, HotJobs.com, www.jobtrak.com (сайт Monster.com) и headhunter.net1. Поиск работы через Интернет может осуществляться в два этапа. Например, во время первого посещения сайта JobsOnline.com от пользователя требуется заполнение бланка регистрации. В нее входит основная информация: имя, адрес и сфера интересов. После регистрации члены могут просматривать предложения вакансий по таким критериям, как категория работы, местоположение, а также количество дней, которое данное предложение дей- ствительно. Для дополнительной связи здесь же представлена контактная информация. ПЗ.5.2. Технические вакансии По мере развития и распространения Интернета активным спросом на рынке труда пользуются технические специалисты. Ограниченная лояльность работы и высокие показатели утечки кадров позволяют соискателям 1 В российском Интернете также имеется множество сайтов трудоустройства, например, www.job.ru,www.jobs.ru,www.rabota.ru, www.naim.ru,www.findjob.ru и др. — Пер.
888 Приложение 3 находить вакансии, наилучшим образом соответствующие их потребностям и профессиональным навыкам. Для поддержания высоких уровней производительности и для удержания работников на местах от работодателей требуется непрерывный процесс найма. Количество времени, которое работодатель тратит на заполнение тех- нической вакансии, можно значительно сократить, воспользовавшись Web-сайтом технической направленности. Сайты трудоустройства соискателей технических вакансий входят в число наиболее посещаемых. В данном разделе рассматриваются несколько сайтов технических вакансий. Е-факт_________________________________________________________________________________ Компания тратит на 25% больше средств на наем технического специалиста, чем на оплату труда уже дейст- вующего сотрудника. Dice.com (www.dice.com) — сайт трудоустройства, особое внимание на котором уделено техническим облас- тям. Оплата зависит от количества размещенных объявлений о вакансиях, а также от частоты обновления раз- мещаемых объявлений. Соискатели могут размещать резюме и бесплатно просматривать базу данных вакансий. На сайте JustTechJobs.com соискателям предлагается 39 специализированных компьютерных технологий поис- ка работы. В число сайтов, посвященных языкам программирования, входят JustJavaJobs.com, JustCJobs.com и JustperlJobs.com. Также имеются специализированные сайты по аппаратным, программным средствам и телекоммуникациям. Дополнительные сайты трудоустройства технических работников: HireAbility.com и HotDispatch.com. П3.5.3. Вакансии в области беспроводных технологий Очень быстрыми темпами развивается индустрия беспроводных технологий. По информации сайта WirelessResumes.com количество специалистов в области беспроводных технологий составляет 328 000. Ожи- дается, что в течение ближайших пяти лет это число будет увеличиваться на 40% ежегодно. Для соответствия такому росту и параллельной потребности в профессионалах на сайте WirelessResumes.com создан интерактив- ный раздел, специально предназначенный для заполнения вакансий в области беспроводных технологий. WIrelessResumes.com: заполнение вакансий в области беспроводных технологий WirelessResumes.com — Web-сайт, где особый упор сделан на соответствие профессионалов в области бес- проводных технологий предлагаемым вакансиям. Такая узкая специализация обеспечивает быстрый поиск нуж- ных специалистов с сокращением затрат времени и финансов по сравнению с традиционными методами трудо- устройства. Кандидаты также могут ограничить поиски только интересующими их сферами деятельности. На сайте представлены вакансии в области беспроводных технологий, ведущие производители аппаратных средств, разработчики WAP и BlueTooth, компании электронного бизнеса и провайдеры служб приложений. Помимо поиска вакансий и размещения резюме, на сайте WirelessResumes.com соискателям даются советы по составлению резюме, описываются методы проведения собеседований, предоставляются инструменты пере- распределения, а также оказывается помощь в визировании или заполнении необходимых документов. Работо- датели могут пользоваться сайтом для поиска нужных кандидатов и для размещения объявлений о вакансиях. The Caradyne Group (www.pcsjobs.com) — компания по подбору руководящих работников, которая связывает соискателей с работодателями в области беспроводных технологий Заинтересованным соискателям предлагает- ся заполнить "профильную анкету", после чего полученная информация вводится в базу данных The Caradyne Group и автоматически сопоставляется с имеющимися вакансиями в указанной соискателем области. Если от- крытых вакансий нет, тогда квалифицированный консультант компании связывается с соискателем для допол- нительного собеседования и обсуждения. ПЗ.5.4. Заключение контрактов через Интернет В Интернете имеется форум для соискателей работы над отдельными проектами. Службы заключения кон- трактов дают компаниям возможность размещения объявлений о вакансиях, необходимых для заполнения внешними ресурсами, а посетители форума определяют для себя проекты, наиболее точно подходящие их инте- ресам, планам и квалификации. Е-факт_________________________________________________________________________________ Приблизительно 6% трудоспособного населения США подпадают под категорию независимых подрядчиков. Guru.com (www.guru.com) — сайт трудоустройства для работников по контракту. Независимые подрядчики, частные консультанты и инструкторы пользуются сайтом Guru.com для поиска краткосрочной и долгосрочной работы по контракту. Подрядчикам, желающим получить дополнительную информацию о сфере своей деятель- ности, предлагаются советы, рекомендации и специализированные статьи. В других разделах сайта пользовате- лям предоставляются инструкции по управлению бизнесом, закупках лучшего оборудования и освещение раз-
Возможности карьерного роста 889 личных юридических аспектов. На сайте Guru.com имеется интерактивный магазин, в котором подрядчики мо- гут приобретать товары, имеющие отношение к управлению мелкими предприятиями, например, офисное обо- рудование, а также пользоваться услугами печати. В отличие от соискателей-индивидуалов, компании, заинте- ресованные в работниках по контракту, должны пройти регистрацию на Guru.com. В разделе Talent Market сайта Monster.com свободным агентам предлагаются службы трудоустройства аукци- онного типа. Заинтересованные пользователи составляют краткое описание своей трудовой деятельности с ука- занием квалификации. После этого свободные агенты предлагают ставки на свои вакансии. "Торги" длятся пять дней, в течение которых пользователи могут ознакомиться с поступающими предложениями. По истечении пя- ти дней пользователь может выбрать наиболее подходящую ему работу. Данная служба бесплатная для пользо- вателей, а работодатели, предлагающие вакансии, платят взносы с совершенных сделок. eLance.com — еще один Web-сайт, на котором отдельные подрядчики могут найти работу по контракту. Заин- тересованные соискатели могут просматривать базу данных eLance по категориям, в число которых входят биз- нес, финансы и маркетинг (рис. П3.4). Эти проекты (или заявки на предложение). Размещаются компаниями по всему миру. Когда пользователи находят проекты, подходящие их квалификации, они подают заявки на участие в их разработке. В заявке должна быть указана желаемая зарплата, описание профессиональных навыков и ква- лификации соискателя, а также отзывы руководителей предыдущих проектов, в реализации которых участвовал соискатель. Если заявка принята, то пользователь приступает к работе над проектом, управление которой осу- ществляется по системе совместного использования файлов сайта eLance, что обеспечивает оперативную связь между работодателем и подрядчиком. Интерактивная демо-версия имеется на сайте www.eLance.com (ссылка take a tour...). На других Web-сайтах подрядчикам предлагаются проекты и информация: eWork Exchange (www.ework.com), MBAFreeAgent.com, Aquent.com и WorkingSolo.com. Рис. П3.4. Заявка на предложение el_ance.com (предоставлено eLance, Inc.) ПЗ.5.5. Управленческие должности Как правило, сайты трудоустройства на управленческие должности включают в себя многие функции, имею- щиеся на универсальных сайтах. Поиск управленческой вакансии в Интернете отличается от поиска вакансии начального уровня. Интернет дает пользователям возможность непрерывного отслеживания рынка труда. Одна- ко кандидатам на замещение вакансий управленческого уровня следует соблюдать осторожность при ©пределе-
890 Приложение 3 нии того, кто будет просматривать их резюме. Подача заявки на управленческую вакансию — процесс экстен- сивный. По причине пристального внимания к кандидату во время процесса приема первоначальные критерии, предъявляемые к кандидату руководящего уровня, часто оказываются более специфичными, нежели к соискате- лям вакансий первого уровня. Вакансии руководителей заполнить сложнее из-за высоких требований к кандида- ту и необходимости наличия у него большого опыта работы. SixFigureJobs (www.sivfigurejobs.com) — сайт трудоустройства для опытных руководителей. Для соискателей размещение резюме и поиск бесплатные. Другие сайты, включая www.execunet.com, раздел ChiefMonster сайта Monster.com (www.chiefmonster.com) и www.nationjob.com, предназначены для поиска вакансий руководящих работников. ПЗ.5.6. Студенты и молодые специалисты Для студентов и молодых специалистов в Интернете имеются специальные инструментальные средства, помо- гающие им начать свой путь на рынке труда. Целевая аудитория таких сайтов — школьники, заинтересованные в поступлении в интернатуру, выпускники и лица, уже имеющие работу в течение нескольких лет. Также име- ются дополнительные инструменты, разработанные с учетом демографии (для слоев населения с особыми при- знаками). Например, в журналах, организованных бывшими интернами, публикуется информация для будущих интернов относительно того, чего следует ожидать от интернатуры и чего избегать. На многих сайтах представ- лена информация, направляющая молодых специалистов на правильный трудовой путь, например, вакансии, соответствующие профилирующим дисциплинам в университетах или колледжах. Experience.com — сайт трудоустройства, нацеленный на молодежь. Зарегистрированные члены могут осущест- влять поиск вакансий, соответствующих особым критериям, например, по.географическому положению, кате- гории работы, по ключевым словам, занятости (полный день, неполный день или интернатура), по продолжи- тельности отпусков и времени командировок. После регистрации соискателей они могут направлять свои резю- ме непосредственно в компании, информация о которых размещена на сайте. Помимо резюме кандидаты предоставляют краткую биографию, перечисляют профессиональные навыки и знание иностранных языков. Зарегистрированные пользователи также получают доступ к разделу Job Agent сайта. Каждый зарегистриро- ванный член может использовать до трех агентов по трудоустройству. Агенты осуществляют поиск имеющихся вакансий, исходя из предоставленных пользователем критериев. Если соответствующая должность найдена, то сайт связывается с кандидатом по электронной почте (www.experience.com). Сайт Internships.wetfeet.com помогает студентам в поиске интернатур. Помимо размещения резюме и поиска интернатуры, студенты могут пользоваться так называемым калькулятором расходов на переезд для сравнения стоимости жилья в различных регионах. На сайте представлены советы и рекомендации по составлению резюме и написанию биографии. В программе City Intern представлены руководства по передвижению, проживанию и развлечениям для интернов, с которыми проводится собеседование, либо с теми, кто устраивается на работу в незнакомом городе, облегчающие задачу адаптации в незнакомом месте. В дополнение к поиску интернатур, аспирантур, служб юридических, медицинских и бизнес-школ на Web-сайте' Prinston Review (www.review.com) предлагаются службы трудоустройства для выпускников вузов. В процессе поиска работы студенты и молодые специалисты могут ознакомиться с новостями сайта и даже пополнить запас полезных слов посещением раздела Word of the day (слово дня). В число сайтов трудоустройства молодежи входят campuscareercenter.com, brassringcampus.com и collegegrad.com. ПЗ.5.7. Другие интерактивные службы трудоустройства В дополнение к Web-сайтам, дающим пользователям возможность находить объявления о трудоустройстве и размещать их, существуют сайты, предлагающие функции расширенного поиска, подготовку к интерактивному поиску, рекомендации по составлению резюме или помощь при расчете затрат на переезд. Сайт Salary.com помогает соискателям при измерении ожидаемых доходов на основании должности, степени ответственности и опыта работы. При поиске необходимо знать категорию работы, почтовый индекс и название должности. На основании этой информации сайт возвращает данные о расчетной зарплате для человека, живу- щего в указанном регионе и занимающего обозначенную должность. Калькуляция рассчитывается на основании среднего уровня доходов для данной должности. Помимо помощи соискателям в поиске работы, сайт www.careerpower.com предоставляет посетителям тесты, помогающие определить свои сильные, слабые стороны, достоинства, профессиональные навыки и личные ка- чества. На основании результатов, которые могут занимать 10—12 страниц для каждого теста, пользователи могут определить, для какой работы они лучше всего квалифицированы, и какая должность будет максимально соответствовать их личным амбициям. Данная служба — платная.
Возможности карьерного роста 891 InterviewSmart — еще одна служба, предоставляемая сайтом CareerPower, которая помогает соискателям всех уровней в процессе прохождения собеседования. Данную службу можно загрузить за символическую плату, либо использовать ее в сети. Обе версии доступны по адресу www.careerpower.com/CareerPerfect /interviewing.htm#is.start.anchor. Дополнительные службы помогают соискателям в поиске вакансий, наиболее точно соответствующих уникаль- ным потребностям, или в составлении резюме для привлечения внимания работодателей тех или иных катего- рий. Сайт Dogfriendly.com, организованный по географическому признаку, помогает соискателям в поиске мест работы, на которые они могли бы брать с собой своих домашних питомцев, а сайт cooljobs.com представляет собой базу данных для поиска уникальных возможностей развития карьеры. П3.6. Резюме Интернет повышает возможности работодателей при поиске новых сотрудников и помогает пользователям на- ходить работу в любой точке мира. Соискатели могут научиться правильно составлять резюме и сопроводи- тельные письма, размещать их в сети, а также просматривать списки вакансий, наиболее точно соответствую- щие потребностям. Работодатели могут размешать объявления о вакансиях, просматривать которые может ог- ромное число потенциальных кандидатов. Соискатели могут сохранять и распространять резюме в цифровом виде, получать по электронной почте уведомления о наличии подходящих вакансий, пользоваться калькулято- рами затрат на переезд, консультироваться у профессиональных агентов по трудоустройству и использовать инструменты самотестирования при поиске работы в Интернете. На сегодняшний день в глобальной сети насчитывается порядка 40 000 служб трудоустройства. Поиск работы через Интернет значительно сокращает количество времени, которое затрачивается на подачу заявления на за- мещение вакансии. Потенциальные кандидаты могут получить дополнительную информацию о компаниях, по- сетив их корпоративные сайты. Доступ соискателей к большинству сайтов бесплатный. Такие сайты существу- ют за счет платы работодателей, размещающих объявления о своих вакансиях, а также за счет продажи реклам- ного пространства на Web-страницах. На многих сайтах по трудоустройству сопоставление резюме с той или иной должностью осуществляется с помощью программных средств фильтрации резюме. Самыми популярны- ми и наиболее часто посещаемыми являются сайты по трудоустройству технических работников. Службы инте- рактивного заключения контрактов позволяют компаниям размещать объявления о вакансиях, на замещение которых требуются внешние ресурсы, а также позволяют отдельным соискателям идентифицировать проекты, наиболее полно соответствующие их интересам, планам и профессиональным навыкам. Интернет обеспечивает студентов и молодых специалистов необходимыми инструментами, помогающими на- чать путь построения трудовой карьеры. Целевой рынок включает в себя студентов учебных заведений, зани- мающихся поисками интернатур, выпускников вузов, а также лиц, уже имеющих работу в течение нескольких лет. Определенное количество Web-сайтов предлагают функции расширенного поиска работы, подготовки пользователей к интерактивному поиску, помощи при составлении резюме или расчете затрат на переезд. Тру- доустройство через Интернет охватывает гораздо более обширную аудиторию, нежели размещение объявлений, например, в местной газете. Существует множество сайтов, на которых работодатели могут размещать объявле- ния о вакансиях. Некоторые из таких сайтов платные (100—200 долларов). Объявления остаются в сети от 30 до 60 дней. При составлении объявления о работе определение того, что делает должность единственной в своем роде и включение в него, например, информации о зарплате и различных льготах и преимуществах может заинтересо- вать квалифицированного кандидата. В Интернете имеются сайты демографической направленности, помогаю- щие работодателям добиться разнородности своей рабочей силы. Интернет обеспечил работодателей эффектив- ными средствами тестирования потенциальных сотрудников по таким категориям, как умение принимать реше- ния, решение возникающих проблем и по личным качествам. П3.7. Ресурсы Интернета и WWW Сайты трудоустройства специалистов по информационным технологиям □ www.dice.com Сайт трудоустройства в области вычислительной техники. П www.guru.com Сайт трудоустройства для лиц, работающих по контракту. Независимые подрядчики, частные консультанты и инструкторы могут использовать сайт Guru.com для поиска краткосрочной и долгосрочной работы.
892 Приложение 3 □ www.hallkinion.com Службы трудоустройства для соискателей вакансий в области информационных технологий □ www.techrepublic.com Данный сайт обеспечивает соискателей и работодателей возможностями трудоустройства и найма, а также информацией, касающейся технологии разработок. □ www.justcomputerjobs.com Портал с доступом к сайтам, посвященным языкам программирования, включая Java, Peri, С и C++. □ www.hotdispatch.com Данный форум предоставляет разработчикам программного обеспечения возможность обмена проектами, обсуждения кода и постановки вопросов. □ www.techjobs.bizhosting.com/jobs.htm Ссылки на многочисленные сайты трудоустройства технических работников, расположенных по категориям местоположения, Интернета, сферы и т. д. Сайты трудоустройства □ www.careerbuilder.com Будучи сетью сайтов трудоустройства, включая IT Careers, USA Today и MSN, CareerBuilder привлекает до 3 млн посетителей в месяц. На сайте представлены механизмы составления резюме и агентов поиска вакан- сий. □ www.recruitek.com Бесплатный сайт трудоустройства для соискателей, работодателей и подрядчиков. □ www.monster.com Крупнейший из интерактивных ресурсов трудоустройства. Возможность размещения резюме, поиска вакан- сий, ознакомления с рекомендациями в процессе поиска работы. На сайте представлен ряд служб трудоуст- ройства для работодателей. □ www.jobsonline.com Подобный сайту Monster.com, данный портал предоставляет возможности трудоустройства для соискателей и работодателей. □ www.hotjobs.com Интерактивный сайт, на котором представлены перекрестные ссылки на дополнительные сайты трудоуст- ройства. □ www.jobfind.com Один из примеров ресурсов локального трудоустройства. Целевой регион сайта jobfind.com — Бостон. □ www.flipdog.com Данный сайт обеспечивает соискателей возможностью поиска вакансий. Здесь применяются "интеллекту- альные агенты" для просмотра сети с возвращением вакансий, соответствующих запросу кандидата. □ www.cooljobs.com Уникальные возможности трудоустройства. □ www.inetsupermall.com Помощь соискателям в составлении профессиональных резюме, а также при контактах с работодателями. □ www.wirelessnetworksonline.com Сайт оказывает соискателям помощь при поиске вакансий, соответствующих квалификации. П www.careerweb.com Обзор работодателей, вакансий, а также помощь соискателям и работодателям при размещении и просмотре резюме, соответственно. □ www.jobsleuth.com На данном сайте соискатели могут заполнить бланк с указанием желаемой области деятельности. Сайт Job Sleuth просматривает Интернет и направляет в почтовый ящик пользователя потенциальные соответствия. Служба бесплатная.
Возможности карьерного роста 893 □ www.ajb.org America's Job Bank— интерактивная служба трудоустройства, созданная Министерством труда и государ- ственной службой занятости. Поиск и размещение объявлений о вакансиях—бесплатные. Управленческие должности □ www.sixfigurejobs.com Сайт трудоустройства, предназначенный для опытных управленцев. □ www.leadersonline.com На сайте предлагается возможность анонимного поиска работы специалистам среднего звена. Потенциаль- ные соответствия вакансий направляются кандидатам по электронной почте. □ www.ecruitinginc.com Сайт предназначен для поиска сотрудников на замещение вакантных руководящих должностей. Разнородность □ www.latpro.com Сайт рассчитан на испано- и португало-говорящих соискателей. Помимо служб рассылки резюме, на сайте соискатели имеют возможность получения соответствующих вакансий по электронной почте. Рекомендации и ознакомительная информация. П www.blackvoices.com Данный портал содержит центр трудоустройства, рассчитанный на соискателей афро-американцев. □ www.hirediversity.com Помимо служб поиска и размещения объявлений о вакансиях, на сайте содержатся службы составления ре- зюме и обновления. Целевая аудитория — разнообразна и включает афро-американцев, американцев азиат- ского происхождения, людей с ограниченными физическими возможностями, женщин и латиноамериканцев. Сайты для людей с ограниченными физическими возможностями □ www.halftheplanet.com Сайт для людей с физическими недостатками. Обширный портал, включающий множество различных ре- сурсов и информационных служб. Специальный раздел посвящен соискателям и работодателям. □ www.wemedia.com Сайт, соответствующий потребностям людей с ограниченными физическими возможностями. Имеется раз- дел трудоустройства для соискателей и работодателей □ www.disabilities.com Ссылки на информационные ресурсы по трудоустройству и развитию карьеры. □ www.mindexchange.com Раздел разнородности сайта предоставляет пользователям несколько ссылок на дополнительные ресурсы (в том числе по трудоустройству), рассчитанные на людей с ограниченными физическими возможностями. □ www.usdoj.gov/crt/ada/adahoml.htm Домашняя страница Закона об американцах с ограниченными физическими возможностями. □ www.abanet.org/publicserv/mental.html Сайт Комиссии по правам людей с умственными и физическими недостатками. □ janweb.icdi.wvu.edu Предложение консультативных услуг работодателям относительно привлечения к трудовой деятельности людей с физическими возможностями. Общие ресурсы □ www.vault.com Потенциальные служащие получают информацию "изнутри" о свыше чем 3000 компаниях. Кроме того, со- искатели могут просматривать списки имеющихся вакансий, размещать вопросы и ответы на них на инте- рактивной доске объявлений.
894 Приложение 3 □ www.wetfeet.com Подобный vault.com, данный сайт обеспечивает посетителей возможностью задавать вопросы и получать "информацию изнутри" о компаниях-работодателях. Сайты по интересам □ www.eharvest.com/careers Помощь соискателям, заинтересованным в вакансиях в области сельского хозяйства. □ www.opportunitynocs.org Сайт трудоустройства для соискателей и работодателей, заинтересованных в некоммерческой деятельности. □ www.experience.com Сайт рассчитан на молодых специалистов и студентов, ищущих работу на полный день, неполный рабочий день, а также в интернатурах. □ www.internships.wetfeet.com Списки вакансий для студентов Наличие раздела City Intern в помощь при адаптации на новом месте. П www.bra5sringcampus.com Сайт предоставляет выпускникам вузов и молодым специалистам с опытом работы до пяти лет возможности трудоустройства и карьерного роста. Дополнительные функции оказывают помощь при покупке автомоби- лей и аренде жилья. Интерактивное заключение контрактов □ www.ework.com Данный интерактивный сайт по трудоустройству обеспечивает контакты внешних подрядчиков с компания- ми, которым требуются специалисты на реализацию отдельных проектов. В число других служб входят ссылки на торговые сайты, льготные пакеты, службы оплаты, а также на ресурсы интерактивных дискуссий и управления. □ www.elance.com Подобно сайту eWork.com, сайт предоставляет внешним подрядчикам возможность работы над отдельными проектами. □ www.MBAFreeAgent.com Сайт предназначен для обеспечения рабочими местами MBA. □ www.aquent.com Доступ к техническим вакансиям на контрактной основе. □ www.WorkingSolo.com Помощь подрядчикам при реализации собственных проектов. Службы трудоустройства □ www.advantagehiring.com Сайт просмотра резюме работодателями. □ www.etest.net Данный сайт обеспечивает работодателей службами тестирования для оценки сильных и слабых сторон по- тенциальных сотрудников. Информацию можно использовать при разработке стратегии найма. □ www.hire.com Провайдер служб приложений сайта eRecruiter в помощь организациям при оптимизации процесса трудо- устройства через Интернет. □ www.futurestep.com На данном сайте работники руководящего звена могут регистрироваться анонимно для последующего рас- смотрения их резюме на должности руководителей высшего звена. Для зарегистрированных пользователей обеспечивается связь с выбранными вакансиями. Многочисленные службы управления процессом трудоуст- ройства.
Возможности карьерного роста 895 □ www.webhire.com Сайт обеспечивает работодателей "сквозными" решениями по трудоустройству. Ресурсы трудоустройства в области беспроводных технологий □ www.wirelessresumes.com Связь работодателей с соискателями вакансий в области беспроводных технологий. □ www.msua.org/job.htm Ссылки на многочисленные сайты, посвященные трудоустройству в области беспроводных технологий. □ www.wiwc.org Трудоустройство в области беспроводных технологий для женщин. О www.firstsearch.com Возможности полной, частичной занятости и сдельной работы в области беспроводных технологий. □ www.pcsjobs.com Сайт The Caradyne Group — компании поиска руководящих работников в области беспроводных техноло- гий. □ www.cnijoblink.com CNI Career Networks обеспечивает конфиденциальное бесплатное размещение объявлений о вакансиях в об- ласти беспроводных технологий;
ПРИЛОЖЕНИЕ 4 Отладчик Visual Studio .NET И часто оправдание вины Ее усугубляет многократно. Уильям Шекспир Человеку свойственно ошибаться, а Богу — прощать. Александр Поуп Темы данного приложения: □ синтаксические и логические ошибки; □ инструменты отладки Visual Studio .NET; □ использование точек прерывания для приостановки исполнения программы* □ контроль данных с помощью выражений в окнах отладки; □ отладка методов и объектов. П4.1. Введение В процессе разработки программных средств имеют место два типа ошибок: синтаксические и логические. Синтаксические ошибки (или ошибки компиляции) возникают, когда операторы программы нарушают грамма- тические правила языка программирования, например, при отсутствии точки с запятой по окончании оператора. При обнаружении компилятором синтаксических ошибок его работа прерывается без построения программного приложения. Логические ошибки не приостанавливают компиляцию или исполнение кода, но при этом про- граммы работают не так, как задумал разработчик. Исправлять синтаксические ошибки проще, чем логические. При обнаружении синтаксической ошибки в окне Task List (рис. П4.1) компилятор выводит ее описание и номер строки. Эта информация дает программисту "ключ" к тому, как исправлять возникшую ошибку, чтобы компилятор мог продолжить составление программы. Однако логические ошибки часто намного более "замысловатые", и пользователь не знает точно, в каком месте программы имела место ошибка такого рода. В данном приложении рассматриваются оба типа ошибок и под- робно описываются функции Visual Studio .NET для обнаружения и исправления логических ошибок. Совет по тестированию и отладке______________________________________________________ После исправления одной ошибки можно заметить, что общее количество ошибок, выявленных компилятором, значительно уменьшается. Совет по тестированию и отладке______________________________________________________ Когда компилятор сообщает о синтаксической ошибке в какой-либо строке, проверьте эту строку на предмет ошибок. Если в этой строке ошибок нет, то проверить следует несколько предыдущих строк кода. Отладкой называется процесс обнаружения и исправления в приложениях логических ошибок. По сравнению с синтаксическими, логические ошибки неочевидны, потому что программа с логической ошибкой компилирует- ся в нормальном режиме, но работает не так, как ожидалось. Исправлять логические ошибки сложно, потому что программист не видит кода во время его исполнения. Одним из методов отладки программ, часто исполь- зуемых начинающими программистами, является прямое отображение данных программы с помощью окон со- общений или операторов Console.WriteLine. Например, для определения, насколько корректное значение при- своено переменной, программист может вводить значение переменной при его изменении. Но такой подход достаточно громоздкий, потому что от разработчика требуется вставка строки кода везде, где есть подозрение
Отладчик Visual Studio .NET 897 на ошибку. Более того, по окончании отладки программист должен удалить все посторонние операторы, кото- рые часто бывает трудно отличить от операторов кода-оригинала. Синтаксическая ошибка Сообщение об ошибке Рис. П4.1. Синтаксическая ошибка Отладчиком называется программное средство, позволяющее разработчику анализировать данные программы и отслеживать ее исполнение во время прогона. Отладчик предоставляет функции приостановки исполнения программы, просмотра и модификации переменных, вызова методов без изменения программного кода и др. В данном приложении представлен отладчик Visual Studio .NET и некоторые из его инструментальных средств отладки. Примечание_____________________________________________________________________________ Перед работой с программой в отладчике она должна быть успешно скомпилирована. П4.2. Точки прерывания Точки прерывания — это простой, но эффективный инструмент отладки. Точка прерывания представляет собой метку, вводимую программистом в листинг кода. По достижении точки прерывания исполнение программы приостанавливается; разработчик изучает ее состояние на предмет работы в заданном режиме. В листинге П4.1 представлена программа, выдающая значение факториала’ 10!, но содержащая две логические ошибки: первая итерация цикла умножает х на 10, вместо умножения х на 9, и результат расчета факториала умножается на о (т. е. результат всегда будет равен 0). Данная программа используется для демонстрации возможностей отладки Visual Studio .NET; в качестве первого примера используются точки прерывания. I Листинг П4.1. Отладка программы-примера U ...........--2...„..ж...’..... ..... ? л....л......:... :- * 1 // Листинг П4.1: DebugExanple.cs 2 // Пример программы для отладки 3 4 using System; 5 6 namespace Debug 7 { 8 class DebugExample 9 { 10 static void Main* string[] args) 11 { 12 int x = 10; 13 1 Факториал x (x!) рассчитывается как произведение всех цифр, меньших или равных х, но больших нуля (т. к. 0! = 1). Например, 10! = 10х9х8х7х6х5х4хЗх2х1. 57 Зак. 3333
898 Приложение 4 14 Console.Write("The value of " + x + " factorial is: "); 15 16 // цикл для определения факториала х; содержит логическую ошибку 17 for (int i = х; i >= 0; i—) 18 x *= i; 19 20 Console.Write(x); 21 22 Console.ReadLine(); // отложить выход из программы 23 24 } // окончание Main 4 25 26 } // окончание класса DebugExample 27 28 } // окончание пространства имен Debug Результат работы программы: The value of 10 factorial is: 0 Для задания точки прерывания в Visual Studio щелкните левой кнопкой мыши на серой области слева от любой строки кода или правой — на строке кода и выберите команду Insert Breakpoint. Строка будет помечена крас- ным круглым маркером, указывающим на вставку точки прерывания (рис. П4.2). Исполнение программы будет приостановлено по достижении строки, содержащей точку прерывания. Для активизации точек прерывания и других функций отладки программу необходимо скомпилировать с по- мощью конфигурации отладчика (рис. П4.3). Выберите в панели инструментов конфигурации команду Debug (если она не выбрана). Либо выберите команды Build | Configuration Manager и измените Active Solution Configuration на Debug. Рис. П4.2. Задание точки прерывания Рис. П4.3. Настройка конфигурации отладки Рис. П4.4. Консольное приложение, исполнение которого приостановлено для отладки При выборе команды Debug | Start программа скомпилируется, и начнется ее отладка. При отладке консольно- го приложения откроется консольное окно (рис. П4.4), что обеспечит взаимодействие с программой (ввод и вы- вод). По достижении отладчиком точки прерывания (строка 18) исполнение программы будет приостановлено, и IDE (Integrated Development Environment, интегрированная среда разработки) станет активным окном. Во вре- мя отладки программисту может потребоваться переход от IDE к консольному окну. На рис. П4.5 показана среда IDE с исполнением программы, приостановленным в точке прерывания. Желтая стрелка слева от оператора х *= i; указывает строку, на которой исполнение программы приостановлено, и сообщает, что данная строка содержит следующий оператор для исполнения. Обратите внимание: в строке заголовка IDE отображено [break], указы-
Отладчик Visual Studio .NET 899 вающее, что IDE находится в режиме прерывания (т. е. отладчик запущен). По достижении программой точки прерывания программист может навести курсор мыши на любую переменную (в данном случае х или i) в ис- ходном коде для просмотра значения этой переменной во всплывающей подсказке. Совет по тестированию и отладке_______________________________________________________ Если поместить точку прерывания после цикла в программе, то это позволит циклу завершиться без остановки до достижения точки прерывания. _ Желтая стрелка указывает на следующую инструкцию, которая будет выполнена Рис. П4.5. Исполнение программы, приостановленное в точке прерывания П4.3. Просмотр данных В Visual Studio .NET имеется несколько окон отладки, позволяющих программистам просматривать перемен- ные и выражения Доступ ко всем окнам осуществляется из подменю Debug | Windows. Некоторые окна появ- ляются в списке только при нахождении IDE в режиме прерывания (также называется режимом отладки). В окне Watch, доступном только в режиме прерывания (рис. П4.6), программист может просматривать значе- ния связанных групп переменных и выражений. Всего окон Watch в Visual Studio .NET — четыре. L Выражения Рис. П4.6. Окно Watch 1 При первом открытии в окне Watch не содержится выражений для оценки. Для просмотра данных введите вы- ражение в поле Name. В это поле можно ввести большинство допустимых выражений С#, включая выражения, содержащие обращения к методам. Полное описание допустимых выражений представлено в документации в разделах debugger, expressions. После ввода выражения его значение и тип появятся в полях Value и Туре соответственно. Первым введенным выражением является переменная i, имеющая значение 10 (в строке 12 значение 10 присваивается переменной х, а в строке 17 значение х присваивается переменной i). В окне Watch также оцениваются и более сложные арифметические выражения (например (i+3)*5). Таким образом, окно Watch обеспечивает удобный способ отображения различных типов данных программы без внесения изменений в код. Вводом переменных и выражений, имеющих отношение к логической ошибке программы, разработчики могут отслеживать некорректные значения до источника ошибки и исправить ее. Например, для отладки программы, представленной в листинге П4.1, в окно Watch можно ввести выражение i*x. При первом достижении точки прерывания выражение имеет значение 100, а не 90, что указывает на логическую ошибку в программе. Это
900 Приложение 4 происходит потому, что цикл в строках 17—18 начал умножать х на 10, а не на 9. Для исправления ошибки не- обходимо вычесть единицу из первоначального значения, которое цикл for присваивает переменной i (т. е. из- менить 10 на 9). Если в поле Name окна Watch содержится имя переменной, тогда в целях отладки ее значение можно изменить. Для изменения значения переменной щелкните кнопкой мыши на значении в поле Value и введите новое значе- ние. Все измененные значения выделяются красным цветом. . Если выражение недопустимо, то в поле Value появится сообщение об ошибке. Например, VariableThatDoesNotExist не является используемым в программе идентификатором (четвертая строка на рис. П4.6). Следовательно, Visual Studio .NET выдает в поле Value сообщение об ошибке. Для удаления этого выражения выделите его и нажмите клавишу <Delete>. . В Visual Studio .NET также имеются окна Autos, Locals и This (рис. П4.7), сходные с окном Watch за исключе- нием того, что программист в них ничего не вводит. В окне Locals отображаются имя и текущее значение всех переменных, имеющих область блокирования в методе, содержащем текущий оператор (указанный желтой стрелкой на рис. П4.5). В окне Autos отображены переменные и значения текущего и предыдущего операторов. Переменные можно изменять в любом окне щелчком кнопкой мыши в нужном поле Value и вводом нового значения. В окне This отображены данные, имеющие область класса для объекта. Если программа входит в метод static (например, метод Main в консольном приложении), тогда окно This пустое. Рис. П4.7. Окна Autos, Locals и This Присваивание Обновленное значение Рис. П4.8. Окно immediate Программист может оценивать выражения построчно в окне Immediate (рис. П4.8). Для оценки выражения про- граммист вводит его в данное окно и нажимает клавишу <Enter>. Например, при вводе Console. Writ eLine (i) и
Отладчик Visual Studio .NET 901 нажатии клавиши <Enter> значение i выводится в консольное окно. Для выполнения присвоений в окне Immediate разработчик также может воспользоваться операцией присваивания (=). Обратите внимание, что зна- чения для i и х в окне Locals содержат обновленные значения. Совет по тестированию и отладке _______________________________________________________ Для однократного обращения к методу воспользуйтесь окном Immediate. При размещении вызова метода в окне Watch метод будет вызываться всякий раз при остановке программы. П4.4. Управление программой Отладчик Visual Studio .NET обеспечивает программистов значительными возможностями управления исполне- нием программы. С помощью точек прерывания и команд управления программой, предоставляемых отладчи- ком, разработчики могут легко проанализировать исполнение кода в любой точке программы. Это полезно при наличии в ней множества обращений к методам, которые исполняются корректно. В панели инструментов Debug имеются кнопки, обеспечивающие удобный доступ к управлению процессом отладки (рис. П4 9). Для отображения панели инструментов Debug выберите команды View | Toolbars | Debug. Показать следующую инструкцию - Шаг внутрь Перезапустить - - Шаг наружу Прервать все Шаг вверх Продолжить J отладку Остановить - Переключиться на шестнадцатеричный J формат представления - Окно Breakpoint отладку данных Рис. П4.9. Панель инструментов Debug С помощью панели инструментов отладки, показанной на рис. П4.9, осуществляется управление, исполнением отладчика. При нажатии кнопки Restart программа начинает выполняться с самого начала с паузой для того, чтобы дать программисту возможность расставить точки прерывания с последующим исполнением. Нажатие кнопки Continue возобновляет исполнение приостановленной программы. Нажатие кнопки Stop Debugging завершает сеанс отладки, а кнопка Break АП позволяет программисту приостановить исполняемую программу напрямую (т. е. без явной установки точек прерывания). После приостановки исполнения появляется желтая стрелка, указывающая на исполнение следующего оператора. Совет по тестированию и отладке__________________________________________________________ Во время исполнения программы такие проблемы, как появление бесконечных циклов, часто можно прервать выбором команд Debug | Break АП либо нажатием соответствующей кнопки на панели инструментов Debug При нажатии кнопки Show Next Statement курсор помещается на строку, на которой находится желтая стрелка. Данная команда полезна, когда программисту необходимо вернуться к текущей точке исполнения после задания точек прерывания в программе, содержащей многострочный код. При нажатии кнопки Step Over отрабатывает следующий исполняемый оператор, и желтая стрелка перемеща- ется на следующую строку. Если в следующей строке кода содержится обращение к методу, то данный метод исполняется как один шаг. Данная кнопка позволяет разработчику исполнять программу по одной строке без просмотра "тонкостей" каждого вызываемого метода. Это полезно, если в программе содержится множество обращений к методам, о которых известно, что они исполняются корректно. Более подробно кнопки Step Into и Step Out описываются в следующем разделе. Нажатием кнопки Hex переключается формат отображения данных. При нажатой кнопке Hex данные отобра- жаются в шестнадцатеричном формате (по основанию 16), а не в десятичном (по основанию 10). Опытные про- граммисты часто предпочитают считывать значения в шестнадцатеричном формате, особенно большие числа, потому что шестнадцатеричное представление более сжато, и его легко можно преобразовать в двоичный фор- мат (по основанию 2). Более подробно шестнадцатеричная и десятичная системы счисления описаны в прило- жении 2.
902 Приложение 4 В окне Breakpoints отображены все точки прерывания, установленные в программе (рис. П4.10). Рядом с каж- дой точкой прерывания появляется флажок, указывающий на то, активизировала точка прерывания (флажок отмечено) или нет (флажок сброшен). Строки с неактивизированными точками прерывания помечены красной окружностью, а не кругом (рис. П4.11). В отключенных точках прерывания отладчик не прерывает исполнение. Рис. П4.10. Окно Breakpoints // loop to determine x factorial, contains logic error for ( int i x; 1 > 0; i— ) Console.Write( x ); L Неактивизированная точка прерывания Рис. П4.11. Неактивизированная точка прерывания Совет по тестированию и отладке__________________________________________________________ Неактивизированные точки прерывания позволяют программистам поддерживать точки прерывания в ключевых местах программы с возможностью активизации, когда это необходимо. Отключенные точки прерывания всегда видимы. В окне Breakpoint (см. рис. П4.10) в поле Condition отображено условие, которое должно быть удовлетворено для приостановки исполнения программы в данной точке прерывания. В поле Hit Count показано количество раз остановки отладчика в каждой точке прерывания. При двойном щелчке кнопкой мыши на каком-либо пунк- те в окне Breakpoints курсор перемешается в строку, содержащую данную точку прерывания. Рис. П4.12. Диалоговое окно New Breakpoint Рис. П4.13. Диалоговое окно Breakpoint Hit Count Программист может добавлять точки прерывания нажатием кнопки New в окне Breakpoints. При этом открыва- ется диалоговое окно New Breakpoint (рис. П4.12). Вкладки Function, File, Address и Data позволяют разра- ботчику приостановить исполнение программы на методе, на строке конкретного файла, на команде в памяти, либо при изменении значения переменной. Кнопку Hit Count... можно использовать для указания того, когда точка прерывания должна приостановить исполнение программы (по умолчанию— always break) — рис. П4.13. Точку прерывания можно задать на приостановку исполнения программы, когда число случаев вы- полнения строки кода, сообщаемое профайлером при анализе программы во время выполнения — hit count —
Отладчик Visual Studio .NET 903 достигает определенного значения, когда hit count кратно определенному значению, либо больше или рав- но ему. Отладчик Visual Studio .NET также позволяет приостановить исполнение программы в точке прерывания в за- висимости от значения выражения. При нажатии кнопки Condition... открывается диалоговое окно Breakpoint Condition (рис. П4.14). Позиция Condition указывает на активизацию условий точки прерывания. Переключа- телями определяется оценка выражения в текстовом поле. При выборе переключателя is true исполнение оста- навливается в точке прерывания всякий раз, когда выражение истинно. При отметке переключателя has changed исполнение программы приостанавливается при первом столкновении с точкой прерывания и всякий раз, когда выражение отличается от его предыдущего значения при столкновении с точкой прерывания. После закрытия диалогового окна New Breakpoint в окне Breakpoints отображаются опции условия и hit count для новой точки прерывания. Рис. П4.14. Диалоговое окно Breakpoint Condition Предположим, что в качестве условия для точки прерывания в цикле задано выражение x*i!=o с активизиро- ванной опцией has changed (данная операция может потребоваться, если программа выдает некорректный вы- ход 0). Исполнение приостанавливается при первом столкновении с точкой прерывания и создается запись о том, что выражение имеет значение true, потому что x*i равно 100 (либо 10, если описанная выше логическая ошибка была исправлена). При продолжении цикл уменьшает переменную i. Пока ее значение находится в диа- пазоне между 10 и 1, значение условия не меняется, и в этой точке прерывания исполнение программы не при- останавливается. Когда значение переменной 1 равно нулю, выражение x*i!=0 ложно (false), и исполнение программы приостанавливается. В данной точке программист обнаруживает вторую логическую ошибку: при последней итерации цикла for результат умножается на 0. Для возвращения IDE в режим конструктора нажмите кнопку Stop Debugging в панели инструментов Debug. П4.5. Дополнительные возможности отладки методов В программах с множеством методов часто бывает сложно определить, какие методы могут участвовать в не- правильных расчетах, результатом которых станет логическая ошибка. Для упрощения этого процесса в отлад- чик Visual Studio входят инструментальные средства для анализа методов и обращений к ним. В следующем примере демонстрируются некоторые инструменты отладки методов (листинг П4.2). В окне Call Stack содержится стек обращений к методам, позволяющий программисту определить точную последовательность вызовов, приводящую к текущему методу, а также просматривать в стеке вызываемые ме- тоды. Данное окно дает программисту возможность определить поток управления программой, результатом которого стало исполнение текущего метода. Например, точка прерывания вставлена в MyMethod, стек вызовов (рис. П4.15) указывает, что программа вызвала сначала метод Main, а затем MyMethod. Листинг П4.2. Отладка методов 1 // Листинг П4.2: MethodDebugExample.cs 2 // Демонстрация отладки методов 3 4 using System; 5 6 namespace Debug 7 { 8 9 // предоставление методов для демонстрации 10 // инструментов отладки Visual Studio 11 class MethodDebug
904 Приложение 4 12 { 13 // точка входа для приложения ~ 14 static void Main(string[] args) 15 { 16 // отображение возвращаемых значений MyMethod 17 for (int i = 0; i < 10; i++) 18 Console.WriteLine(MyMethod(i)); 19 20 Console.ReadLine(); 21 } // окончание метода Main 22 23 // выполнение расчета 24 static int MyMethod(int x) 25 { 26 return (x * x) — (3 * x) + 7; 27 } // окончание метода MyMethod 28 29 // метод с логической ошибкой 30 static int BadMethod(int x) 31 { 32 return 1 / (x — x); 33 } // окончание метода Badmethod 34 35 } // окончание класса MethodDebug 36 37 } // окончание пространства имен Debug Рис, П4.15. Окно Call Stack При двойном щелчке кнопкой мыши на любой строке в окне Call Stack отображается следующая строка для исполнения в этом методе. Это дает программисту возможность определить, как результат каждого метода по- влияет на исполнение вызываемого метода. В Visual Studio .NET строка выделяется зеленым цветом, а также отображается всплывающая подсказка (рис П4.16). Рис. П4.16. Среда IDE, отображающая точку вызова метода В Visual Studio также представлены дополнительные кнопки управления программой для отладки методов. При нажатии кнопки Step Over в методе исполняется один оператор, после чего на следующей строке работа про- граммы приостанавливается. Если оцененный оператор активизирует метод, то с помощью кнопки Step Over вызывается метод, и на следующем операторе исполнение приостанавливается. Если оператор активизирует метод, то при нажатии кнопки Step Into методу передается построчное управление. При нажатии кнопки Step Out исполнение текущего метода прекращается, и управление возвращается в строку, вызвавшую данный метод. В табл. П4.1 перечислены функции управления и отладки программы, "горячие" клавиши и описание. Для дос- тупа к командам меню опытные программисты часто предпочитают пользоваться "горячими" клавишами.
Отладчик Visual Studio .NET 905 Совет по тестированию и отладке________________________________________ Для завершения исполнения случайно вызванного метода воспользуйтесь кнопкой Step Out. Таблица П4.1. Функции отладки программы Кнопка управления “Горячая" клавиша Описание Continue <F5> Продолжение исполнения программы. Исполнение продолжается до обнаружения точки прерывания, либо до окончания программы (при нормальном исполнении) Stop Debugging <Shift>+<F5> Остановка отладки и возврат в режим конструктора Visual Studio Step Over <F10> Переход к следующему оператору без вызовов метода Step Into <F11> Исполнение следующего оператора Если оператор содержит обра- щение к методу то последнему передается построчное управление отладкой. Если оператор не содержит обращения к методу, то дейст- вие кнопки Step Into сходно с действием кнопки Step Over Step Out <Shift>+<F11> Окончание исполнения текущего метода и приостановка исполнения программы в вызывающем методе Для тестирования переданных в метод аргументов программисты могут пользоваться окном Immediate fdw. разд. П4.3). Тестирование аргументов помогает определить корректность работы метода. П4.6. Дополнительные возможности отладки классов В большинстве сложных программ C# большая часть их данных содержится в объектах. Для этих целей в Visual Studio имеются функции отладки классов, позволяющие разработчикам определить текущее состояние исполь- зуемых в программе объектов (рис. П4.17). Некоторые функции отладки классов продемонстрированы с по- мощью кода в листинге П4.3. Для рассмотрения экземпляра класса DebugEntry точка прерывания помещается в строку 43, как показано на рис. П4.18. Примечание_______________________________________________________ Файл C# может содержать много классов, как в случае с приводимым примером. 1 // Листинг П4.3: DebugClass.cs 2 // Консольное приложение для демонстрации отладки объектов 3 4 using System; 5 6 namespace ClassDebug 7 { 8 9 // создание массива, содержащего три разных класса 10 public class DebugEntry 11 ' 12 public int somelnteger = 123; 13 private int[] integerArray = { 74, 101, 102, 102 }; 14 private DebugClass debugClass; 15 private Random randomobject; 16 private object[] list = new object[ 3 ]; 17 18 // конструктор 19 public DebugEntry() 20 { 21 randomobject = new RandomO; 22 debugClass = new DebpgClass("Hello World", 23 new object()); 24
906 Приложение 4 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 Ч 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 list[ 0 ] = integerArray; list[ 1 ] = debugClass; list[ 2 ] «• randomDbject; // отображение значений, извлеченных из трех объектов public void Displayvalues() { Console.WriteLine(randomobject .Next()); Console.WriteLine(debugClass.SomeString); Console.WriteLine(integerArray[ 01); J // главная точка входа для приложения [STAThread] public static void Main() { DebugEntry entry = new DebugEntry(); entry.Displayvalues(); } } 11 окончание класса DebugEntry // демонстрация отладки класса public class DebugClass { // переменные типа private private string someString; private object privateobject; // конструктор public DebugClass(string stringData, object objectData) { someString = stringData; privateobject = objectData; } // свойство средства доступа к someString public string SomeString { get { return someString; } 70 set 71 { 72 someString = value; 73 } 74 ) // окончание свойства SomeString 75 76 } // окончание класса DebugClass 77 78 } // окончание пространства имен ClassDebug В помошь отладке классов Visual Studio .NET дает программистам возможность развертывания и просмотра всех элементов данных и свойств класса, включая элементы типа private. В любом из четырех окон (Watch, Locals, Autos и This) класс, имеющий элементы данных, отображается со знаком "плюс" (+) (рис. П4.19). При щелчке пользователем кнопкой мыши на знаке "плюс” отображаются все элементы данных объекта и их значе- ния. Если элемент делает ссылку на объект, тогда элементы данных этого объекта также Moiyr оказаться в спи- ске после щелчка на знаке "плюс". Многие логические ошибки являются результатом неправильных расчетов массивов. Для упрощения обнаруже- ния Таких ошибок в отладчике имеется функция отображения всех значений массива. На рис. П4.20 показано
Отладчик Visual Studio .NET 907 содержимое массива list. Объект в указателе о — массив int, развернутый для отображения его содержимого. Указатель 1 содержит объект DebugClass, развернутый для отображения элементов данных типа private объек- та, а также свойство public. Указатель 2 содержит объект Random, определенный в библиотеке классов FCL. it n>ain entry point for application [STAThread] , « public static void] Main() { DebugEntry entry “ new DebugEntry(); entry. Displ&yValues () ;| } // end class DebugEntry Рис. П4.17. Использование окна Immediate для отладки методов Рис. П4.18. Местоположение точки прерывания для отладки классов Рис. П4.19. Развернутый класс в окне Watch 1 Рис. П4.20. Развернутый массив в окне Watch 1 Отладчик Visual Studio содержит несколько других окон отладки, в число которых входят Threads, Modules, Memory, Disassembly и Registers. Эти окна используются опытными программистами для отладки крупных, комплексных проектов. Подробно данные функции описаны в документации Visual Studio .NET. В данном приложении продемонстрировано несколько способов отладки программ, методов и классов Отлад- чик Visual Studio .NET — это мощный инструмент, помогающий разработчикам создавать более отказоустойчи- вые программы. П4.7. Резюме Отладкой называется процесс обнаружения логических ошибок в программном приложении. Синтаксические ошибки (или ошибки компиляции) имеют место, когда операторы программы нарушают грамматические пра- вила языка программирования, и отслеживаются компилятором. Логические ошибки возникают, когда про- грамма компилируется в нормальном режиме, но работает не так, как ожидается. Отладчики обеспечивают раз- работчиков возможностью наблюдения за поведением программы во время ее исполнения. Точка прерывания — это метка, устанавливаемая в строке кода Когда программа сталкивается с точкой преры- вания, ее исполнение приостанавливается. Программист может изучить состояние программы и удостовериться в ее корректной работе. Для установки точек прерывания следует щелкнуть кнопкой мыши на сером участке слева от любой строки кода. Либо можно щелкнуть правой кнопкой мыши на строке кода и выбрать команду Insert Breakpoint. В окне Breakpoints отображаются все установленные в программе точки прерывания. От- ключенные точки прерывания позволяют разработчикам поддерживать их в важных местоположениях про- граммы с тем, чтобы при необходимости их можно было использовать повторно.
908 Приложение 4 В окне Watch программист может просматривать значения переменных и выражения. Для просмотра данных в поле Name следует ввести допустимое выражение С#, например, имя переменной. После ввода выражения его тип и значение появляются в полях Туре и Value. Пользователь может модифицировать переменные в окне Watch в целях тестирования. Для изменения значения переменной щелкните мышью в поле Value и введите новое значение. В окне Locals отображаются имя и текущее значение локальных переменных или объектов в текущей области. В окне Autos отображаются переменные и объекты, использованные в предыдущем операторе, а также текущий оператор (указанный желтой стрелкой). Окно Immediate используется для тестирования передаваемых в метод аргументов. Это помогает определить корректность работы метода. Для проведения оценки выражения в окне Immediate просто введите выражение в окно и нажмите клавишу <Enter>. В окне Call Stack содержится стек обращения к методам программы, позволяющий программистам определять точную последовательность вызовов, приводящих к текущему методу, а также просматривать вызывающие ме- тоды в стеке. При нажатии кнопки Continue исполнение приостановленной программы возобновляется. При нажатии кнопки Stop Debugging сеанс отладки заканчивается. Кнопка Break АП позволяет программистам приводить испол- няющуюся программу в режим прерывания. Кнопка Show Next Statement помещает курсор на ту же строку ко- да, на которой находится желтая стрелка, указывающая следующий оператор для исполнения. При нажатии кнопки Step Over отрабатывает следующая исполняемая строка кода, желтая стрелка переводится на очередную исполняемую строку и, наконец, исполнение программы приостанавливается. Если в исполненной строке кода содержится обращение к методу, тогда данный метод выполняется как единый шаг. При нажатии кнопки Step Into отрабатывает следующий оператор. Если оператор содержит обращение к методу, тогда управление пере- дается этому методу для построчной отладки. Если оператор не содержит обращения к методу, тогда операция нажатия кнопки Step Into такая же, что и операция при нажатии кнопки Step Over. Нажатие кнопки Step Out завершает исполнение метода, и управление возвращается к вызвавшей метод строке. С помощью кнопки Hex переключается формат отображения данных. При активизированной кнопке Hex данные отображаются в шест- надцатеричной форме (основание 16), а не в десятичной (основание 10). В Visual Studio .NET имеются функции отладки классов, позволяющие программистам определить текущее со- стояние любых объектов, использованных в программе. В помощь отладке классов в Visual Studio .NET преду- смотрены разворачивание и просмотр всех переменных элементов данных и свойств объекта, включая объяв- ленные как private.
ПРИЛОЖЕНИЕ 5 Создание документации в Visual Studio .NET Темы данного приложения: □ инструментальное средство создания документации Visual Studio .NET; □ комментарии XML-документации; □ теги документирования XML и их использование; □ создание файлов HTML- и XML-документации. П5.1. Введение Представленные в данной книге программы вполне доступны для реализации одному программисту. Однако промышленные программные средства намного сложнее, и каждый проект, как правило, требует профессиона- лизма и таланта группы разработчиков. При реализации таких проектов особую важность и необходимость при- обретает взаимодействие между программистами. При написании одним членом рабочей группы кода для клас- са другие члены группы должны понимать, как этот класс работает. По этой причине каждый программист должен документировать особую информацию о данном классе, например, его роль в системе, функциональ- ность, обеспечиваемую классу каждым методом, а также предназначение каждой переменной класса. Такая до- кументация помогает всем программистам понять способы взаимодействия классов, а также упрощает внесение изменений, использование и расширение каждого класса. Для облегчения задачи создания документации для проекта в Visual Studio .NET имеется специальный инстру- мент документации XML. С его помощью ключевые части информации преобразуются в исходный код, напри- мер, элементы класса, иерархия, к которой принадлежит класс, а также прочие общие замечания, которые про- граммист хочет задокументировать в формате HTML’ или XML1 2. Программист обозначает общие положения (замечания) для документирования путем помещения их в особых областях кода, называемых комментариями XML-документации. В данном приложении представлены функции составления документации в Visual Studio .NET. Начнем с обсуж- дения формата и структуры комментариев XML к документации, используемых инструментальным средством генерирования документации для создания файлов документации. Далее будет описан процесс создания доку- ментации на примере LIVE-CODE. Перед прочтением материала данного приложения авторы рекомендуют оз- накомиться с главами 5—7, потому что приводимые здесь примеры имеют отношение к примерам в указанных главах. П5.2. Комментарии к документации Перед тем, как инструмент создания документации Visual Studio .NET сможет генерировать файлы документа- ции, программист должен вставить в исходные файлы комментарии XML к документации. В этих комментари- ях содержится информация, которую программист хочет задокументировать. Инструмент создания документа- ции распознает только однострочные комментарии, которым предшествуют три левых косых черты (///). При- мером простых комментариев могут служить следующие: 1 Формат HTML рассматривается в приложениях 9 и 10. 2 Формат XML рассматривается в главе 5.
910 Приложение 5 III <summary> /// this is a comment /// </summary> В данном комментарии первая строка начинается с элемента summary, во второй строке указывается текст, кото- рый содержит этот элемент, а в третьей строке элемент summary закрывается. Далее описывается, почему инст- рументарий будет документировать текст только в рамках тегов summary. Все комментарии документации XML (за исключением трех косых черт) должны содержать правильно построенный XML. Подобно общим коммен- тариям, компилятор не может переводить комментарии к документации в промежуточный язык Microsoft Inter- mediate Language (MSIL), поэтому они не становятся "частью" программы. Из-за создания инструментом файлов XML комментарии к документации могут содержать определенные типы разметки, скажем, теги HTML и специализированное содержимое XML. Например, в комментарии /// summary> III Sorts integer array using <em>MySort</em> algorithm /// </summary> содержатся теги HTML <em> и </em>. В сгенерированных файлах HTML MySort появляется в виде выделенного текста (обычно курсивом). П5.3. Документирование исходного кода C# В листингах П5.1—П5.3 представлена модифицированная версия классов Point, circle и CircleTest из разд. 9.4, содержащая комментарии к документации XML. В тексте после примера рассматривается каждый элемент XML, использованный в комментариях XML к документации. В разд. П5.4 рассматривается процесс генерирования документации XML из данного файла1. 1 // Листинг П5.1: Point.cs 2 // Класс Point поддерживает координаты'X и Y 3 4 using System; 5 6 namespace CircleTest 7 { 8 /// <summary> 9 /// Class <c><b>Point</bx/c> defines a point as a pair 10 /// of x- and y-coordinates. 11 /// </summary> 12 public class Point 13 { 14 /// <summary> 15 /// This private member of <c><b>Point</bx/c> 16 /// represents the x-coordinate. 17 /// </summary> 18 /// <returns> The x-coordinate as an integer.</returns> 19 private int x; 20 21 /// <summary> 22 /// This private member of <c><b>Point</bx/c> 23 /// represents the x-coordinate. 24 /// </summary> 25 /// <returns> The у-coordinate as an integer.</returns> 26 private int y; 27 28 /// <summary> 29 /// Default constructor for class <c><b>Point</bx/c>-. 1 В листингах данного приложения комментарии для документирования приводятся в оригинальном виде (т. е. без перевода) для соответствия их рисункам. — Ред.
Создание документации в Visual Studio .NET 911 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 53 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 III </summary> III <remarks> III Sets properties <cxb>X</bx/c> and <c><b>Y</bx/c> to 0. /// </remarks> public Point() { // здесь имеет место обращение к конструктору базового класса } /// <summary> /// Constructor for <cxb>Point</bx/c> that accepts two /// integers that represent the x- and III у-coordinates of the point. Ill </summary> III <remarks> III Uses <cxb>X</bx/c> and <cxb>Y</bx/c> /// properties to set the coordinates of the point, /// <em>not</em> private members <cxb>xCoordinate</bx/c> III and <cxb>yCoordinate</bx/c>. /// </remarks> /// <param name=,'xValue"> /// The x-coordinate of the circle /// </param> III <param name=’'yValue "> /// The у-coordinate of the circle. Ill </param> public Point(int xValue, int yValue) { // здесь имеет место обращение к гонстрч’ктору базового класса X = xValue; Y = yValue; } /// <summary> /// Provides get and set access to member /// <c><b>xCoordinate</bx/c>. /// </summary> III <value> * Щ <cxb>X</bx/c> accesses the value of the III <cxb>xCoordinate</b></c> data member. /// </value> public int X { get { return x; } set { x - value; } } /// <summary> /// Provides get and set access to member /// <cxb>yCoordinate</bx/c>. /// </summary> III <value> III <c><b>Y</bx/c> accesses the value of the /// <cxb>yCoordinate</bx/c> data member. Ill </value> public int Y
912 Приложение 5 93 { 94 get 95 { 96 return у; 97 } 98 99 set 100 { 101 у = value; 102 } 103 } 104 105 III <summary> 106 /// Converts the <c><b>Point</bx/c> to 107 /// <b>string</b> format. 108 III </summary> 109 /// <returns> 110 /// Returns a string in format: 111 /// [x-coordinate, y-coordinate]. 112 III </returns> 113 public override string Tostring() 114 { 115 return "[" + X + ” + ¥ + 116 } 117 118 } // окончание класса Point 119 } Листинг П5.2 Класс Cxrcle, размеченный комментариями XML / s.уй- 1 // Листинг П5.2: Circle.cs 2 // Класс Circle наследует от класса Point 3 4 using System; 5 6 namespace CircleTest 7 { 8 /// <summary> 9 III Class <c><b>Circle</b></c> inherits from class 10 /// <c><b>Point</bX/o. It has an additional member to 11 III represent the radius> a property that provides access 12 /// to it and method <cxb>Area</bx/c> to compute the area 13 /// of the circle. 14 /// </summary> 15 public class Circle : Point 16 { 17 /// <summary> 18 /// This private member of <cxb>Circle</bx/c> 19 /// represents the radius. 20 /// </summary> 21 private double radius; ’ 22 23 /// <summary> 24 /// Default constructor for class <cxb>Circle</bx/c>. 24 /// </summary> 26 - /// <remarks> 27 /// Sets the radius to 0.0. 28 III </remarks> 29 public Circle() 30 { 31 // неявное обращение к конструктору базового класса .32 } 33
Создание документации в Visual Studio .NET 913 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 '76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 /// <summary> /// Constructor for <oCircle</c> that accepts two'integers III that represent the x- and у-coordinates of the circle /// and a <b>double</b> that represents the radius. /// </summary> /// <remarks> /// Uses property <c><b>Radius</bx/c> to set the radius /// of the circle, <em>not</em> private member /// <c><b>radius</bx/c>. /// </remarks> /// <param name="xValue"> III The x-coordinate of the circle III <l'paraTti> -< II/ <param name="yValue"> /// The у-coordinate of the circle. /// </param> /// <param name="radiusValue"> /// The radius of the circle. /// </param> public Circle(int xValue, int yValue, double radiusValue) : base(xValue, yValue) ( Radius = radiusValue; } Ш <summary> III Provides get and set access to member /// <c><b>radius</bx/c>. /// </summary> /// <remarks> /// The <cxb>set</bx/c> method ensures that /// <c><b>radius</bx/c> is <em>not</em> set to a negative /// number, and sets it to 0.0 if there is an attempt to /// set <cxb>radius</b>x/c> to do so. /// </remarks> /// <value> III <cxb>Radius</bx/c> accesses the value of the III <c><b>radius</bx/c> data member. Ill </value> public double Radius { get { return radius; ) set { if (value >= 0) radius = value; } } /// <summary> /// Computes the diameter of the circle.» /// </summary> /// <returns> /// Returns the diameter of the circle.' /// </returns> public double Diameter() { return Radius * 2; ) 58 Зак. 3333
914 Приложение 5 91 98 /// <summary> 99 /// Computes the circumference of the circle. 100 /// </summary> |01 III <remarks> 102 /// Uses constant <c><b>Math.PI</bx/c> 103 /// <see cref="System.Math.PI"/> 104 III </remarks> 105 /// <returns> 106 /// Returns the circumference of the circle. 107 /// </returns> 108 public double Circumference() 109 { 110 return Math.PI * Diameter(); 111 } 112 113 /// <summary> 114 /// Computes the area of the circle. 115 /// </summary> 116 /// <remarks> 117 /// Uses constant <cxb>Math.PI</bx/c> 118 III <see cref="System.Math.PI"/> 119 /// </remarks> 120 /// <returns> 121 /// Returns the area of the circle. 122 III </returns> 123 public double Area() 124 { 125 return Math.PI * Math.Pow(Radius, 2); 126 } 127 128 /// <summary> 129 III Converts the <cxb>Circle</bx/c> to 130 III <b>string</b> format. 131 /// </summary> 132 /// <remarks> 133 /// Overrides <c><b>ToString</bx/c> method of base class. 134 /// <see cref="CircleTest.Point.Tostring"/> 135 /// </remarks> 136 /// <returns> 137 /// Returns a string that includes the center of the 138 /// circle and its radius. 139 III </returns> 140 public override string ToString() 141 { 142 return "Center = " + base.ToString() + 143 Radius = " + Radius; 144 } 145 146 } // окончание класса Circle 147 } 1 // Листинг П5.3: CircleTest.cs 2 // Манипуляции объектом Circle 3 4 using System; 5 using System.Windows.Forms; 6 7 namespace CircleTest 8 {
Создание документации в Visual Studio .NET 915 9 /// <summary> 10 /// Class <c><b>CircleTest</bx/c> test the 11 /// <c><b>Point</bx/c> and <cxb>Point</bx/c> classes. 12 III </summary> 13 class CircleTest 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 /// <summary> /// Entry point of application. /// </summary> /// <remarks> /// In this application all command-line arguments /// are ignored. /// </remarks> /// <param name="args"> /// Optional arguments to Main. Ill </param> static void Main(string[] args) { Circle circle = new Circle(37, 43, 2.5); // добавление свойств Circle к выходным данным string output - "X coordinate is " + circle.X + "\n" + "Y coordinate is " + circle.Y + "\n" + "Radius is " + circle.Radius; Рис. П5.1. Окно работающей программы 33 34 // установка новых координат и радиуса 35 circle.X =2; 36 circle.Y = 2; 37 circle.Radius = 4.25; 38 39 output += "\n\n" + 40 "The new location and radius of circle are " + 41 "\n" + circle + "\n"; 42 43 // отображение диаметра круга 44 output += "Diameter is " + 45 String.Format("{0:F}", circle.Diameter()) + "\n"; 46 47 // отображение длины окружности 48 output += "Circumference is " + 49 String.Format("{0:F}", circle.Circumference()) + "\n"; 50 51 // отображение площади окружности 52 output += "Area is " + 53 String.Format(”{0:F)", circle.AreaO); 54 55 MessageBox.Show(output, "Demonstrating Class Circle"); 56 57 } // окончание метода Main 58 59 } // окончание класса Circletest 60 } Результат работы программы представлен на рис. П5.1. Комментарии XML-документации можно размещать перед определением класса, интерфейса, конструктора или элемента (т. е. переменной экземпляра или ссылкой). Программист может разместить описание (т. е. предназна- чение) класса в элементе summary. Элемент summary может содержать столько строк, сколько необходимо для описания метода класса, свойств, элементов и т. д. В следующем разделе отмечается, что любое содержимое, размещенное в элементе summary, будет размечено в столбце (помеченном Description) таблицы HTML. Пример элемента summary приведен в строках 8—11 листинга П5.1 для описания класса Point. (Эти теги также исполь- зуются в разд. П5.2 при описании комментариев к документации.^ Двумя широко используемыми элементами для описания методов являются returns и param. Элемент returns содержит информацию о возвращаемом значении, как проиллюстрировано в строках 109—112 листинга П5.1.
916 Приложение 5 Метод Tostring класса Point возвращает отформатированную строку, имеющую пару координат х и у точки. Точно так же элемент param содержит информацию о параметрах метода. Например, в строках 50—55 листин- га П5.1 один элемент param соотносится с переменной х, а другой элемент param— с переменной у. Элементы с XML используются для разметки частей кода в комментариях. В строке 102 листинга П5.2 демон- стрируется использование элемента с для указания, что объект Math.pi необходимо пометить как код в резуль- тирующей документации. Обратите внимание, что элемент с содержит элемент Ь, размещающий на Web- странице объект Math.Pi, выделенный полужирным шрифтом. Тег see (строки 103, 118 и 134 листинга П5.2) делает ссылку на другой класс или элемент (метод, константу, свойство и т. д.). Ссылку на любой элемент можно сделать указанием полного имени (например, System.Console.ReadLine). Тег value (строки 67—70 и 88—91 листинга П5.1 и строки 69—72 листинга П5.2.) используется для описания свойств. Эти комментарии не влияют на Web-страницы комментариев, которые мо- гут быть созданы. Более подробную информацию об описанных и других тегах см. на следующем URI: ms-help://MS.VSCC /MS.MSDNVS/csref/html/vclrftagsfordocumentationcomments.htm. П5.4. Создание Web-страниц документации В данном разделе демонстрируется создание Visual Studio .NET документации в формате Web-страницы из ис- ходного кода, содержащего комментарии XML-документации. Функция демонстрируется на проекте, содержа- щем классы в листингах П5.1—П5.3. После открытия проекта выберите Tools | Build Comment Web Pages... (рис. П5.2). Откроется окно Build Comment Web Pages, в котором разработчик может указать проект (проек- ты), содержащие файлы для документирования Visual Studio .NET (рис. П5.3). При выборе разработчиком пере- ключателя Build for entire Solution Visual Studio .NET задокументирует все файлы текущего программного ре- шения. При выборе разработчиком переключателя Build for selected Projects Visual Studio .NET задокументи- рует только те файлы проекта, которые укажет разработчик. Кроме того, разработчик может указать каталог, в котором Visual Studio .NET следует сохранить сгенерированное содержимое в формате HTML. Если разработ- чик отметит флажок Add to Favorites, Visual Studio .NET создаст закладку данного содержимого в меню Favorites браузера Internet Explorer. Рис. П5.2. Выбор команды Build Comment Web Pages в меню Tools Рис. П5.3. Сохранение документа в файл Для создания содержимого в формате HTML нажмите кнопку ОК. Система Visual Studio сразу создаст и ото- бразит документацию с помощью таблицы стилей. В приводимом примере пользователь может просмотреть взаимосвязь из классов circle, circleTest и Point выбором нужного класса в крайнем левом столбце. На рис. П5.4 представлена документация для класса Circle. Обратите внимание, что имена всех членов класса и элементов summary в листинге П5.2 были отформатированы и размещены в столбцах Members и Description соответственно. При выборе элемента из столбца Members открывается страница HTML, относящаяся к данному элементу На рис. П5.5 показана страница HTML, связан- ная с методом Area класса circle. Обратите внимание, что тег returns в строках 120—122 листинга П5.2 раз- мечает текст, задокументированный как текст, помещенный в столбец Description.
Создание документации в Visual Studio .NET 917 Рис. П5.4. HTML-документация класса Circle Рис. П5.5. HTML-документация метода Area класса Circle П5.5. Создание файлов XML-документации В данном разделе рассматривается создание файла XML-документации, содержащего все элементы в коммен- тариях исходного кода. После этого приложение может считывать данный файл и создавать по его информации специализированную документацию. Для создания файла XML-документации проекта щелкните правой кнопкой мыши на проекте в окне Solution Explorer и выберите команду Properties. Выберите папку Configuration, после чего — вкладку Build. Измените свойство XML Documentation File на имя файла, в котором будет сохранена документация XML, и нажмите кнопку ОК. Если этого файла не существует, тогда система Visual Studio создаст его и разместит в каталоге bin/Debug текущего проекта. Выберите команды Build | Build Solution для компиляции проекта и создайте до- 4
918 Приложение 5 кумент XML. В листинге П5.4 представлен документ XML, сгенерированный для примера, приведенного в лис- тингах П5.1—П5.3. j Листинг П5.4. Класс CircleTest, размеченный комментариями XML ••**** 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 2b 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 <?xml version="1.0,,?> <doc> <assembly> <name>Point-Circle</name> </assembly> <members> <member name="Y:CircleTest.Circle"> <summary> Class <c><b>Circle</bx/c> inherits from class <cxb>Point</bX/c>. It has an additional member to represent the radius, a property that provides access to-it and method <c><b>Area</bx/c> to compute the area of the circle. </summary> </member> <member name="T:CircleTest.Point’’> <summary> Class <cxb>Point</bx/c> defines a point as a pair of x and у coordinates. </summary> </member> <member name="F:CircleTest.Point.xCoordinate”> <summary> This protected member of <cxb>Point</bx/c> represents the x coordinate. </sunmary> <returns> The x coordinate as an integer.</returns> </member> <member name="F:CircleTest.Point.yCoordinate”> <summary> This protected member of <c><b>Point</bx/c> represents the у coordinate. </summary> <returns> The у coordinate as an integer.</returns> </member> cmember name="M:CircleTest.Point.#ctor"> <summary> Default constructor for class <c><b>Point</bx/c>. </summary> <remarks> Sets properties <cxb>X</bx/c> and <c><b>Y</bx/c> to 0. </remarks> </member> <member name= "M:CircleTest.Point.#ctor(System.Int32,System.Int32)"> <summary> Constructor for <cxb>Point</bx/c> that accepts two integers that represent the x and у coordinates of the point. </summary>
Создание документации в Visual Studio .NET 919 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 <remar£s> Uses <O<b>X</bx/c> and <cxb>Y</bx/c> properties to set the coordinates of the point, <em>not</em> private members <cxb>x</bx/c> and <o<b>y</bx/c>. </remarks> <param name="xValue,,> The x coordinate of the circle </param> <param name=”yValue"> The у coordinate of the circle. </param> </member> <member name="M:CircleTest.Point.ToString"> <summary> Converts the <c><b>Point</bx/c> to <b>string</b> format. </summary> <returns> Returns a string in format: [x coordinate, у coordinate]. </returns> </member> <member name="P:CircleTest.Point.X"> <summary> Provides get and set access to member <O<b>x</bX/c>. </summary> <value> <O<b>X</bx/c> accesses the value of the <c><b>x</bx/c> data member. </value> </member> <member name="P:CircleTest.Point. Y"> <summary> Provides get and set access to member <c><b>y</bx/c>. </surranary> <value> <c><b>Y</bx/c> accesses the value of the <cxb>y</bx/c> data member. </value> </member> <member name="F:CircleTest.Circle.radius"> <summary> This private member of <c><b>Circle</bx/c> represents the radius. </summary> </member> <member name=**M:CircleTest .Circle. #ctor"> <summary> Default constructor for class <cXb>Circle</b></c>. </summary> <remarks> Sets the radius to 0. </remarks> </member>
920 Приложение 5 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 ^member name="M:CircleTest.Circle.#ctor(System.Int32, System.Int32,System.Double)"> <summary> Constructor for <c><b>Circle</bx/c> that accepts two integers that represent the x and у coordinates of the circle and a <b>double</b> that represents the radius. </summary> <remarks> Uses property <c><b>Radius</bx/c> to set the radius of the circle, <em>not</em> private member <cxb>radius</bx/c>. </remarks> <param name="xValue"> The x coordinate of the circle </param> <param name="yValue"> The у coordinate of the circle. </param> <param name="radiusValue"> The radius of the circle. </param> </member> <member name="M:CircleTest.Circle.Diameter"> <summary> Computes the diameter of the circle. </summary> <returns> Returns the diameter of the circle. </returns> </member> <member name="M:CircleTest.Circle.Ci rcumference"> <summary> Computes the circumference of the circle. </summary> *<remarks> Uses constant <cxb>Math.PI</bx/c> <see cref•"F: System. Math. PI" /> </remarks> <returns> Returns the circumference of the circle. </returns> </member> <member name="M:CircleTest.Circle.Area”> <summary> Computes the area of the circle. </summary> <remarks> Uses constant <c><b>Math.PI</bx/c> <see cref="F:System.Math.PI"/> </remarks> <returns> Returns the area of the circle. </retums> </member> <member name="M:CircleTest.Circle.ToString"> <summary> Converts the <cxb>Circle</bx/c> to <b>string</b> format. </summary>
Создание документации в Visual Studio .NET 921 183 <remarks> 184 Overrides <c><b>ToString</b></c> method of base class. 185 <see cref="!:CircleTest.Point.Tostring"/> 186 </remarks> 187 <returns> 188 Returns a string that includes the center of the t 189 circle and its radius. 190 </returns> 191 </member> 192 193 <member name="P:CircleTest.Circle.Radius"> 194 <summary> z 195 Provides get and set access to member ’ 196 <c><b>radius</bx/c>. 197 </summary> 198 <remarks> 199 The <c><b>set</bx/c> method 200 ensures that <c><b>radius</bx/c> t 201 is <em>not</em> set to a 202 negative number. 203 </remarks> ' , 204 <value> 205 <cxb>Radius</b></c> accesses the value of the 206 <cxb>radius</bx/c> data member. 207 </value> 208 </member> 209 210 <member name="T:CircleTest.CircleTest"> 211 <summary> 212 Class <c><b>CircleTest</bx/c> inherits from class 213 tests the <cxb>Point</b></c> and 214 <cxb>Point</bx/c> classes. 215 </summary> 216 </member> 217 218 <member name="M:CircleTest.CircleTest.Main(System.String[])">"> 219 <summary> 220 Entry point of application. 221 </summary> 222 <remarks> 223 In this application all command-line arguments 224 are ignored. 225 </remarks> * 226 <param name="args"> 227 Optional arguments to Main. 228 </param> 229 </member> 230 231 </members> 232 </doc> Обратите внимание, что в сгенерированный файл XML включены только элементы класса. Каждый элемент класса имеет элемент member, включающий в себя все комментарии XML для этого элемента. НапримерГ, в стро- ках 50—69 определен элемент member, содержащий информацию о конструкторе Point, состоящем из двух ар- гументов. Атрибут name тега <member> представляет собой строку, содержащую информацию об имени и типе элемента. Тип обозначается прописной буквой: м — метод, р — свойство (или индексатор), е — событие, ат — тип (т. е. класс). Для получения полного списка аббревиатур выберите команду Help | Index, после чего опреде- лите местоположение строки processing XML files in С#. В листинге П5.3 в строке 51 содержится значение ат- рибута name, и первая буква — м; это означает, что в строке 51 объявляется метод. (Конструктор представляет собой специализированный метод.) После буквы введено двоеточие и полное имя метода. Для данного приме- ра — ЭТО CircleTest.Point.#ctor (System. Int32, System.Int32). По причине ТОГО, ЧТО ЭТО конструктор, в ПОЛ- НОМ имени используется #ctor. Данный конструктор принимает две переменные int со скобками после имени каждого элемента, в которые заключен тип элемента.
922 Приложение 5 П5.6. Резюме ♦ Представленные в данной книге программы вполне доступны для реализации одному программисту. Однако промышленные программные средства намного сложнее, и каждый проект, как правило, требует профессиона- лизма и талантов группы разработчиков. При реализации таких проектов особую важность и необходимость приобретает взаимодействие между программистами. При написании одним членом рабочей группы кода для класса другие члены группы должны понимать, как этот класс работает. По этой причине каждый программист должен составлять документацию по специфической информации о данном классе, например, о его роли в сис- теме, о функциональности, обеспечиваемой классу каждым методом, а также предназначение каждой перемен- ной класса. Такая документация помогает всем программистам понять способы взаимодействия классов, а так- же упрощает внесение изменений, использование и расширение каждого класса. Для облегчения задачи создания документации для проекта в Visual Studio .NET имеется специальный инстру- мент документирования XML. С его помощью ключевые части информации преобразуются в исходный код, например, элементы класса, иерархия, к которой принадлежит класс, а также прочие общие замечания, которые программист хочет задокументировать в формате HTML или XML. Программист обозначает общие положения (замечания) для документирования путем размещения их в особых областях кода, называемых комментариями XML-документации. Перед тем, как инструмент создания документации Visual Studio .NET сможет генерировать файлы документа- ции, программист должен вставить в исходные файлы комментарии XML к документации. В этих комментари- ях содержится информация, которую программист хочет задокументировать. Инструмент создания документа- ции распознает только однострочные комментарии, которым предшествуют три левые косые черты (///). После добавления в исходный файл комментариев XML-документации разработчик может создавать Web- страницы комментариев выбором команд Tools | Build Comment Web Pages..., При этом откроется окно Build Comment Web Pages, в котором разработчик указывает проект (проекты), содержащие файлы для документи- рования Visual Studio .NET. При выборе разработчиком переключателя Build for entire Solution Visual Studio .NET задокументирует все файлы текущего программного решения. При выборе разработчиком переключателя Build for selected Projects Visual Studio .NET задокументирует только те файлы проекта, которые укажет разра- ботчик. Кроме того, разработчик может указать каталог, в котором системе Visual Studio .NET следует сохра- нить сгенерированное содержимое в формате HTML. При выборе разработчиком флажка Add to Favorites Visual Studio .NET создаст закладку данного содержимого в меню Favorites браузера Internet Explorer. Разработчик также может создавать файлы XML-документации по комментариям к ним. Информацию в таких файлах можно форматировать с помощью приложений третьих фирм с целью создания более специализирован- ной документации. Для создания файла XML-документации проекта щелкните правой кнопкой мыши на проек- те в окне Solution Explorer и выберите команду Properties. Выберите папку Configuration, после чего — вкладку Build. Измените свойство XML Documentation File на имя файла, в котором будет сохранена докумен- тация XML, и нажмите кнопку ОК.
ПРИЛОЖЕНИЕ 6 (ш Набор символов ASCII На рис. П6.1 представлена таблица символов ASCII. Цифры слева от таблицы — левые цифры десятичного эк- вивалента (0—127) кода символа, а цифры над таблицей — правые цифры кода символов. Например, код сим- вола F — 70, а код символа & — 38. Большинству читателей будет интересно знать, какие символы входят в набор ASCII, используемый для обозна- чения английских символов на многих компьютерах. Набор символов ASCII — это подмножество набора сим- волов Unicode, используемого в C# для обозначения символов на большинстве компьютеров мира. Подробно набор символов Unicode описан в приложении 7. 0123456789 nul soh stx etx eot enq ack bel - bs ht nl vt ff cr so si die del dc2 dc3 dc4 nak syn etb can em sub esc fs gs rs us sp • i II # $ % & 1 ( ) * + f - • / 0 1 2 3 4 5 6 7 8 9 : r < = > 7 @ A В C D E F G H -I J К L M N 0 P Q R S T U V W X Y Z [ \ ] A — ' a b c d e f g h i j k 1 m n о P q r s t u V w X У z { 1 } del Рис. П6.1. Набор символов ASCII
ПРИЛОЖЕНИЕ 7 Unicode (ш Темы данного приложения: □ ознакомление с кодировкой символов Unicode; □ обсуждение миссии Консорциума Unicode; □ рассмотрение основ проектирования Unicode; □ понятие трех форм кодирования Unicode: UTF-8, UTF-16 и UTF-32; □ представление символов и глифов; □ обсуждение преимуществ и недостатков использования Unicode; □ краткий обзор Web-сайта Консорциума Unicode. П7.1. Введение При разработке глобальных программных продуктов использование не всегда совместимых кодировок симво- лов (т. е. числовых значений, сопоставленных с символами) сопряжено с серьезными проблемами, потому что компьютеры обрабатывают информацию, представленную цифрами. Например, символ "а" преобразуется в чи- словое значение с тем, чтобы компьютер имел возможность манипулирования с этой частицей данных. Многие страны и корпорации разработали собственные системы кодирования, несовместимые с системами кодировок других стран и компаний. Например, в операционной системе Microsoft Windows символу "А с грависом"1 (А) присваивается значение ОхСО, тогда как в операционной системе Apple Macintosh такое же значение присваива- ется символу перевернутого вопросительного знака £ (используется в испанском языке). В результате этого имеет место ошибочное распознавание символов, и данные могут быть повреждены из-за их некорректной об- работки. В отсутствие повсеместно распространенного универсального стандарта кодирования символов разработчики глобальных программных средств вынуждены тщательно локализовать свои продукты перед тем, как предло- жить их на рынке. В процесс локализации входит языковой перевод и культурная адаптация содержимого, на- ряду с внесением значительных изменений в исходный код (например, при преобразовании числовых значений и фундаментальных допущений, сделанных разработчиками); все это повышает затраты и задерживает выпуск программных средств. Например, некоторые англо-говорящие программисты могут спроектировать глобальные программные продукты, допустив, что один символ может обозначаться одним байтом. Однако при локализа- ции этих продуктов для азиатских стран такое допущение будет некорректным, поэтому большую часть кода (если не весь код целиком) придется переписывать. Локализация необходима для каждой версии любого про- граммного продукта. К тому времени, как продукт будет локализован для определенного рынка, уже могут быть готовые его новые версии, которые также требуют локализации. Именно поэтому производство глобальных программных средств и их распространение на рынках, где отсутствует универсальная система кодирования символов, — дело не очень благодарное и весьма затратное. Для решения этой проблемы был создан стандарт Unicode— стандарт кодировки символов, облегчающий производство и распространение программных продуктов В стандарте Unicode изложена спецификация после- довательной кодировки практически любых знаков и символов, используемых в мире. Программные продукты, обрабатывающие текст, закодированный в соответствии со стандартом Unicode, также требуют локализации, но 1 Специальный знак в виде прямой черты над гласной буквой, левый конец которой выше правого; используется в разных значени- ях в разных письменных традициях.
Unicode 925 этот процесс осуществляется гораздо проще и более эффективно, потому что числовые значения преобразовы- вать не нужно, и все допущения разработчиков о кодировке — универсальны. Стандарт Unicode поддерживает- ся некоммерческой организацией под названием Консорциумом Unicode (Unicode Consortium), в число членов которой входят такие корпорации, как Apple, IBM, Microsoft, Oracle, Sun Microsystems, Sybase и многие другие. При замысле и разработке стандарта Unicode целью консорциума было добиться его универсальности, эффек- тивности, однородности и однозначности. Универсальная система кодировки охватывает все так или иначе используемые символы. Эффективная система кодировки облегчает синтаксический и грамматический анализ текстовых файлов. В однородной системе кодировки всем символам присваиваются фиксированные значения. В однозначной системе конкретный символ представлен согласованно. Эти четыре термина называются осно- вами проектирования по стандарту Unicode. П7.2. Форматы преобразования Unicode Несмотря на то, что в Unicode входит ограниченный набор символов ASCII, стандарт охватывает гораздо более расширенный их набор. В ASCII каждый символ представлен в виде одного байта, состоящего из единиц и ну- лей. В одном байте можно сохранять двоичные числа от 0 до 255. Каждому символу присваивается число от О до 255; следовательно, системы на базе ASCII могут поддерживать только 256 символов — мизерную ча^ть то- го, что существует в мире. Unicode расширяет набор символов ASCII путем кодирования подавляющего боль- шинства всех символов, встречающихся в мировых языках. Стандарт Unicode кодирует их в однородном число- вом пространстве от 0 до шестнадцатеричного 10FFFF. При реализации все числа будут выражены в одном из нескольких форматов преобразования с выбором наиболее подходящего из них для того или иного программно- го приложения. В настоящее время применяются три таких формата— UTF-8, UTF-16 и UTF-32, — в зависимости от размера (в битах) используемых элементов. UTF-8 — форма кодирования переменной ширины — для обозначения ка- ждого символа в системе Unicode требует от одного до четырех битов. Данные в формате UTF-8 состоят из 8-битовых байтов (последовательности из одного, двух, трех или четырех байтов, в зависимости от кодируемого символа) и хорошо соответствуют системам, поддерживающим стандарт ASCII, при подавляющем большинстве однобайтовых символов (стандарт ASCII обозначает символы в виде одного байта). В настоящее время формат UTF-8 широко распространен в системах на базе UNIX и в базах данных. Примечание___________________________________________________________________________ Сейчас формат UTF-8 поддерживают только браузеры Internet Explorer 6.0 и Netscape Communicator 6, поэтому техническим писателям следует пользоваться UTF-8 для кодирования документов в форматах XML и XHTML. Форма кодировки переменной ширины UTF-16 выражает символы Unicode в виде блоков из 16 битов (т. е. как два смежных байта, либо, на-многих компьютерах, как целое короткое число). Большинство символов системы Unicode выражено в виде одного блока, состоящего из 16 битов. Однако символы со значениями более шестнад- цатеричного FFFF обозначаются упорядоченной парой 16-битовых блоков, называемых суррогатами. Суррога- ты — это 16-битовые целые числа в диапазоне от D800 до DFFF, используемые исключительно для "выхода" к символам с большими цифровыми значениями. Таким способом можно обозначить порядка миллиона симво- лов. Несмотря на то, что для обозначения символов суррогатная пара требует 32 бита, в ней достаточно места для использования 16-битовых блоков. В настоящее время суррогаты используются редко. Многие продукты, обрабатывающие строки, пишутся в формате UTF-16. Примечание___________________________________________________________________________ Подробности и пример кода кодировки UTF-16 доступны на сайте Консорциума Unicode — www.unicode.org, В программных продуктах, требующих частого использования редких символов или целых сценариев, закоди- рованных значениями более чем шестнадцатеричное FFFF, следует использовать UTF-32 — 32-битовую форму кодирования фиксированной ширины, обычно требующей в два раза больше памяти, нежели символы в форма- те кодировки UTF-16. Основное преимущество формы кодирования фиксированной ширины UTF-32 заключа- ется в том, что все символы в ней выражаются однородно: при этом значительно облегчается обработка мас- сивов. Существуют определенные рекомендации о том, в каких случаях использовать ту или иную форму кодировки. Оптимальная форма кодировки зависит от компьютерных систем и бизнес-протоколов, а не только от данных. Форма кодирования UTF-8, как правило, рекомендуется к использованию, когда компьютерная система или бизнес-протоколы требуют обработки данных в виде 8-битовых блоков, особенно в обновляемых патентован- ных системах, потому что при этом значительно облегчается процесс внесения изменений в существующие про- граммы. По этой причине формат UTF-8 был выбран как форма кодировки в Интернете. Подобным же образом формат UTF-16 выбран для кодировки в Windows-приложениях Microsoft. Возможно, что UTF-32 станет более
926 Приложение 7 популярным в будущем, по мере того, как большее количество символов станет кодироваться выше шестнадца- теричного FFFF. При всем том, UTF-32 требует менее сложной обработки, чем UTF-16, при наличии суррогат- ных пар. В табл. П7.1 представлены различные способы, с помощью которых три описанные формы осуществ- ляют кодирование символов. Таблица П7.1. Соотношение между тремя формами кодирования Символ UTF-8 UTF-16 UTF-32 LATIN CAPITAL LETTER А 0x41 0x0041 0x00000041 GREEK CAPITAL LETTER ALPHA OxCD 0x91 0x0391 0x00000391 CJK UNIFIED IDEOGRAPH-4E95 0xE4 OxBA 0x95 0x4E95 0x00004E95 OLD ITALIC LETTER A OxFO 0x80 0x83 0x80 OxDCOO OxDFOO 0x00010300 П7.3. Символы и глифы Стандарт Unicode состоит из символов — написанных компонентов (букв алфавита, цифр, знаков пунктуации, знаков ударения и т. д.), которые могут обозначаться числовыми значениями. В число примеров символов вхо- дит U+0041 LATIN CAPITAL LETTER А. В первом обозначении символа U+yyyy — значение кода, где U+ от- носится к значениям кода Unicode, в отличие от других шестнадцатеричных значений, уууу обозначает четырех- значное шестнадцатеричное число закодированного символа. Значения кода представляют собой комбинации битов, соответствующие закодированным символам. Символы обозначаются и отображаются с помощью гли- фов — различных форм, гарнитур шрифтов и размеров. В стандарте Unicode для глифов не существует значе- ний кода. Примеры глифов показаны на рис. П7.1. А А Я А Я Л Рис. П7.1. Разные глифы символа буквы А Стандарт Unicode охватывает алфавиты, слоговые азбуки, знаки препинания, диакритические знаки, математи- ческие операторы и т. д. большинства мировых языков, имеющих письменность Диакритическими знаками называются специальные знаки, добавляемые к символу для выделения его от другой буквы, либо для указания ударения (например, знак "тильда" (~) в испанском языке над буквой ”п" для обозначения мягкости). В настоя- щее время Unicode представляет кодовые значения для 94 140 символов с более чем 880 000 кодовых значений для последующего дополнения. П7.4. Преимущества и недостатки Unicode Стандарт Unicode обладает несколькими значительными преимуществами, обуславливающими его использова- ние. Одно из них — влияние, оказываемое на эффективность международной экономики. Unicode стандартизи- рует символы мировых систем письма в единую модель, обеспечивающую передачу данных и обмен ими. Про- граммы, разработанные с использованием такой схемы, являются очень точными, потому что каждый символ имеет единое определение (т. е. буква "а" всегда имеет код U+0061, символ % — всегда U+0025). Это позволяет корпорациям справляться с повышенными требованиями международных рынков одновременной обработкой различных мировых систем письма. При этом все символы обрабатываются одинаково, без путаницы, вызывае- мой различными архитектурами кодировки символов. Более того, последовательная и однородная обработка данных устраняет риск их повреждения, потому что данные можно сортировать, осуществлять их поиск и мани- пуляции единым и однообразным процессом. Другим преимуществом стандарта Unicode является его портативность — возможность поддержки программ- ного обеспечения на разных компьютерах или с разными операционными системами. Большинство современ- ных операционных систем, баз данных, языков программирования и Web-браузеров поддерживают или плани- руют поддерживать систему Unicode. Кроме того, Unicode включает в себя гораздо более расширенный набор символов (хотя и не все, используемые в мире), по сравнению с любым другим стандартом кодировки. Недостатком Unicode является объем памяти, требуемый форматами UTF-16 и UTF-32. Наборы символов ASCII имеют длину 8 битов, поэтому они требуют меньшего объема памяти, нежели 16-битовый набор символов
Unicode 927 Unicode, используемый по умолчанию. Однако двухбайтовый набор символов (Double-Byte Character Set, DBCS) и многобайтовый набор символов (Multi-Byte Character Set, MDCS), кодирующие символы азиатских языков (идеограммы) требуют от двух до четырех байтов, соответственно. В таких случаях можно использовать формы кодировки UTF-16 или UTF-32 с небольшими потерями объема памяти и производительности. П7.5. Web-сайт Консорциума Unicode Подробная и расширенная информация о стандарте Unicode представлена на Web-сайте www.unicode.org. В настоящее время на домашней странице сайта имеется несколько разделов: New to Unicode (Новое в Unicode), General Information (Общая информация), The Consortium (Консорциум), The Unicode Standard (Стандарт Unicode), Work in Progress (Текущие разработки) и For Members (Страница для зарегистрирован- ных пользователей). Раздел New to Unicode состоит из двух подразделов: What is Unicode? (Что такое Unicode?) и How to Use this Site (Как пользоваться сайтом). В первом подразделе представлено техническое введение в Unicode с описанием принципов проектирования, интерпретаций символов и присвоения, обработки текста и соответствия Unicode. Данный подраздел рекомендуется для ознакомления тем, кто никогда не сталкивался с системой кодировки Unicode. Также здесь представлен список ссылок на страницы с дополнительной информацией о Unicode. В разделе How to Use this Site содержится информация о пользовании сайтом, а также гиперссылки на допол- нительные ресурсы. Раздел General Information состоит из шести подразделов: Where is my Character? (Где мой символ?), Display Problems? (Отображение проблемных ситуаций), Useful Resources (Полезные ресурсы), Enabled Products (Раз- решенные продукты), Mail Lists (Списки рассылки) и Conferences (Конференции). Основные сферы, охваты- ваемые в данном разделе, включают ссылку на таблицы кодов (полные списки кодовых значений), составлен- ные Консорциумом Unicode, а также подробное описание процесса поиска закодированного символа в таблице кодов. В данном разделе представлены рекомендации по конфигурированию различных операционных систем и Web-браузеров для удобного просмотра символов Unicode. Более того, из данного раздела пользователь может переходить на другие сайты с информацией по разным темам, например, о гарнитурах шрифтов, о лингвистиче- ских подробностях, а также к другим стандартам — American Standard Page и Chinese GB 18030 Encoding Standard. Раздел The Consortium состоит из пяти подразделов: Who we are (Общая информация о консорциуме), Our Members (Наши члены), How to Join (Процедура вступления в консорциум), Press Info (Информация для прес- сы) и Contact Us (Контакты). Здесь представлен список действительных членов Консорциума Unicode, а также информация о вступлении. Здесь же описаны привилегии для каждого типа членства— полноправный член, кандидат в члены, специалист и физическое лицо, а также взносы по каждой категории. Раздел Unicode Standard состоит из девяти подразделов: Start Неге (Вход), Latest Version (Последняя версия), Technical Reports (Технические отчеты), Code Charts (Таблицы кодов), Unicode Data (Данные Unicode), Updates & Errata (Обновление и списки опечаток), Unicode Policies (Правила Unicode), Glossary (Глоссарий) и Technical FAQ (Технические вопросы и ответы). Здесь описываются обновления последней версии стандарта Unicode, а также категории всех заданных кодировок. Пользователь имеет возможность изучения изменений, внесенных в последнюю версию для включения большего количества функций и возможностей. Например, од- ним из усовершенствований версии 3.2 стало добавление в нее закодированных символов. Любые используемые в консорциуме термины, не знакомые пользователю, описаны в разделе глоссария. Раздел Work in Progress состоит из трех подразделов: Calendar of Meetings (Расписание встреч), Proposed Characters (Предлагаемые для кодировки символы) и Submitting Proposals (Порядок подачи предложений). Здесь пользователь может ознакомиться с каталогом символов, недавно включенных в схему стандарта Unicode, а также символов-кандидатов на включение. Если пользователь обнаруживает какой-либо пропущенный сим- вол, то он может подать письменное предложение о его включении в систему. В подразделе Submitting Propos- als изложены строгие правила, которых необходимо придерживаться при подаче письменных заявок. Раздел For Members состоит из двух подразделов: Member Resources (Список членов) и Working Documents (Рабочие документы). Доступ к этим подразделам защищен паролями; ссылки доступны только для зарегистри- рованных членов консорциума. П7.6. Использование кодировки Unicode Для представления всех символов в Visual Studio .NET применяется форма кодировки Unicode UTF-16. В лис- тинге П7.1 используется язык C# для отображения текста "Welcome to Unicode!" на восьми языках: английском, французском, немецком, японском, португальском, русском, испанском и упрощенном китайском. Результат работы программы представлен на рис. П7.2.
928 Приложение 7 Примечание__________________________________________________________ На сайте консорциума имеется ссылка на таблицы кодов с 16-битовыми значениями Unicode Листинг П7.1. Значения Unicode для нескольких языков j,® 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 // Листинг П7.1: Unicode.cs // Использование кодировки Unicode using System; using System.Drawing; using System.Collections; using System.ComponentModel; using System.Windows.Forms; using System.Data; public class Unicode : System. Windows. Forms. Form { internal System.Windows.Forms.Label IblChinese; internal System.Windows.Forms.Label IblSpanish; internal System.Windows.Forms.Label IblRussian; internal System.Windows.Forms.Label IblPortuguese; internal System.Windows.Forms.Label IblJapanese; internal System.Windows.Forms.Label IblGerman; internal System.Windows.Forms.Label IblFrench; internal System.Windows.Forms.Label IblEnglish; private System.ComponentModel.Container components = null; // код, сгенерированный Visual Studio .NET // главная точка входа для приложения [STAThread] static void Main() { Application.Run(new Unicode()); } private void Unicode_Load(object sender, System.EventArgs e) ( // английский chart] english = {'\u0057', *\u0065', ’\u006C’, '\u0063', '\u006F’, 'ЧиООбО*, '\u0065', '\u0020', •\u0074', *\uOO6F’, '\u0020'}; IblEnglish.Text = new string(english) + "Unicode" + '\u0021'; // французский ' char[] french = {'\u0042', '\u00691, '\u0065', •\uOO6E', '\u0076’, ЛиООбб', ’\u006E’, ’\u0075', '\u0065', ’\u0020’, '\u0061', '\u0075', '\u0020'}; IblFrench.Text = new string(french) + "Unicode" + ’\u0021’; // немецкий char[] german = {'\u0057', '\u0069', *\u006C*, •\u006B’, ’ХиООбГ’, '\u006D',, ’\u006D', '\u0065', '\u006E', '\u0020', ’\uO67A', '\u0075', '\u0020’}; IblGerman.Text = new string(german) + "Unicode" + '\u0021';
Unicode 929 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 // японский char[] Japanese = (’\u3078', '\u3078', *\u3045', '\u3053’, '\u305D', ’\u0021'}; 1ЫJapanese.Text = "Unicode" + new string(Japanese); // португальский chart] Portuguese = {'\u0053', '\u0065', *\u006A', '\u0061', ’\u0020', '\u0062', '\u0065', '\u006D’, •\u0020', '\u0076', '\u0069', '\u006E', '\u0064', 1\u006F’, '\u0020', '\u0061', '\u0020'}; IblPortuguese.Text = new string(Portuguese) + "Unicode" + '\u0021'; // русский chart] russian = {’\u0414’, '\u043E', '\u0431', '\u0440’, '\u043E', '\u0020', '\uO43F', '\u043E', '\u0436', '\u0430', '\uO43B', '\uO43E', '\u0432', •\u0430', 1 \u0442', '\u044C, '\u0020', ’\u0432', •\u0020'}; IblRussian.Text = new string(russian) + "Unicode" + '\u0021'; // испанский chart] Spanish = {'\u0042', '\u0069', '\u0065', ’\uOO6E’, '\u0076', '\u0065', '\u006E', '\u0069', '\u0064’, '\u006F', '\u0020', '\u0061', ’\u0020' }; IblSpanish.Text = new string(Spanish) + "Unicode" + '\u0021'; // упрощенный китайский char[] Chinese = {'\u6B22', '\u8FCE’, '\u4F7F', '\u7528', '\u0020'}; IblChinese.Text = new string(Chinese) + "Unicode" + '\u0021'; } // окончание метода Unicode_Load } // окончание класса Unicode Welcome to Unicode* Ж £Л Ж Bienvenye au Unicode* 'Ж 3& -5 -S S ’ WilLommen zu Unicode! Seja bemvindo t Unicodi' Добри побаловать в Unicode! 5® ‘ \ & £ В anvemdo a Unicode* . ' Unicode x t . C . f ' 4 - „Ж -: ЯКэЭКЖ Unicode1 to Рис. П7.2. Окно, содержащее приглашение на восьми языках В строках 35—37 содержатся шестнадцатеричные коды для текста на английском языке. На странице Code Charts сайта Консорциума Unicode имеется документ, где приведены значения кодов для блока (или категории) Basic Latin, в который входит английский алфавит. Шестнадцатеричным кодам в строках 35—36 соответствует слово "Welcome". При применении символов Unicode в C# используется формат \uyyyy, где уууу обозначает шестнадцатеричную кодировку Unicode. Например, буква "W” в слове "Welcome" обозначается кодом \u0057. В строке 36 содержится шестнадцатеричное значение для символа пробела (\u0020). Значение Unicode для сло- ва "to" размещено в строке 37. В строках 39—40 из массива символов создается строка и добавляется слово "Unicode". Это слово не кодируется, потому что оно является зарегистрированной торговой маркой, и в боль- шинстве языков не имеет эквивалентного перевода. В строке 40 также содержится представление \u002i для восклицательного знака (!). 59 Зак. 3333
930 Приложение 7 Оставшиеся приветственные сообщения (строки 43—96) содержат значения Unicode для других семи языков. Значения кодов, использованные для текста на французском, немецком, португальском и испанском языках, размещены в блоке Basic Latin, значения кодов для текста на упрощенном китайском языке размещены в блоке CJK Unified Ideographs, значения кодов для текста на русском языке размещены в блоке Cyrillic, а значения кодов для текста на японском языке размещены в блоке Hiragana. Примечание____________________________________________________________________________ Для отображения символов азиатских языков в приложениях Windows может потребоваться установка на ком- пьютер файлов соответствующего языка. Чтобы сделать это в системе Windows 2000, откройте диалоговое окно Regional Options в панели управления (Start | Settings | Control Panel). В нижней части вкладки General пред- ставлен список языков. Выберите японский (Japanese) и классический китайский (Traditional Chinese) языки и нажмите кнопку Apply После этого следуйте инструкциям мастера установки языков. Дополнительная вспомога- тельная информация имеется на сайте www.unicode.org/help/display_problems.html. П7.7. Диапазоны символов Стандарт Unicode присваивает значения кодог в диапазоне от 0000 (Basic Latin) до E007F (Tags) символам ми- ровой письменности. В настоящее время существуют коды для 94 140 символов. Для облегчения поиска симво- ла и относящегося к нему кодового значения в Unicode они обычно группируются по сценарию и функции (т. е. латинские символы сгруппированы в один блок, математические операторы — в другой и т. д.). Как правило, сценарий представляет собой единую систему письменности, используемую для многих языков (например, ла- тинский сценарий используется для английского, французского, испанского и других языков). На странице Code Charts сайта Консорциума Unicode представлены все заданные блоки и соответствующие им значения кодов. В табл. П7.2 приведены некоторые блоки (сценарии) с сайта с их диапазонами значений кодов. Таблица П7.2. Некоторые диапазоны символов Сценарий Диапазон значений кодов Arabic U+0600 — U+06FF Basic Latin и+оооо— U+007F Bengali (Индия) U+0980 — U+09FF Cherokee (аборигены Америки) U+13A0 — U+13FF CJK Unified Ideographs (Восточная Азия) U+4E00 — U+9FAF Cyrillic (Россия и Восточная Европа) U+0400 — U+04FF Ethiopic U+1200 — U+137F Greek U+0370 — U+03FF Hangul Jarno (Корея) U+1100 — U+11FF Hebrew U+0590 — U+05FF Hiragana (Япония) U+3040 — U+309F Khmer (Кампучия) U+1780 — U+17FF Lao (Лаос) U+0E80 — U+OEFF Mongolian U+1800 — U+18AF Myanmar U+1000 — U+109F Ogham (Ирландия) U+1680 — U+169F Runic (Германия и Скандинавия) U+16A0 — U+16FF Sinhala (Шри-Ланка) * U+0D80 — U+ODFF Telugu (Индия) и+осоо— U+0C7F Thai U+OEOO — U+0E7F
Unicode 931 П7.8. Резюме До появления системы кодирования Unicode разработчики программного обеспечения сталкивались с серьез- ными проблемами при использовании несогласованных кодировок символов (например, при использовании числовых значений для обозначения символов). Большинство стран и корпораций имели собственные системы кодирования, несовместимые друг с другом. Без Unicode локализация глобальных программных продуктов тре- бует значительных изменений исходного кода, в результате чего повышаются затраты на разработку и задержи- вается выпуск продукта на рынок. Для решения серьезных проблем, связанных с многочисленными кодировками символов и их использованием, Консорциум Unicode разработал стандарт Unicode, упрощающий производство и распределение локализован- ных программных средств. Данный стандарт представляет спецификацию для последовательного и универсаль- ного кодирования символов, используемых в мировой письменности. Программные продукты, обрабатывающие текст, закодированный в соответствии со стандартом Unicode, также требуют локализации, но этот процесс осуществляется гораздо проще и более эффективно. Стандарт Unicode спроектирован как универсальное, эф- фективное, однородное и однозначное средство кодировки. Универсальная система кодировки охватывает все так или иначе используемые символы; эффективная система кодировки облегчает синтаксический и грамматический анализ текстовых файлов; однородной системе коди- ровки всем символам присваиваются фиксированные значения. Unicode расширяет набор символов ASCII путем кодирования подавляющего большинства всех символов, встречающихся в мировых языках. В системе Unicode существуют три формата преобразования Unicode (Unicode Transformation Format, UTF)— UTF-8, UTF-16 и UTF-32, каждый из которых может использовать в разных контекстах. Данные в формате UTF-8 состоят из 8-битовых байтов (последовательности из одного, двух, трех или четырех байтов, в зависимости от кодируемого символа) и хорошо соответствуют системам, поддерживающим стандарт ASCII, при подавляющем большинстве однобайтовых символов (стандарт ASCII обозначает символы в виде одного байта). UTF-8 — форма кодирова- ния переменной ширины, компактная для текстов, в которых превалируют латинские буквы и пунктуация ASCII. UTF-16 — форма кодирования по умолчанию стандарта Unicode переменной ширины, в которой вместо байтов используются 16-битовые блоки кодирования. Большинство символов обозначаются одной единицей (блоком), однако некоторые символы требуют суррогатных пар. Суррогаты — это 16-битовые целые числа в диапазоне от D800 до DFFF, используемые исключительно для "выхода" к символам с большими цифровыми значениями. Без суррогатных пар форма кодирования UTF-16 может охватить всего 65 000 символов, однако при наличии суррогатных пар число охватываемых символов расширяется до миллиона и больше. UTF-32 — это 32-битовая форма кодирования фиксированной ширины, основным преимуществом которой является то, что все символы в ней выражаются однородно: при этом значительно облегчается обработка массивов и других структур. Символом называется любой письменный компонент, который может быть представлен в виде числового зна- чения. Символы обозначаются глифами, формами, гарнитурами шрифтов и размерами. Значения кодов — это комбинации битов, представляющие кодированные символы. Обозначение кода в Unicode — U+yyy, гДе U+ от- носится к значениям кодов Unicode. В отличие от других шестнадцатеричных значений, уууу представляет че- тырехзначное шестнадцатеричное число. Стандарт Unicode стал системой кодировки по умолчанию для XML и любого производного от него языка, на- пример, XHTML. Для представления символов в интегрированной среде разработки Visual Studio .NET приме- няется кодировка UTF-16. При использовании символов Unicode в C# выбран формат \uyyyy, где уууу обознача- ет шестнадцатеричную кодировку Unicode.
ПРИЛОЖЕНИЕ 8 (В Интеграция СОМ П8.1. Введение С самого начала программные приложения, создаваемые для Windows или DOS, проектировались как единые монолитные исполняемые приложения, т. е. программы целиком компоновались как единые исполняемые фай- лы. По мере усложнения программных средств разработчики столкнулись с трудностями проектирования всех необходимых элементов программных приложений. Более того, с увеличением размеров программ их поставка на рынок после исправления ошибок или с появлением новых версий стала непрактичной. С целью решения этих проблем корпорация Microsoft внедрила совместно используемые библиотеки в систему Windows, что позволило разработчикам многократно использовать код и вносить в него изменения. В Windows совместно используемая или динамически подключаемая библиотека (Dynamic Link Library, DLL) представляет собой файл, содержащий скомпилированный код, загружаемый прил< жжением во время исполнения. Тот факт, что эти библиотеки загружаются во время прогона программы, позволяет разработчикам модифицировать оп- ределенные библиотеки и тестировать результаты без перекомпоновки всего приложения. Одна библиотека мо- жет использоваться одновременно несколькими приложениями, что снижает требования к памяти для запуска таких приложений. Разделение программ на небольшие части также упрощает распространение обновленных версий программ, потому что для поставки предназначены только модифицированные версии DLL. Появление совместно используемых библиотек решило многие проблемы, которые ранее ограничивали мо- дульность и возможность многократного использования кода. Однако библиотеки принесли с собой новые сложности. Разные версии монолитных приложений редко конфликтовали между собой: если производитель устранял ошибку в одной части программы, было маловероятно, что подобное усовершенствование повлияет на другие программы системы. С появлением совместно используемых библиотек обновление или изменение, вне- сенное производителем, могло нарушить нормальную работу программ, использующих старую версию этой библиотеки. Ради обеспечения совместимости разработчики часто сопровождали новые библиотеки "своими" приложениями. Однако пакеты новых DLL могли подменять уже существующие в пользовательских системах библиотеки, что могло отрицательно повлиять на нормальное функционирование установленных программных средств. Обнаруживать и устранять проблемы, вызываемые совместно используемыми библиотеками, было на- столько сложно, что они получили прозвище "проклятия DLL" (DLL hell). В попытках расширения функциональности DLL и устранения связанных с ними проблем корпорация Microsoft разработала Component Object Model (СОМ, модель компонентных объектов). СОМ представляет собой специ- фикацию, которая управляет версиями библиотек, обратной совместимостью и определяет стандарт взаимодей- ствия между библиотеками. Корпорация Microsoft сделала все возможное для подробного и непротиворечивого проектирования спецификации СОМ с тем, чтобы разработчики СОМ могли создавать совместимые библиоте- ки. При этом была крупномасштабно реализована архитектура СОМ; теперь практически все библиотеки Win- dows разрабатываются в соответствии со спецификацией СОМ. При корректной реализации спецификация СОМ предоставляет хорошо структурированные библиотеки с воз- можностью их многократного использования, однако и она имеет определенные ограничения. Например, СОМ сложно программировать и развертывать, потому что разработчики должны гарантировать абсолютную совмес- тимость с предыдущими версиями и корректную регистрацию в системе каждого нового COM-компонента. Ес- ли библиотека СОМ помещается в систему без надлежащей регистрации, тогда компонент будет неправильно представлен в реестре, и программное приложение может просто не обнаружить этой библиотеки. На платформе .NET необходимость в компонентах СОМ отпала. Компоненты .NET от Microsoft сохранили пре- имущества СОМ, решив при этом множество связанных с ней проблем. Компоненты в .NET поддерживают всю информацию идентификации изнутри, т. е. независимость компонента от реестра Windows обеспечивает его
Интеграция COM 933 корректную идентификацию. Многие разработчики и компании сделали значительные капиталовложения в соз- дание компонентов СОМ, но предпочитают пользоваться мощью, организационной структурой и функциональ- ностью платформы .NET. Для облегчения процесса перехода от СОМ к .NET корпорация Microsoft создала на- бор инструментальных средств, предназначенных для переноса существующих компонентов СОМ на платфор- му .NET. П8.2. Интеграция ActiveX В последние годы популярными компонентами СОМ были элементы управления ActiveX. Язык C# дает разра- ботчикам возможность импортировать и использовать элементы управления ActiveX в приложениях Windows Forms. Авторы включили элемент управления ActiveX — Labelscrollbar — в Web-сайт, который будет исполь- зован для демонстрации утилиты интеграции ActiveX. Для использования данного элемента управления сначала нужно зарегистрировать файл с расширением осх в реестре Windows. Для регистрации элемента управления откройте окно Command Prompt и активизируйте ути- литу RegSvr32, размещенную в каталоге c:\winnt\system32. (Данный путь может варьироваться в зависимости от компьютеров и версий системы Windows.) На рис. П8.1 показана успешная регистрация элемента управления LabelScrollbar. Рис. П8.1. Регистрация элемента управления ActiveX: а — запуск утилиты RegSvr32; б — сообщение о регистрации После регистрации элемента управления ActiveX необходимо добавить его в панель инструментов среды разра- ботки Visual Studio. Для этого щелкните правой кнопкой мыши на панели инструментов и выберите команду Customize Toolbox. Откроется одноименное диалоговое окно (рис. П8.2). Здес£ представлен список всех ком- понентов СОМ, зарегистрированных на машине. Выберите осх-файл, отметив флажок Deitel LabelScrollbar, для добавления элемента LabelScrollbar в панель инструментов и нажмите кнопку ОК. При добавлении LabelScrollbar в Web-форму Visual Studio генерирует библиотеки Deitel и AxDeitel и добавляет их в ссылки в окно Solution Explorer. Первая ссылка (Deitel) является "заместителем" общей библиотеки исполнения (Common Runtime Library), обеспечивающим доступ программиста к методам и свойствам компонента из кода .NET. Вторая ссылка (AxDeitel) представляет собой "заместитель" форм Windows, позволяющий программи- сту добавить в форму компонент ActiveX. В данном контексте под "заместителем" (proxy) понимается объект, обеспечивающий взаимодействие кода .NET с кодом СОМ; в следующем разделе взаимодействие .NET и СОМ рассматривается более подробно. Обратите внимание, что как только элемент LabelScrollbar будет добавлен в панель инструментов, в каталоге bin приложения создаются два dli-файла: AxDeitel.Deitel.dll и Interop.Deitel.dll. Рис. П8.2. Диалоговое окно Customize Toolbox с выбранным элементом управления ActiveX
934 Приложение 8 На левом изображении рис. П8.3 показана панель инструментов IDE после добавления элемента LabelScrollbar. На правом изображении представлен список свойств элемента Labelscrollbar, в который входят свойства, оп- ределенные в элементе управления ActiveX (т. е. Min, Max, SmallChange, LargeChange и Value), а также свойства, определенные Visual studio (например, Anchor, Dock И Location). Элемент управления LabelScrollbar Свойства NET Свойства элемента управления Рис. П8.3. Панель инструментов среды разработки и свойства элемента LabelScrollbar Для демонстрации функциональности элемента LabelScrollbar добавим в форму три элемента LabelScrollbar (листинг П8.1). Эти элементы управления разрешают пользователю выбрать значения RGB (красный, зеленый, синий), определяющие цвет объекта PictureBox. ......................................-.................................:.... ; Листинг П8.1. Интеграция элемента управления ActiveX СОМ в C# ..............................................................................................................и.... 1 // Листинг П8.1: LabelScrollbar.cs 2 // Демонстрация использования компонента ActiveX в .NET 3 4 using System; 5 using System.Drawing; 6 using System. Col lections;- 7 using System.ComponentModel; 8 using System.Windows.Forms; 9 using System.AxDeitel; 10 11 namespace FigH_04 12 { 13 // Демонстрация ActiveX-элемента управления LabelScrollBar 14 public class LabelScrollTest : System.Windows.Forms.Form 15 { 16 private System.Windows.Forms.PictureBox pbColorBox; 17 private AxDeitel.AxLabelScrollbar redScrollBar; 18 private AxDeitel.AxLabelScrollbar greenScrollBar; 19 private AxDeitel.AxLabelScrollbar blueScrollBar; 20 21 /// требуется переменная проектировщика 22 private System.ComponentModel.Container components = null; 23 24 public LabeScrolITest() 25 { 26 InitializeComponent(); 27
Интеграция COM 935 28 // настройка свойств LabelScrollbar 29 SetupRGBTitleScrollbar(redScrollbar); 30 SetupRGBTitleScrollbar(blueScrollbar); 31 SetupRGBTitleScrollbar(greenScrollbar); 32 33 // инициализация фонового цвета объекта PictureBox 34 pbColorBox.BackColor = Color.FromArgb( 35 redScrollBar.Value, greenScrollBar.Value, 36 blueScrollBar.Value); 37 38 } // окончание конструктора 39 40 // инициализация свойств LabelScrollBar 41 private void SetupRGBTitleScrollbar( 42 1 AxLabelScrollbar scrollbar) 43 { 44 scrollBar.Min =0; // минимальное значение 45 scrollBar.Max = 255; // максимальное значение 46 scrollBar.LargeChange = 10; // значение большого изменения 47 scrollBar.SmallChange =1; // значение небольшого изменения 48 49 } // окончание метода SetupRGBTitleScrollBar 50 51 // код, сгенерированный Visual Studio .NET 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 } [STAThread] static void Mainf) { Application.Run(new LabelScrollTest()); } // управление событием изменения состояния полосы прокрутки private void scrollbar_Change(object sender, System.EventArgs e) { pbColorBox.BackColor = Color.FromArgb( redScrollBar.Value, greenScrollBar.Value, blueScrollBar.Value); } II окончание метода scrollbar_Change } // окончание класса LabelScrollTest // окончание пространства имен FigH_04 Конструктор вызывает метод SetupRGBTitleScrollbar (строки 41—49), задающий первоначальные значения свойства Min, Max, LargeChange и SmallChange для каждого элемента управления Labelscrollbar. Свойство Мах каждого элемента Labelscrollbar имеет значение 255, что обеспечивает диапазон цветов свыше 16 млн. Конструктор также задает начальный фоновый цвет объекта PictureBox (строки 34—36). В строках 60—66 определяется обработчик события Change элемента Labelscrollbar. При изменении пользователем значения Labelscrollbar запускается событие Change, и фоновый цвет объекта PictureBox изменяется. Результат работы приложения представлен на рис. П8 4. Рис. П8.4. Выбор цвета: а — исходное состояние; б — изменение цвета
936 Приложение 8 П8.3. Интеграция DLL Visual Studio .NET также поддерживает интеграцию библиотек СОМ. Данный процесс схож с интеграцией ком- понентов ActiveX. Для демонстрации интеграции Visual Studio .NET dll-библиотеки на Web-сайте включен файл deitelvb6addition.dll. Данная простая библиотека содержит функцию AdditionFunction, принимающую два ар- гумента, складывающую их и возвращающую результат. Первым шагом в интеграции COM DLL является идентификация DLL в реестре Windows с помощью утилиты RegSvr32 следующим образом: regsvr32 deitelvb6addition.dll После регистрации библиотеки добавьте в программу C# ссылку на нее щелчком правой кнопки мыши на стро- ке References в окне Solution Explorer и выбором команды Add Reference. В диалоговом окне Add Reference (рис. П8.5, а) выберите вкладку СОМ. После этого выберите строку Simple Addition DLL и нажмите кнопку ОК. Откроется диалоговое окно, указывающее, что среда .NET должна генерировать первичную компоновку взаимодействия (primary interop assembly) (рис. П8.5, б). После открытия окна нажмите кнопку Yes. Первичная компоновка взаимодействия содержит информацию для всех методов и классов, находящихся в библиотеке СОМ. Рис. П8.5. Добавление ссылки на библиотеку: а — диалоговое окно Add Reference выбора DLL; б — информационное сообщение В Windows всем компонентам (.NET и СОМ) должна сопутствовать информация идентификации. Данная ин- формация содержит глобально уникальные идентификаторы (Global Unique Identifier, GUID) для компонента и его внутренних классов, а также независимые от языка описания всех классов и типов, определенных в компо- ненте. Такие независимые от языка' описания помогают активизировать многократное использование данного компонента во многих языках программирования. GUID состоит из длинной строки чисел, генерируемой ком- пьютером, исходя из установленного на компьютере текущего времени, аппаратных средств и случайного чис- ла. Алгоритм GUID никогда не генерирует одного идентификатора дважды; следовательно, GUID активизирует уникальную идентификацию компонента на всех компьютерах. После того, как компонент СОМ зарегистрирован, его GUID сохраняется в реестре Windows; затем программы могут пользоваться реестром для определения местоположения и идентификации компонента. После обнаруже- ния программой нужного компонента она учитывает библиотеку типов компонента для обнаружения и исполь- зования объектов и методов библиотеки. В библиотеке типов описываются все интерфейсы, типы и методы компонента СОМ; библиотека типов включена либо в dll-файл компонента, либо в отдельный tlb-файл. Отделе- ние идентификаторов компонента (размещенных в реестре Windows) от представленных на диске данных (файл
Интеграция COM 937 библиотеки) является источников многих проблем, связанных с архитектурой СОМ. В отличие от такого поло- жения дел, компоненты .NET избегают подобных проблем внутренней поддержкой всей идентификационной информации. Когда Visual Studio импортирует компонент СОМ, среда создает файл, в котором содержится вся информация идентификации и описания данных. Среда Visual Studio получает GUID компонента из реестра Windows и преобразует описание данных из формата библиотеки типов в формат компоновки .NET. При такой обработке создается новый dll-файл, называемый первичной компоновкой взаимодействия, который помещается в каталог bin приложений. Первичная компоновка взаимодействия используется средой .NET для обнаружения методов СОМ и для преоб- разования типов данных компонента из типов платформы .NET в типы компонентов СОМ и наоборот. Перевод, для каждого компонента СОМ выполняется программой-упаковщиком, вызываемой во время исполнения (Run- time Callable Wrapper, RCW). RCW — это прокси-объект, созданный исполняющей средой .NET на основе ин- формации, находящейся в первичной компоновке объекта RCW управляет объектом СОМ и осуществляет связь между кодом среды .NET и объектом СОМ. Совет по повышению производительности____________________________________________________ Архитектуры .NET и СОМ кардинально различаются в том, что касается управления памятью и представления объектов. Обращения методов к объектам СОМ могут заметно снизить производительность программы, потому что программа RCW должна преобразовать все типы данных между управляемым контекстом (.NET) и не управ- ляемым контекстом (СОМ). При создании в C# объекта СОМ фактически формируется новый экземпляр RCW объекта. Взаимосвязь между RCW и компонентом СОМ абсолютно прозрачная, что позволяет разработчику .NET взаимодействовать с объ- ектом СОМ так, будто он является объектом .NET. Авторами создано приложение (листинг П8.2) демонстрирующее использование библиотеки Simple Addition DLL, которую необходимо интегрировать в .NET. В данную программу входят три текстовых поля и кнопка. После ввода значений типа int в каждое из первых двух текстовых полей пользователь нажимает кнопку Calculate, и в третьем текстовом поле программа отображает сумму двух значений int. 1 // Листинг П8.2: Addition.cs 2 // Использование компонента СОМ для сложения двух целых чисел 3 4 using System; 5 using System.Drawing; 6 using System.Collections; 7 using System.ComponentModel; 8 using System.Windows.Forms; 9 using System.Data; 10 using Deitel_DLL; 11 12 namespace Addition 13 { 14 // сложение двух целых чисел с помощью компонента СОМ 15 public class Addition : System.Windows.Forms.Form 16 { 17 // отображение меток 18 private System.Windows.Forms.Label SecondLabel; 19 private System.Windows.Forms.Label FirstLabel; 20 21 // текстовые поля целых чисел 22 private System.Windows.Forms.TextBox resultBox; 23 private System.Windows.Forms.TextBox firstlntegerBox; 24 private System.Windows.Forms.TextBox secondlntegerBox; 25 26 // выполнение сложения 27 private Systern.Windows.Forms.Button calculateButton; 28 29 private CaAddition additionobject = new CAdditionO; 30
938 Приложение 8 31 // требуется переменная проектировщика 32 private System.ComponentModel.Container 33 components = null; 34 35 public Addition() 36 {. 37 InitializeComponent(); 38 } 39 40 // код, сгенерированный Visual Studio .NET 41 42 // главная точка входа для приложения 43 [STAThread] 44 static void Main() 45 { 46 Application.Run (new AdditionO); 47 } 48 49 // обработчик события для активизации calculateButton 50 private void integerBox_TextChanged(object sender, 51 System.EventArgs e) 52 { 53 // активизация кнопки Calculate, если оба 54 11 поля содержат текст 55 if (firstlntegerBox.Text != "" && 56 secondlntegerBox.Text != "") 57 calculateButton.Enabled = true; 58 else 59 calculateButton.Enabled = false; 60 } // окончание метода integerBox_TextChanged 61 62 // обработчик события, отображающего сумму при 63 // нажатии кнопки Calculate 64 private void calculateButton_Click(object sender, 65 System.EventArgs e) 66 { 67 int firstInteger, secondlnteger, total; 68 69 - firstlnteger = Int32.Parse(firstlntegerBox.Text); 70 secondlnteger = Int32.Parse(secondlntegerBox.Text); 71 72 // объект сложения активизирует объект AddFunction, 73 // возвращающий целочисленное значение 74 total = additionobject.AddFunction(ref firstlnteger, 75 ref secondlnteger); 76 77 resultBox.Text = total.ToStringO ; 78 } // окончание метода calculateButton_Click 79 ) // окончание класса Addition 80 } // окончание пространства имен Addition Рис. П8.6. Приложение, складывающее два целых числа В строке 29 создается RCW additionobject для компонента COM Deitel DLL.CAddition. В строках 55—59 ак- тивизируется кнопка calculateButton, если оба текстовых поля содержат значения, и деактивизируется, если эти текстовые поля значений не содержат. При нажатии пользователем кнопки Calculate запускается обработ- чик события calculateButton_ciick, получающий содержимое текстовых полей и складывающий значения. Обработчик события вызывает COM-метод addFunction, возвращающий сумму в виде значения int (стро- ки 74—75). После этого результат отображается в txtResuitBox (строка 77). Результат работы программы представлен на рис. П8.6. В данном приложении продемонстрировано использование библиотек СОМ и элементов управления ActiveX из приложения .NET» Кроме того, кратко рассмотрена история модели СОМ и отличия ее архитектуры от архитек-
Интеграция COM 939 туры .NET. После ознакомления с материалом данного приложения читатели должны получить базовое пред- ставление о СОМ и уметь использовать компоненты СОМ в приложениях .NET. Подробности о .NET и СОМ представлены в Web-pecypcax (см. разд. П8.5). П8.4. Резюме С самого начала программные приложения, создаваемые для Windows или DOS, проектировались как единые монолитные исполняемые приложения, т. е. программы целиком компоновались как единые исполняемые фай- лы. По мере усложнения программных средств стало непрактично разрабатывать и распространять все необхо- димые компоненты, потому что на это приходилось затрачивать больше времени и средств. Корпорация Microsoft внедрила в систему Windows динамически подключаемые библиотеки (DLL), что позволило разработ- чикам многократно использовать коды и вносить в них изменения. Совместно используемая или динамически подключаемая библиотека представляет собой файл, содержащий скомпилированный код, загружаемый прило- жением во время исполнения. Тот факт, что эти библиотеки загружаются во время прогона программы, позво- ляет разработчикам вносить изменения в любую библиотеку и тестировать результаты без перекомпоновки все- го приложения. Совместно используемые библиотеки повышают модульность программ обеспечением доступа нескольким приложениям к одной библиотеке кода. Разделение программ на небольшие части также упрощает распространение обновленных версий программ, потому что для поставки предназначены только модифициро- ванные версии DLL. Ради обеспечения совместимости разработчики часто сопровождали новые библиотеки "своими" приложениями. Однако пакеты новых DLL могли подменять уже существующие в пользовательских системах библиотеки, что могло отрицательно повлиять на нормальное функционирование установленных про- граммных средств. Обнаруживать и устранять проблемы, вызываемые совместно используемыми библиотеками, было настолько сложно, что они получили прозвище "проклятия DLL". Для устранения проблемы "проклятия DLL" корпорация Microsoft разработала модель- компонентных объектов (СОМ) — спецификацию, которая управляет версиями библиотек, обратной совместимостью и взаимодействием языков. Корпорация Microsoft сделала все возможное для подробного и непротиворечивого проектирования спецификации СОМ с тем, чтобы разработчики СОМ могли создавать совместимые библиотеки. При этом была крупномасштабно реализована архитектура СОМ; теперь практически все библиотеки Windows разрабатываются в соответствии со спецификацией СОМ При корректной реализации спецификация СОМ предоставляет хорошо структурированные библиотеки с возмож- ностью их многократного использования, однако и она имеет определенные ограничения. Например, СОМ сложно программировать и развертывать, потому что разработчики должны гарантировать абсолютную совмес- тимость с предыдущими версиями и корректную регистрацию в системе каждого нового СОМ-компонента. Компоненты .NET от Microsoft сохранили преимущества СОМ, решив, при этом, множество связанных с ней проблем. Для облегчения процесса перехода от СОМ к .NET корпорация Microsoft создала набор инструмен- тальных средств, предназначенных для переноса существующих компонентов СОМ на платформу .NET. Компоненты Windows содержат глобально уникальные идентификаторы (GUID) для компонента и его внутрен- них классов, а также независимые от языка описания всех классов и типов, определенных в компоненте Такие независимые от языка описания помогают активизировать многократное использование данного компонента с многими языками программирования. GUID состоит из длинной строки чисел, генерируемой компьютером, исходя из установленного на компьютере текущего времени, аппаратных средств и случайного числа. Алгоритм GUID никогда не генерирует одного идентификатора дважды; следовательно, GUID активизирует уникальную идентификацию компонента на всех компьютерах. После регистрации компонента СОМ его GUID сохраняется в реестре Windows, а затем программы могут пользоваться реестром для определения местоположения и иден- тификации компонента. В библиотеке типов описываются все интерфейсы, типы и методы компонента СОМ; библиотека типов включена либо в dll-файл компонента, либо в отдельный tlb-файл. Отделение идентификато- ров компонента (размещенных в реестре Windows) от представленных на диске данных (файл библиотеки) яв- ляется источников многих проблем, связанных с архитектурой СОМ. Система .NET избегает подобных проблем внутренней поддержкой всей идентификационной информации. Пер- вичная компоновка взаимодействия используется средой .NET для обнаружения методов СОМ и для преобразо- вания типов данных компонента из типов платформы .NET в типы компонентов СОМ и наоборот. Перевод для каждого компонента СОМ выполняется программой-упаковщиком, вызываемой во время исполнения (RCW). RCW — это прокси-объект, созданный средой исполнения .NET на основе информации, находящейся в первич- ной компоновке объекта. RCW управляет объектом СОМ и осуществляет связь между кодом среды .NET и объ- ектом СОМ. При создании в C# объекта СОМ фактически формируется новый экземпляр RCW объекта Взаи- мосвязь между RCW и компонентом СОМ абсолютно прозрачная, что позволяет разработчику .NET взаимодей- ствовать с объектом СОМ так, будто он является объектом .NET.
940 Приложение 8 П8.5. Ресурсы Интернета и WWW □ www.microsoft.com/com Web-страница Microsoft о СОМ, на которой представлена техническая документация, "белые книги" и мате- риалы в поддержку разработчиков. Страница является основным ресурсом для разработчиков СОМ. □ www.cs.umd.edu/~pugh/com На сайте на высоком уровне представлен технический обзор архитектуры СОМ. □ msdn.microsoft.com/msdnmag/issues/01/08/Interop/Interop.asp На данном сайте предлагается введение в службы интеграции, имеющиеся в .NET. Вводные примеры и опи- сание функций взаимодействия СОМ и .NET.
ПРИЛОЖЕНИЕ 9 Введение в HTML 4: часть 1 Читать между строк было проще, чем понять собственно текст. Генри Джеймс Чистый цвет, не замутненный смыслом и лишенный какой бы то ни было формы, может общаться с душой тысячью разных способов. Оскар Уайлд Высокие мысли должны выражаться высоким языком. Аристофан Постепенно я поднялся с заднего плана низшего класса до авансцены низшего класса. Марвин Коэн Темы данного приложения: □ основные компоненты документа HTML; □ использование основных элементов HTML для создания Web-страниц; □ добавление графических изображений в Web-страницы; □ создание и использование гиперссылок для прохождения Web-страниц; □ создание списков. П9.1. Введение В данном приложении описываются основы создания Web-страниц на языке HTML. Авторами составлено не- сколько простых Web-страниц. В приложении 10 описываются более сложные методики HTML, такие как таб- лицы, представляющие особую важность для структурирования информации баз данных. В настоящем прило- жении речь о программировании на языке C# не идет. Здесь представлены базовые элементы и атрибуты HTML. Основным моментом при использовании HTML является отделение представления документа (т. е. его отображения на экране браузером) от структуры этого документа. Данный вопрос весьма подробно освещается в приложениях 9 и 10. П9.2. Языки разметки HTML — это язык разметки. Он используется для форматирования текстов и информации. Цель такой "размет- ки" информации отличается от целей традиционных языков программирования — выполнения операций в за- данном порядке. В HTML текст размечается элементами в окружении тегов — ключевых слов, заключенных в угловые скобки. Например, сам по себе элемент HTML, указывающий на то, что создается Web-страница для визуализации браузером, начинается с начального тега <html> и заканчивается конечным тегом </html>. Эти элементы зада- ют особый формат страницы. В данном приложении и приложении 10 представлены другие широко используе- мые теги, а также описание их использования. О хорошем стиле программирования________________________________________________ Теги HTML не зависят от регистра. Однако ввод всех букв в одном регистре значительно повышает читаемость программы. Программист может выбрать любой регистр, но авторы рекомендуют писать код строчными буква- ми: нижний регистр обеспечивает лучшую совместимость с другими языками разметки, разработанными для ввода тегов и элементов только строчными буквами.
942 Приложение 9 Распространенная ошибка программирования______________________________________________ Отсутствие тегов при требующих их элементах является синтаксической ошибкой, которая может серьезно по- влиять на форматирование и внешний вид страницы. В отличие от традиционных языков программирования, синтаксическая ошибка в HTML, как правило, не вызывает сбоя отображения страницы в браузере. П9.3. Редактирование кода HTML В данном приложении рассматривается применение HTML в форме его исходного кода. С помощью текстового редактора будут созданы документы HTML с сохранением их в файлах с расширениями html или htm. В на- стоящее время существует огромное множество текстовых редакторов. Для начала рекомендуется использовать Notepad (Блокнот) в системе Windows. Размещена программа Notepad в панели Accessories (Стандартные) в списке программ меню Start (Пуск). Бесплатный редактор исходного кода HTML под названием HTML-Kit можно загрузить с сайта www.chami.coni/html-kit. Пользователи UNIX могут воспользоваться популярными текстовыми редакторами vi или emacs. Действенный метод программирования _________________________________________________ Файлам рекомендуется присваивать имена, описывающие их (файлов) функции Такая практика помогает быст- рее идентифицировать документы, а также упрощает доступ посетителей к Web-странице, которым легче запо- минать имена нужных файлов. Например, при написании документа HTML, предназначенного для отображения номенклатуры продукции предприятия, файлу можно присвоить имя products html Как отмечалось выше, ошибки в таких языках программирования, как С, C++ и Visual Basic, часто мешают нормальному функционированию программ. Ошибки при разметке HTML, как правило, не столь фатальны. Браузер сделает все от него зависящее для вывода страницы на экран, но отображение может быть не совсем корректным. Файл домашней страницы (первой страницы HTML, которую видит пользователь при входе на сайт) должен иметь имя index.html, потому что, если браузер не запрашивает конкретный файл в каталоге, обычный Web- сервер по умолчанию возвращает имя index.html (на разных серверах разные установки), если такой файл суще- ствует в каталоге. Например, при направлении браузера на сайт www.deitel.com сервер фактически отправляет в браузер файл www/deitel.com/index.html. П9.4. Общие элементы В процессе изложения материалов приложений, посвященных HTML, будут представлены как исходный код HTML, так и образцы изображений экрана при визуализации документов HTML в Internet Explorer. В листин- ге П9.1 приведен код HTML, выводящий одну строку текста, а на рис. П9.1 — результат работы программы. .... — <•» • ““ниш •" | Листинг 119.1. Базовый файл HTML 1 <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN" 2 http://www.w3.org/TR/html4/sctrict.dtd> 3 <html> 4 5 <!— Листинг П9.1: main.html —> 6 <!— Первая Web-страница —> 7 8 <head> 9 <title>C# How to Program - Welcome</title> 10 </head> 11 12 <body> 13 14 <p>Welcome to Our Web Site!</p> 15 16 </body> 17 </html> Строки 1 и 2 <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN” http://www.w3.org/TR/html4/set rict.dtd>
Введение в HTML 4: часть 1 943 необходимы в каждом документе HTML: они используются для обозначения типа документа (document type). В типе документа указывается версия HTML, используемая в документе, а также тип может применяться вместе с инструментом аттестации, например, с validator.w3.org от W3C для обеспечения соответствия документа HTML рекомендациям HTML. В приводимых примерах создаются документы HTML версии 4.01. Документ HTML начинается с открывающего тега <htmi> (строка 3) и заканчивается закрывающим тегом </html> (строка 17). Рис. 119.1. Базовый файл HTML О хорошем стиле программирования__________________________ В начало и конец документа HTML всегда следует вводить теги <html> и </html>. О хорошем стиле программирования__________________________________________________________ В коде рекомендуется размещать комментарии. Комментарии в HTML заключаются в теги <! —... —>. Коммен- тарии помогают другим разработчикам понять код, служат подспорьем при отладке и содержат полезную ин- формацию, которая не отображается браузером. Комментарии также помогают разобраться в коде самому авто- ру, особенно если какое-то время разработчик к нему не обращался. Первые комментарии (т. е. текст, описывающий разметку HTML) представлены в строках 5 и 6: <!— Листинг П9.1: main..html —> <!— Первая Web-страница —> Комментарии в HTML всегда начинаются с тега <! — и заканчиваются тегом —>. Внутри комментария браузер игнорирует любой текст и/или теги. Комментарии вводятся перед началом каждого документа HTML; в них указывается номер листинга, имя файла и краткое описание цели примера. В последующих примерах коммента- рии включаются в разметку, особенно при представлении новых функций. В каждом документе HTML содержится элемент <head>, обычно с информацией о документе, и элемент <body> с содержимым Web-страницы. Инфо; >мация элемента <head> обычно не отображается в окне, но доступ к ней может быть осуществлен другими способами. В строках 8—10 <head> <title>C# How to Program - Welcome</title> </head> показан раздел элемента head Web-страницы. В каждый документ HTML требуется включение элемента title. Для включения выбранного для страницы названия его следует заключить в элементе head между парой тегов <title> И </title>. О хорошем стиле программирования__________________________________________________________ Для всех страниц сайта рекомендуется придерживаться единых правил присвоения названий. Например, если сайт называется "Сайт Эла", тогда лучшим названием страниц ссылок будет "Сайт Эла — ссылки". Таким обра- зом, сайт будет более понятным для всех его посетителей. Элементом title Web-странице присваивается название. Оно обычно отображается в заголовке окна, а также в виде текста, обозначающего страницу, если пользователь добавляет ее в списки Favorites (Избранное) или Bookmarks (Закладки) на своем компьютере. Название страницы также используется механизмами поиска для целей каталожного упорядочивания: выбор названия с определенным смыслом помогает системе поиска напра- вить на сайт более узкую группу потенциальных посетителей.
944 Приложение 9 В строке 12 <body> открывается элемент body. Тело документа HTML — это область, в которой размещается содержимое докумен- та. Сюда входит текст, изображения, ссылки и формы. Далее рассматривается много элементов, которые можно вставить в элемент body. Перед закрывающим тегом </html> необходимо вставлять закрывающий тег </body>. Для размещения текста в документе HTML применяются различные элементы. В строке 14 видим элемент абзаца: <p>Welcome to Our Web SiteL</p> Текст, помещенный между тегами <р> и </р>, образует один абзац. Большинство Web-браузеров интерпретиру- ет абзацы как отделенные от прочего материала на странице линией вертикального пространства перед абзацем и после него. Строка 12 документа HTML "заставляет" Internet Explorer интерпретировать вложенный текст, как показано в листинге П9.1. Код примера заканчивается в строках 16 и 17 тегами </body> </html> Эти два тега закрывают тело документа и его разделы HTML соответственно. Как отмечалось выше, в каждом документе HTML последним должен быть тег </html>, сообщающий браузеру о завершении кодировки HTML. Закрывающий тег </body> помещается перед тегом </html>, потому что раздел тела документа полностью вхо- дит в раздел HTML, следовательно, раздел тела документа должен быть закрыт до раздела HTML. П9.5. Заголовки Для обозначения новых разделов и подразделов Web-страницы используются шесть заголовков. В листин- ге П9.2 показано использование этих элементов (с Ы по h6), а на рис. П9.2 представлен результат. Обратите внимание, что фактический размер текста каждого элемента заголовка выбирается браузером, и в зависимости от обозревателя может значительно варьироваться. О хорошем стиле программирования________________________________________________ Добавление комментариев справа от коротких строк HTML придает больше привлекательности внешнему виду кода. 1 <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN” 2 http://www.w3.org/TR/html4/sctrict.dtd> 3 <html> 4 5 <!— Листинг П9.2: header.html —> 6 <!— Заголовки HTML —> 7 8 <head> 9 <title>C# How to Program - Welcome</title> 10 </head> 11 12 <body> 13 14 <hl>Level 1 Header</hl> <!— Заголовок уровня 1 — 15 <h2>Level 2 header</h2> <!— Заголовок уровня 2 — 16 <h3>Level 3 header</h3> <!— Заголовок уровня 3 — 17 <h4>Level 4 header</h4> <!— Заголовок уровня 4 — 18 <h5>Level 5 header</h5> <!— Заголовок уровня 5 — 19 <h6>Level 6 header</h6> <!— Заголовок уровня 6 — 20 21 </body> 22 </html>
Введение в HTML 4: часть 1 945 Edit , View Favorites Tools *♦ * 12 j’ iftSeerch (It j Favorites ...,..n.....^Ja ....•..f..,„. . ...... C# How to Program - Welcome Microsoft Internet Explore) Level 1 Header Level 2 header Level 3 header Level 4 header Level 5 header Рис. П9.2. Заголовки c hl no h6 В строке 14 <hl>Level 1 Header</hl> представлен элемент заголовка hl с начальным тегом <hl> и конечным тегом </hl>. Любой текст, предназна- ченный для отображения на экране, размешается между этими двумя тегами. Все шесть элементов заголов- ков — с hl по h6 — следуют тому же правилу. О хорошем стиле программирования____________________________-___________________________ Размещение заголовка в начале каждой Web-страницы помогает посетителям понять их предназначение. П9.6. Ссылки Самой важной функцией HTML является возможность создания гиперссылок на другие документы для обеспе- чения перехода на различные документы и информацию в Интернете. В HTML текст и изображения могут вы- ступать в роли якорей для ссылок на другие страницы глобальной сети. Якоря и ссылки представлены в листин- ге П9.3, а результат — на рис. П9.3. Листинг П9.3. Ссылки на доугие Web-страницы «...........«inn.HKin аппппм.ан > nt |< > 11 V«V»' w а». М- »№- СЯМГ •« . ................ -лй--------... .... . Х~£ым£«— 1 <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN" 2 http: //www.w3. org/TR/htm.14 /sctrict. dtd> 3 <html> 4 5 <!— Листинг П9.3: links.html —> 6 <!— Введение в гиперссылки —> 7 8 <head> 9 <title>C# How to Program - Welcome</title> 10 </head> 11 12 <body> 13 14 <hl>Here are my favorite Internet Search Engines</hl> 15 16 <p><strong>Click on the Search Engine address to go to that 17 page.</strong></p> 18 19 <p><a href = "http://www.yahoo.com">Yahoo</ax/p> 20 21 <pxa href = "http://www.alstavista.com">AltaVista</ax/p> 22 60 Зак. 3333
946 Приложение 9 23 <р><а href = "http://www.askjeeves.com">Ask Jeeves</a></p> 24 25 <p><a href = "http://www.webcrawler.com">WebCrawler</a></p> 26 27 </body> 28 </html> Here are my favorite Internet Search Engines Click on the Search Engine address to ко to that page Yahoo AltaVista Ask Jeeves WebCrawler ............................................................... „ ... .. .M ....................— My comber Рис. П9.3. Ссылки на другие Web-страницы Первая ссылка имеется в строке 19 <р><а href = http://www.yahoo.com>Yahoo</a></p> Ссылки вставляются с помощью элемента а (якорь). Якорь отличается от всех элементов, рассматривавшихся до сих пор, тем, что он требует определенных атрибутов (т. е. разметки, предоставляющей информацию об элементе) для задания гиперссылки. Атрибуты помещаются после начального тега элемента и состоят из имени и значения. Самым важным атрибутом для элемента а является местоположение, с которым необходимо'связать якорный объект. Таким местоположением может быть любой ресурс в сети, включая страницы, файлы и адреса электронной почты. Для указания адреса, на который необходимо поставить ссылку, добавьте к якорному эле- менту атрибут href следующим образом: <а href = ”адрес” > В данном случае связываемый адрес — http: //www.yahoo.com. Гиперссылка (строка 19) превращает текст Yahoo в ссылку на адрес, указанный в href. Для создания ссылок на адреса электронной почты якоря могут использовать URL mailto. При выборе пользо- вателем такой ссылки в большинстве браузеров запускается программа электронной почты по умолчанию для создания электронного сообщения на связанный адрес. Такой тип якоря представлен в листинге П9.4, а резуль- тат — на рис. П9.4. 1 <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN" 2 http://www.w3.org/TR/html4/sctrict.dtd> 3 <html> 4 5 <!— Листинг П9.4: contact.html 6 <!— Добавление гиперссьшок электронного адреса 7 8 <head> 9 <title>C# How to Program - Welcome</title> 10 </head> 11 12 <body> 13
Введение в HTML 4: часть 1 947 14 <р>Му email address is <а href = "mailto:deitel@deitel.com"> 15 deitel@deitel.com</a>. Click on the address and your browser 16 will open an email message and address it to me.</p> 17 18 </body> 19 </hnml> C# How to Program - Weh ome MteroWt ln»emer iwpltxer * M f'a¥Oritos Tpak..,;He|( .... \DesJ<top\Cijrrert C# Pro)ect\Examptes\Aop_I HTMLllFrglJMUcntact.hbnij^l lit*. * 3 My email address is deitel@deitel. com. Click on the address and your browser will open an email message and address it to me. Рис. П9.4. Ссылка на электронный адрес Ссылка на электронный адрес представлена в строках 14 и 15: <р>Му email address is <а href = "mailto:deitel@deitel.com"> deitel@deitel.com</a>. Click on the address and your browser Форма якоря электронной почты — <а href = "mailto: адрес_электронной_почть^>___</а>. Важно, чтобы весь этот атрибут, включая компонент mailto:, был заключен в кавычки. П9.7. Изображения До сих пор мы обсуждали вывод в браузере исключительно текста. Теперь рассмотрим процесс размещения в Web-странице изображений (листинг П9.5, рис. П9.5). Листин! П9 5. Размещение изображений в файлах HTML (предоставлено лздатепытсом Prentice Hall, Inc.) 0.1 !.... Ю™.; 1 i...... . ___......_. .л.... .л. .. ... 1 <! DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4..01//EN" 2 http://www.w3.org/TR/html4/sctrict.dtd> 3 <html> 4 5 <!— Листинг П9.5: picture.html —> 6 <!— Добавление изображений в файл HTML —> 7 8 <head> 9 <title>C# How to Program - Welcome</title> 10 </head> 11 12 <body> 13 14 <p><img sre ="csphtp.jpg" height = "236" width = "181" 15 alt = "Demonstration of the alt attribute"></p> 16 17 </body> 18 </html> Изображение в данном примере кода вставлено в строках 14 и 15: <pximg sre ="csphtp.jpg" height = "236" width = "181" alt = "Demonstration of the alt attribute"x/p> Местоположение файла изображения указано в элементе img. Это осуществляется добавлением атрибута sre = "местоположение". Также можно задать высоту (height) и ширину (width) изображения, измеряемые в пикселах. Термин "пиксел" произошел от английского словосочетания "picture element" — "элемент картинки". Каждый
948 Приложение 9 пиксел представляет одну цветовую точку на экране. В нашем случае изображение имеет ширину 181 пиксел и высоту 236 пикселов. Рис. П9.5. Пример размещения изображений в файлах HTML О хорошем стиле программирования______________________________________________________ Высоту height и ширину width изображения всегда следует задавать внутри тега <img>. При загрузке браузе- ром файла HTML программе сразу становится известно, какую часть экрана займет изображение, и она отобра- зит его корректно даже до загрузки. О хорошем стиле программирования______________________________________________________ Ввод новых размеров, при которых изменяется соотношение ширины к высоте, может исказить внешний вид изображения. Например, если изображение имеет ширину 200 пикселов, а высоту — 100 пикселов, то при указа- нии новых значений всегда следует соблюдать соотношение ширины и высоты 2:1. Атрибут alt необходим для каждого элемента img. В листинге П9.5 значение этого атрибута alt = "Demonstration of the alt attribute" Атрибут alt предназначен для браузеров с отключенной опцией просмотра изображений или не имеющих ее вообще (т. е. для текстовых браузеров). Значение атрибута alt появится на экране на месте изображения, и пользователь сможет понять, что изображено. Данный атрибут особо важен для обеспечения доступности Web- страниц для людей с ограниченными физическими возможностями (см. главу 21). О хорошем стиле программирования___________________________________________ С помощью атрибута alt в теге <img> всегда рекомендуется включать описание каждого изображения. Теперь, после описания процесса вставки изображений в Web-страницы, продемонстрируем преобразование изображений в якоря для создания ссылок на другие сайты Интернета (листинг П9.6, рис. П9.6). Листинг П9.6. Использование изображении в качестве ссылок ? эгй 1 <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN" 2 http://www.w3.org/TR/html4/sctrict.dtd> 3 <html> 4 5 <!— Листинг П9.6: nav.html —> 6 <!— Использование изображений в качестве ссылок —> 7 8 <head> 9 <title>C# How to Program - Welcome</title> 10 </head> 11 12 <body> 13
Введение в HTML 4: часть 1 949 14 <р> 15 <а href = "link.html"> 16 <img src = "buttons/links.jpg" width = "65" height = "50" 17 alt = "Links Page"x/a><br> 18 19 <a href = "list.html"> 20 <img src = "buttons/list.jpg" width = "65" height = "50" 21 alt = "List Example Page"x/axbr> 22 23 <a href = "contact.html"> 24 <img src = "buttons/contact.jpg" width = "65" height = "50" 25 alt = "Contact Page"x/a><br> 26 27 <a href = "header.html"> 28 <img src = "buttons/header.jpg" width = "65" height = "50" 29 alt = "Header Page”x/axbr> 30 31 <a href = "table.html"> 32 <img src = "buttons/table.jpg" width = ”65" height = "50" 33 alt = "Table Page"x/axbr> 34 35 <a href = "form.html"> 36 <img src = "buttons/form.jpg" width = ”65" height = "50" 37 alt = "Feedback Form"x/a><br> 38 </p> 39 40 </body> 41 </html> Рис. П9.6. Использование изображений в качестве ссылок Гиперссылка на изображение указана в строках 15—17 <а href = "link.html"> <img src = "buttons/links.jpg" width = "65" height = "50" alt = "Links Page"x/axbr> Здесь используется элемент а и элемент img. Якорь работает так же, как при окружении текста; изображение превращается в активную гиперссылку на любой ресурс в Интернете, указанный атрибутом href внутри тега <а>. Следует помнить о необходимости закрытия элемента при завершении гиперссылки.
950 Приложение 9 Если обратить внимание на атрибут src элемента img src = "buttons/links.jpg" то можно заметить, что он имеет иную форму, по сравнению с атрибутом изображения из предыдущего приме- ра. Причиной этого является то, что используемое в данном случае изображение links.jpg размещено в подката- логе buttons основного каталога сайта. Это сделано для того, чтобы вся графика кнопок находилась в одном месте для облегчения поиска и редактирования. К файлам в разных каталогах всегда можно обратиться простым вводом имени каталога в корректном формате в атрибут src. Если, например, внутри каталога buttons имеется каталог images, и графику из этого каталога не- обходимо поместить на страницу, необходимо, чтобы исходный атрибут отображал местоположение изображе- ния: src="buttons/images/filename". Переносить изображения можно даже из других сайтов (разумеется, после получения разрешения от владельца сайта). Необходимо сделать так, чтобы атрибут src отражал местоположение и имя файла изображения. В строке 17 alt = "Links Page"x/a><br> представлен элемент br, обозначающий разрыв строки, визуализируемый большинством браузеров. П9.8. Специальные символы и разделительные линии В HTML раскладки QWERTY обычной пишущей машинки уже недостаточно для удовлетворения всех потреб- ностей при наборе разных текстов. В HTML версии 4.01 имеется функция вставки специальных символов (лис- тинг П9.7, рис. П9.7). SV-Ч..............................----------------------------------------------------------------------------------------------------------------------------------------------------------------- Г Листинг П9.7 Вставка специальных символов в документ HTML л Г..‘н. - . ............. •..'.i.Wt.i..... ... ..«•» ...............................................................Л............. ..... И............^....ЛЛ.........!... 1 2 3 4 5 б 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 26 27 28 29 30 31 32 <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN" http://www.w3.org/TR/html4/sctrict.dtd> <html> <!— Листинг П9.7: contact.html —> <!— Вставка специальных символов —> <head> <title>C# How to Program - Welcome</title> </head> <body> <!— Специальные символы вводятся с помощью формы &code; —> <р>Му email address is <а href = "mailto:deitel@deitel.com"> deitel@deitel.com</a>. Click on the address and your browser will open an email message and address it to my address.</p> <hr> <!— Вставка горизонтальной линии —> <p>All information on this site is <strong>&copy;</strong> Deitel <strong>&amp;</strong> Associates, 2002.</p> <!— Текст можно зачеркнуть с помощью тегов <del>—</del>, —> <!— его можно вставить в нижний индекс с помощью тегов —> <!— <sub>...</sub> или вставить в верхний индекс —> <!— с помощью тегов <sup>...</sup> —> <p><del>You may copy up to 3.14 x 10<sup>2</sup> characters worth of information from this site.</del> Just make sure you <sub>do not copy more intormation</sub> than is allowable. </p>
Введение в HTML 4: часть 1 951 33 <p>No permission is needed if you only need to use <strong> 34 &lt; &fracl4;</strong> of the information presented here.</p> 35 36 </body> 37 </html> Рис. П9.7. Специальные символы в документе HTML В строках 22 и 23 в текст вставлены некоторые специальные символы'. <р>А11 information on this site is <st.rong>&copy; </strong> Deitel <strong>&amp;</strong> Associates, 2002.</p> Все специальные символы вставляются в форме кодов. Формат кода всегда— &code;. Например, &атр; — вставка символа &. Коды часто представляют собой сокращенные формы символов (подобно тому, как amp обо- значает символ &, а сору — символ авторского права), а также могут выражаться в форме шестнадцатеричных кодов. (Например, шестнадцатеричный код для символа & —38, поэтому другой способ вставки этого симво- ла— ввод &#38;.) В приложении 13 приведен список специальных символов с соответствующими им кодами. В строках 28—31 представлены три новых стиля. <p><del>You may copy up to 3.14 x 10<sup>2</sup> characters r worth of information from this site.</del> Just make ?ure you <sub>do not copy more information</sub> than is allowable. </p> Можно вывести зачеркнутый в документе текст включением в него элемента del. Это можно применять как простой способ указания исправлений в интерактивном документе. Многие браузеры интерпретируют элемент del как перечеркнутый текст. Для превращения текста в верхний индекс (т. е. его поднятия вертикально на стро- ке и уменьшения размера шрифта) или в нижний индекс (опускание текста в строке ниже и уменьшения размера шрифта) воспользуйтесь элементом sup или sub соответственно. В строке 20 <hr> <!— Вставка горизонтальной линии —> вставляется горизонтальная черта, обозначенная тегом <hr>. Горизонтальная линия визуализируется браузерами как прямая линия, проходящая горизонтально по экрану. С помощью элемента hr сразу под ней добавляется разрыв строки. П9.9. Маркированные списки Листинг П9.8 позволяет вывести текст в виде маркированного списка. Здесь еще раз используется файл HTML из листинга П9.3 с добавлением маркированного списка для расширения структуры страницы. С помощью эле- мента ui создается список, каждая строка которого начинается в большинстве браузеров с маркера абзаца. Результат представлен на рис. П9.8.
952 Приложение 9 1 <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN" 2 http://www.w3.org/TR/html4/sctrict.dtd> 3 <html> 4 5 <!— Листинг П9.8: links.html —> 6 <!— Маркированный список, содержащий гиперссылки —> 7 8 <head> 9 <title>C# How to Program - Welcome</title> 10 </head> 11 12 <body> 13 14 <hl>Here are my favorite Internet Search Engines</hl> 15 16 17 <p><strong>Click on the Search Engine address to go to that 18 page. </strongx/p> 19 20 <ul> 21 <li> 22 <a href = "http//www.yahoo.com">Yahoo</a> 23 </li> 24 25 <li> 26 <a href = "http//www.altavista.com">AltaVista</a> 27 </li> 28 29 <li> 30 <a href = "http//www.askjeeves.com">Ask Jeeves</a> 31 </li> 32 33 <li> 34 <a href = "http//www.webcrawler.com">WebCrawler</a> 35 </li> 36 </ul> 37 38 </body> 39 </html> Рис. П9.8. Маркированный список в HTML Первый элемент списка появляется в строках 21—23 <li> <а href - "http//www.yahoo.com">Yahoo</a> </li>
Введение в HTML 4: часть 1 I 953 Каждая запись в маркированном списке — элемент li (list item). В большинстве браузеров эти элементы визуа- лизируются разрывом строки и маркером абзаца в начале строки. П9.10. Вложенные и нумерованные списки В листинге П9.9 продемонстрированы вложенные списки (т. е. один список в другом). Результат представлен на рис. П9.9. ---------......-----------------------------------------------------------------------------------------------------------. ^Листинг П9.9. Вложенные и нумерованные списки в t: * ... .................... . ч.«.. ...г.’...... ............ ........'.;...;л;л;.;.........^.........Л..'.«^.^;^...пп......'...........»..»;^.1.——- 1 <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN" 2 http://www.w3.org/TR/html4/sctrict.dtd> 3 <html> 4 5 <!— Листинг П9.9: list.html —> 6 <!— Расширенные списки: вложенные и нумерованные —> 7 8 <head> 9 <title>C# How to Program - Welcome</title> 10 </head> 11 12 <body> 13 14 <hl>The Best Features of the Internet</hl> 15 16 <ul> 17 <li>You can meet new people from countries around 18 the world.</li> 19 <li>You have access to new media as it becomes public: 20 21 <!— Начало сложенного списка, в котором —> 22 <!— используется модифицированный маркер. —> 23 <!— Список заканчивается закрытием тега <ul>. —> 24 <ul> 25 <li>New games</li> 26 • <li>New Applications 27 28 <!— Другой вложенный список. —> 29 <ul> 30 <li>For business</li> 31 <li>For pleasure</li> 32 </ul> <!— Окончание двойного вложенного списка. —> 33 </li> 34 35 . , <li>Around the clock news</li> 36 - <li>Search engines</li> 37 <li>Shopping</li> 38 <li>Programming 39 40 <ul> 41 <li>C#</li> 42 <li>Java</li> 43 <li>HTML</li> 44 <li>Scripts</li> 45 <li>New languages</li> 46 </ul> 47 48 </li> 49 50 </ul> <!— Окончание вложенного списка первого уровня. —> 51 </li> 52
954 Приложение 9 53 <li>Links</li> 54 <li>Keeping in touch with old friends</li> 55 <li>It is the technology of the future!</li> 56 57 </ul> <!— Окончание первичного маркированного списка. —> 58 59 <hl>My 3 Favorite <em>CEOs</em></hl> 60 61 <!— Нумерованные списки строятся так же, —> 62 <!— как и маркированные, за исключением начального тега <о1>. —> 63 <о1> 64 <li>Lawrence J. Ellison</li> 65 <li>Steve Jobs</li> 66 <li>Michael Dell</li> 67 </ol> 68 69 </body> 70 </html> Рис. П9.9. Вложенные и нумерованные списки в HTML Первый вложенный список начинается в строке 24; его первый элемент находится в строке 25. <ul> <li>New games</li> Вложенный список создается так же, как список в листинге П9.8, за исключением того, что вложенный список сам содержится в элементе списка. В большинстве Web-браузеров вложенные списки визуализируются отсту- пом списка на один уровень и изменением маркеров абзаца. О хорошем стиле программирования_______________________________________ Отступы вложенных списков на один уровень облегчают процесс редактирования и отладки кода.
Введение в HTML 4: часть 1 955 В листинге П9.9 в строках 16—57 представлен список с тремя уровнями вложения. При вложении списков убе- дитесь, что закрывающие теги </ul> размещены в нужных местах. В строках 63—67 <о1> <li>Lawrence J. Ellison</li> <li>Steve Jobs</li> <li>Michael Dell</li> </ol> определяется элемент нумерованного списка тегами <ol>.. ,</о1>. В большинстве браузеров вложенные списки визуализируются с помощью последовательности цифр перед каждым элементом списка, вместо маркеров. По умолчанию во вложенных списках используются последовательности десятичных чисел (1,2, 3,...). П9.11. Резюме HTML не является процедурным языком программирования, как С, Fortran, Cobol или Pascal. Это язык размет- ки, идентифицирующий элементы страницы так, чтобы программа-браузер могла визуализировать страницу на экране. .Язык HTML используется для форматирования текста и информации. Такая "разметка” информации отличается от целей традиционных языков программирования — выполнения операций в заданном порядке. В HTML текст размечается тегами — ключевыми словами, заключенными в угловые скобки. Документы HTML создаются в текстовых редакторах. Все документы HTML сохраняются в файлах с расширениями html или htm. Для большинства Web-серверов имя файла домашней страницы должно быть index.html. При запросе браузером каталога Web-сервер пр умолчанию возвращает файл index.html, если он существует в данном каталоге. В типе документа указывается применяемая версия HTML, а также тип может использоваться вместе с инстру- ментом проверки, например, с validator.w3.org от W3C для обеспечения соответствия документа HTML реко- мендациям HTML. Тег <html> сообщает браузеру, что вся информация, содержащаяся между начальным тегом <htmi> и конечным тегом </htmi>, является HTML-кодом. Каждый файл HTML имеет два раздела: заголовок и тело. Включение заголовка обязательно для каждого документа HTML. Для этой цели служат теги <title>...</title>, которые помещаются внутри заголовка. Тег <body> открывает тело документа. Это об- ласть, в которой размещается вся отображаемая браузером информация. Заголовки представляют собой простое форматирование текста, при котором объем текста увеличивается, исходя из "уровня" заголовка (от hl до Ьб). Заголовки часто используются для выделения новых разделов и подразделов Web-страницы. Самой важной функцией HTML является возможность создания гиперссылок на другие документы с обеспече- нием всемирной сети ссылок на различные документы и информацию в Интернете. Ссылки вставляются с по- мощью элемента а (якорь). Для указания электронного адреса, на который необходимо сделать ссылку, добавь- те в якорный элемент атрибут href с адресом в качестве значения этого атрибута. Якоря могут связываться с электронными адресами. При выборе пользователем якорной ссылки такого типа в большинстве браузеров за- пускается программа электронной почты, заданная по умолчанию, для создания электронного сообщения на указанный адрес. Местоположение файла изображения задается добавлением атрибута src = "местоположение" в теге <img>. Можно задать высоту (height) и ширину (width) изображения, измеряемые в пикселах. Атрибут alt предназна- чен для браузеров с отключенной опцией просмотра изображений или не имеющих ее вообще (т. е. для тексто- вых браузеров). Значение атрибута alt появится на экране на месте изображения, и пользователь сможет по- нять, что изображено. В HTML версии 4.01 имеется функция вставки специальных символов. Все специальные символы вставляются в форме кодов. Формат кода всегда — &code;. Примером является &ашр; — вставка символа &. Коды часто пред- ставляют собой сокращенные формы символов (подобно тому, как amp обозначает символ &, а сору — символ авторского), а также могут выражаться в форме шестнадцатеричных кодов. (Например, шестнадцатеричный код для символа & — 38, поэтому другой способ вставки этого символа — ввод значения &#38;.) Элемент del поме- чает текст как зачеркнутый, и многие браузеры интерпретируют элемент del как перечеркнутый текст. Для пре- вращения текста в верхний индекс или в нижний индекс воспользуйтесь элементом sup или sub соответственно. В большинстве браузеров в начале каждого элемента в маркированного списке размещается маркер абзаца. Все записи маркированного списка должны размещаться между тегами <ul>.. .</ul>, открывающими и закрываю- щими элемент неупорядоченного списка. Каждая запись маркированного списка вложена в элемент li. После этого в него можно вставить и отформатировать любой текст. Вложенным называется список, содержащийся в элементе li. В большинстве Web-браузеров вложенные списки визуализируются с отступом на один уровень, и маркер абзаца изменен для выделения вложения. Нумерованный список (<о1>... </о1>) визуализируется с по-
956 Приложение 9 следовательностью цифр, а не маркером перед каждым его (списка) элементом. По умолчанию во вложенных списках используются последовательности десятичных чисел (1, 2, 3,...). ПЭЛ 2. Ресурсы Интернета и WWW В Интернете имеется много ресурсов, в которых рассмотренные темы описываются более подробно. Для полу- чения дополнительной информации обратитесь к следующим сайтам. □ www.w3.org Консорциум World Wide Web (W3C)— рабочая группа, занимающаяся разработкой рекомендаций по ис- пользованию HTML. На данном сайте содержится различная информация об HTML — с момента создания до сегодняшнего состояния. П www.w3.org/TR/html401 В Спецификации HTML 4.01 описаны все нюансы и тонкости HTML версии 4.01. □ www.w3schools.com/html Школа HTML. На сайте содержится полное руководство по работе с HTML, начиная с введения в WWW и заканчивая самыми совершенными функциями HTML. Хороший ресурс с описаниями функций HTML. □ www2.utep.edu/~kross/tutorial Сайт университета Техаса в Эль Пасо содержит рекомендации и инструкции по простым методам програм- мирования на HTML. Информация сайта полезна для начинающих программистов благодаря наличию обу- чающих примеров. □ www.w3scripts.com/html Вариант сайта "Школа HTML" — сборник примеров кода с демонстрацией всех функций HTML от началь- ного до профессионального уровня.
ПРИЛОЖЕНИЕ 10 Введение в HTML 4: часть 2 И со скрижали памяти своей Сотру я все ничтожные любовные посланья. Уильям Шекспир Темы данного приложения: □ создание таблиц со строками и столбцами данных; □ управление отображением и форматированием таблиц, □ создание и использование форм; □ создание и использование карт изображений для вставки гиперссылок; □ обеспечение доступа Web-страниц для поисковых систем; □ использование элемента frameset для создания развернутых Web-страниц. П10.1. Введение В приложении 9 обсуждались некоторые базовые функции HTML. Было создано несколько полноценных Web- страниц с текстом, гиперссылками, изображениями и такими инструментами форматирования, как горизонталь- ные линии и разрывы строк. В данном приложении рассматриваются более сложные элементы и функции HTML: представление информа- ции в форме таблиц, использование форм для сбора информации от посетителей сайта, использование внутрен- них ссылок и карт изображения для повышения доступности Web-страниц. Авторы также рассматривают про- цесс использования фреймов для упрощения доступа к Web-страницам. В конце приложения читатели познако- мятся с наиболее часто применяемыми тегами и функциями HTML, методологией создания более сложных Web-страниц. Программирование на C# в приложении не рассматривается. П10.2. Базовые таблицы HTML Таблицы HTML версии 4.0 используются для разметки табличных данных, например, информации баз данных. В таблице, представленной в листинге П10.1, данные упорядочиваются по строкам и столбцам. Результат представлен на рис. П10.1. Листинг П10.1. Таблица HTML .....:: мма . . .. . ... 1 <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN" 2 "http://www.w3.org/TR/html4/strict.dtd"> 3 <html> 4 5 <!— Листинг П10.1: table.html —> f 6 <!— Проектирование простой таблицы —> 7 8 <head> 9 <title>C# How to Program — Tables</title> 10 </head> 11
958 Приложение 10 12 <body> 13 14 <hl>Table Example Page</hl> 15 16 <!— Элемент <table> открывает новую таблицу и позволяет —> 17 <!— ввести опции и инструкции проектирования —> 18 <table border = "1" width = "40%"> 19 20 <!— Использование тега <caption> для общего представления —> 21 <!— содержимого таблицы (для людей с нарушениями зрения) —> 22 <caption>Here is a small sample table.</caption> 23 24 <!— Элемент <thead> — первый горизонтальный раздел —> 25 <!—(без прокрутки). Элемент <th> вставляет ячейку —> 26 <!— заголовка и отображает полужирный текст —> 27 <thead> 28 <tr><th>This is the head.</thx/tr> 29 </thead> 30 31 <!— Все содержимое входит в тег <tbody>. Данный элемент —> 32 <!— используется для форматирования всего раздела. —> 33 <!— Тег <td> вставляет ячейку данных с обычным текстом —> 34 <tbody> 35 <tr><td>This is the body .</tdx/tr> 36 </tbody> 37 38 </table> 39 </body> 40 </html> Рис. П10.1. Таблица HTML Все теги и текст таблицы входят в элемент <table>, начинающийся в строке 18 <table border = "1” width = "40%"> Атрибут border позволяет задать ширину рамок таблицы в пикселах. Если необходимо, чтобы рамки были не- видимыми, можно присвоить значение 0: border="0". В нашем случае значение атрибута границ равно 1. Атри- бутом width задается ширина таблицы либо в пикселах, либо в процентах от ширины экрана. В строке 22 <caption>Here is a small sample table.</caption> в таблицу вводится элемент caption. В большинстве браузеров с возможностью отображения графики текст внутри элемента caption вводится непосредственно над таблицей. Текст заголовка также используется для ин- терпретации табличных данных текстовыми браузерами (не поддерживающими графические изображения). Таблицы можно разделять по горизонтали и вертикали. Первая из них — область заголовка — вводится в стро- ках 27—29: <thead> <trxth>This is the head.</thx/tr> </thead>
Введение в HTML 4: часть 2 Поместите всю информацию заголовка (например, заголовки таблицы и столбцов) в элемент thead. Элемент tr (строка таблицы) служит для создания рядов ячеек таблицы. Все ячейки строки принадлежат тегу <tr> этой строки. Наименьшим компонентом таблицы является ячейка данных. Существуют два типа ячеек данных: элемент th (размещен в заголовке таблицы) и элемент td (размещен в теле таблицы). В примере кода, приведенного в лис- тинге П 10.1, ячейка заголовка вводится с помощью элемента th. Ячейки заголовка, размещаемые в элементе <thead>, также подходят для заголовков столбцов. Вторая часть — элемент tbody — вводится в строках 34—36: <tbody> <tr><td>This is the body.</tdx/tr> </tbody> Подобно элементу thead, элемент tbody применяется для форматирования и для целей группирования. Несмот- ря на то, что в вышеприведенном примере в таблице имеется только одна строка и одна ячейка (строка 35), в большинстве таблиц элемент tbody используется для группирования большей части содержимого при наличии в таблице множества строк и ячеек. Замечание о "впечатлениях и ощущениях"__________________________________________________ Для разметки табличных данных на страницах HTML рекомендуется пользоваться таблицами. Распространенная ошибка программирования________________________________________________ Отсутствие закрывающих тегов элементов внутри элемента table является ошибкой которая может исказить формат таблицы. Убедитесь, что все элементы имеют начальные и конечные теги, а также размещены в нужных местах для поддержания плановой структуры таблицы. П10.3. Сложные таблицы HTML и их форматирование В предыдущем разделе и в примере кода рассматривалась структура простой таблицы. В листинге П10.2 дан- ный пример расширяется большим количеством структурных элементов и атрибутов. Результат представлен на рис. П10.2. -и-v——в— ‘T-в-Г-ж -----yj ---------------- - ———* 4R Листинг ГМ 0.2. Сложная таблица HTML МНИМ? .ч . .v-.. «Л. . от .«rnnwiii < .... .I ;»->>?«».ЛиЛй. I -'•••’ w Rfr - м • - 1 <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN" 2 http://www.w3.org/TR/html4/strict.dtd> 3 <html> * 4 5 <!— Листинг П10.2: table.html —> 6 <!— Сложная таблица 7 8 <head> 9 <title>C# How to Program — Tables</title> 10 </head> 11 12 <body> 13 14 <hl>Table Example Page</hl> 15 16 <table border = "1"> 17 <caption>Here is a more complex sample table.<caption> 18 19 <!— Теги <colgroup> и <col> служат для одновременного —> 20 <!— форматирования всех столбцов. Элемент span определяет —> 21 <!— количество столбцов, на которые влияет тег <со1>. —> 22 <colgroup> 23 <col align = "right"> 24 <col span = "4"> 25 <colgroup> 26
960 Приложение 10 21 <thead> 28 29 <!— Интервалы строк и столбцов объединяют указанное —> 30 <!— количество ячеек по вертикали и горизонтали. —> 31 <tr> 32 <th rowspan = "2"> 33 <img src = "camel.gif" width = "205" 34 height = "167" alt = "Picture of a camel”> 35 </th> 36 <th colspan = "4" valign = "top"> 37 <hl>Camelid comparison</hl><br> 38 <p>Approximate as of 8/99</p> 39 </th> 40 </th> 41 42 <tr valign = "bottom"> 43 <th># Humps</th> 44. <th>Indigenous region</th> 45 <th>Spits?</th> 4 6 <th>Produces Wool?</th> 47 </tr> 48 49 </thead> 50 51 <tbody> 52 53 <tr> 54 <th>Camels (bactrian)</th> 55 <td>2</td> 56 <td>Africa/Asia</td> 57 <td rowspan = ”2">Llama</td> 58 <td rowspan *= "2">Llama</td> 59 </tr> 60 61 <tr> 62 <th>Llamas</th> 63 <td>l</td> 64 <td>Andes Mountains</td> 65 </tr> 66 67 </tbody> 68 69 </table> 70 71 </body> 72 </html> Таблица начинается в строке 16. Элемент colgroup, используемый для группирования столбцов, показан в стро- ках 22—25: <colgroup> <col align = "right"> <col span = "4"> <colgroup> Элемент colgroup можно использовать для группирования и форматирования столбцов. Каждый элемент col между тегами <colgroup>.. .</colgroup> может отформатировать любое количество столбцов (указанное в ат- рибуте span). Любое форматирование для применения к столбцу или группе столбцов можно задать как в тегах <colgroup>, так и в тегах <со1>. В данном случае текст выравнивается по правому краю в крайнем левом столб- це. Другим полезным атрибутом является width, с помощью которого задается ширина столбца. В большинстве визуальных браузеров ячейки данных автоматически форматируются так, что в них помещаются 'все необходимые данные. Однако размер некоторых ячеек можно увеличить. Этот процесс осуществляется с помощью атрибу гов rowspan и colspan, которые можно разместить внутри любого элемента ячейки данных. Значение атрибута указывает количество строк или столбцов, которые займет ячейка. Например, значение
Введение в HTML 4: часть 2 961 rowspan="2" "сообщает" браузеру, что эта ячейка данных займет две ячейки, смежные по вертикали. Эти ячейки будут объединены по вертикали (т. е. растянутся на две строки). Пример атрибута coispan появляется в стро- ке 36: <th coispan = "4" valign = "top"> где ячейка заголовка расширена и занимает четыре ячейки. Здесь также присутствует пример выравнивания по вертикали. Атрибут valign принимает следующие значения: "top", "middle", "bottom" и "baseline". Текст во всех ячейках в строке, атрибут valign которых имеет значение "baseline", будет расположен на обычной базовой строке. Значение вертикального выравнивания во всех ячей- ках данных и заголовка valign = "middle" В оставшейся части кода листинга П10.2 продемонстрированы другие варианты использования атрибутов table и описанных выше элементов. Рис. П10.2. Сложная таблица HTML Распространенная ошибка программирования___________________________________________ При использовании атрибутов coispan и rowspan в ячейках данных таблицы следует учитывать, что изменен- ные ячейки захватят пространство других ячеек. Этого можно избежать, если сократить количество ячеек в стро- ке или столбце. Если этого не сделать, то форматирование таблицы собьется, и пользователь рискует создать по ошибке больше столбцов и/или строк, чем необходимо. П10.4. Базовые формы HTML Язык HTML предоставляет несколько механизмов сбора информации от посетителей сайта; одним из них явля- ется форма (листинг П10.3, рис. П10.3). 1 <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN" http://www.w3.org/TR/html4/strict.dtd> 3 <html> 4 5 <!— Листинг П10.3: form.html —> 6 <!—Пример 1 проектирования формы —> 7 8 <head> 9 <title>C# How to Program — Tables</title> 10 </head> 11 61 Зак. 3333
962 Приложение 10 12 <body> 13 14 <hl>Feedback Form </hl> 15 16 <p>Please fill out this form to help us improve our site.</p> 17 18 <!— Этот тег открывает форму, передает метод отправки данных —> 19 <!— и местоположение сценариев формы. Скрытые входные данные —> 20 <!— передают на сервер невизуальную информацию —> 21 <form method = "post" action = "/cgi-bin/formmail"> 22 23 <p> 24 <input type = "hidden" name = "recipient" 25 value = "deitel@deitel.com"> 26 27 <input type = "hidden" name = "subject" 28 value = "Feedback Form"> 29 30 <input type = "hidden" name = "redirect" 31 value = "main.html"> 32 </p> 33 34 <!— Элемент <input type = "text"> вставляет текстовое окно —> 35 <pxlabel>Name: 36 <input name = "name" type = "text" size = "25"> 37 </labelx/p> * 38 39 <p> 40 <!— Типы входных данных "submit" и "reset" вставляют кнопки —> 41 <!— для предоставления или удаления содержимого формы —> 42 <input type = "submit" value * "Submit Your Entries"> 43 <input type = "reset" value = "Clear Your Entries"> 44 </p>' 45 46 </form> 47 48 </body> 49 </html> Рис. П10.3. Простая форма co скрытыми полями и полем ввода Описание формы начинается в строке 21 <form method = "post" action = "/cgi-bin/formmail"> с элемента form. Атрибут method указывает способ, которым собранная в форме информация будет отправлена на Web-сервер для обработки. В форме, вызывающей изменения данных сервера, например, при обновлении базы данных, используйте method « "post". Данные форм будут отправлены на серверы в качестве переменных среды, доступ к которым могут получить сценарии. Другое возможное значение — method = "get" — следует
Введение в HTML 4: часть 2 963 использовать, когда форма не обуславливает изменений серверных данных, например, при запросе базы дан- ных: Данные формы из method = "get" добавляются в конец URL (например, /cgi-bin/formmail?name= bob&order=5). Также, следует помнить, что атрибут method = "get" ограничен стандартными символами и не поддерживает никаких специальных символов. Web-cepeep— это компьютер с программным пакетом Microsoft PWS (Personal Web Server), Microsoft IIS (Internet Information Server) или Apache. Web-серверы обрабатывают запросы с браузеров. При запросе страни- цы или файла в любом месте сервера последний обрабатывает этот запрос и возвращает ответ в браузер. В приведенном примере данные формы поступают в сценарий CGI (Common Gateway Interface, общий шлюзо- вый интерфейс), являющийся средством взаимодействия страницы HTML со сценарием (т. е. программой), на- писанным на Perl, С, Тс1 или на любом другом языке. Сценарий обрабатывает полученные данные и, как прави- ло, возвращает пользователю какую-либо информацию. Атрибут action в теге form— это URL для сценария; в рассматриваемом случае это простой сценарий, отправляющий данные формы на электронный адрес. Боль- шинство провайдеров интернет-услуг имеют на своих сайтах подобный сценарий, так что пользователь всегда может выяснить у системного администратора необходимые настройки HTML для корректного использования этого сценария. Именно для данного сценария имеется несколько отрывков информации (скрытых от пользователя), необходи- мых для формы. Данная информация указана в строках 24—31 <input type = "hidden" name = "recipient" value = "deitel@deitel.com"> <input type = "hidden" name = "subject" value.= "Feedback Form"> , <input type = "hidden" name = "redirect" value = "main, html "> с помощью скрытых элементов ввода. Тег <input> всегда присутствует в формах и требует наличия атрибута type. Два других атрибута предоставляют: name — уникальный идентификатор для элемента input и значение value, указывающее значение, которое элемент input отправляет при передаче данных на сервер. Как видно из кода, скрытые элементы ввода всегда имеют атрибут type = "hidden". Три показанных скрытых элемента ввода типичны для сценария CGI такого типа: электронный адрес, на который будут отправлены дан- ные, строка темы электронного сообщения и URL, на который перенаправляется пользователь после получения формы. О хорошем стиле программирования___________________________________________________________ Скрытые элементы input рекомендуется размещать в начале формы, сразу после начального тега <form>. Это облегчает поиск и идентификацию этих элементов. Использование элемента input определено значением его атрибута type. Еще одна из этих опций представлена в строках 35—37: <p><label>Name: <input name = "name" type = "text" size = "25"> </labelx/p> Входной элемент type = "text" вставляет в форму однострочное текстовое поле (строка 36). Текстовый эле- мент ввода полезно использовать для имен или другой информации, умещающейся на одной строке. Элемент label в строках 35—37 предоставляет описание элемента input в строке 36. Также используется атрибут size элемента input для задания ширины вводимого текста, измеряемой в симво- лах. С помощью атрибута maxlength можно задать максимальное количество символов, которого будут придер- живаться входные текстовые' данные. О хорошем стиле программирования___________________________________________________________ При использовании элементов input в формах рекомендуется предусматривать больше пустого пространства с атрибутом maxlength для ввода пользователями необходимой информации. О хорошем стиле программирования___________________________________________________________ - Отсутствие элемента label для каждого элемента формы является ошибкой проектирования. Без этих ярлыков пользователи не смогут определить функцию того или иного элемента формы.
964 Приложение 10 В строках 42 и 43 имеются два типа элементов input: <input type = "submit" value = "Submit Your Entries"> <input type = "reset" value = "Clear Your Entries"> которые необходимо вводить в каждую форму. Атрибут type = "submit" дает пользователю возможность пере- дачи введенных в форму данных на сервер для обработки. В большинстве визуальных Web-серверов в форму помещается кнопка, при нажатии которой осуществляется передача данных. Атрибут value изменяет отобра- женный на кнопке текст (значение по умолчанию— "submit"). Атрибут type = "reset" предоставляет возмож- ность задания всем элементам формы значений по умолчанию; при этом пользователь может исправить ошибки или начать все заново. Как и в случае с входным элементом submit, атрибут value элемента влияет на отобра- жаемый на экране текст кнопки, но не влияет на ее функциональность. Распространенная ошибка программирования_______________________________________________ Код формы должен обязательно закрываться тегом </form>. Его отсутствие является ошибкой, которая может отрицательно повлиять на функциональность других форм на этой же странице. П10.5. Более сложные формы HTML Дополнительные опции ввода элементов в форму представлены в листинге П10.4 (рис. П10.4). «------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ Листинг П10.4. Форма, включающая текстовые области, поля ввода паролей и флажки Х44........4...'* ..w '• '.ni.i'n. 4..4..«. ’ 4444.......4..<.4..44^4.4*...4.....Л4...•'*.<..•*.4......4444...4.В....4..4. ..~4... .4..4.Г4.4.4.444.....44.............44.444444....4....•.•.r.444.4...4...........4......i>.......•...44444.44...^4.4и 1 <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN" 2 http://www.w3.org/TR/html4/strict.dtd> 3 <html> 4 5 <!— Листинг П10.4: form.html —> 6 <!— Пример 2 проектирования формы —> 7 8 <head> 9 <title>C# How to Program — Tables</title> 10 </head> 11 12 <body> 13 14 <hl>Feedback Form</hl> 15 16 <p>Please fill out this form to help us improve our site.</p> 17 18 <form method = "post" action = "/cgi-bin/formmail"> 19 20 <p> 21 <input type = "hidden" name = "recipient" 22 value = "deitel@deitel.com"> 23 24 <input type = "hidden" name = "subject" 25 value = "Feedback Form"> 26 27 <input type = "hidden" name = "redirect" 28 value = "main, html "> 29 </p> 30 31 <pxlabel>Name: 32 cinput name = "name" type = "text" sxze = "25"> 33 </labelx/p> 34 35 <!— Ter <textarea> создает текстовое поле заданного размера —> 36 <pxlabel>Comments: 37 ctextarea name = "comments" rows = "4" cols = "36"> 38 </textarea> 39 </labelx/p> 40
Введение в HTML 4: часть 2 965 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 <!— Тег cinput type = "password"» отображает текстовое поле, —> <!— данные в котором будут отображаться в виде *** —> <p><label>Email Address: <input name = "email" type = "password" size = "25"> </labelx/p> <P> <strong»Things you liked:</strongxbr> <label»Site design cinput name = "thingsliked" type « "checkbox" value = "Design"»c/label> clabel»Links cinput name = "thingsliked" type = "checkbox" value = "Links"»c/label> clabel»Ease of use cinput name = "thingsliked" type = "checkbox" value = "Ease"»c/label> clabel»Images cinput name = "thingsliked" type = "checkbox" value = "Images"»c/label> clabel»Source code cinput name = "thingsliked" type = "checkbox" value = "Code">c/label» c/p> <P> cinput type = "submit" value = "Submit Your Entries"» cinput type = "reset" value = "Clear Your Entries"» c/p> * c/form> c/body> c/html> Рис. П10.4. Форма, включающая текстовые области, поля ввода паролей и флажки
966 Приложение 10 В строках 37 и 38 <textarea name = "comments" rows = "4" cols = "36"> </textarea> задается элемент textarea. С его помощью в форму вставляется текстовое поле. Размер поля определяется ат- рибутом rows, задающим количество строк, которое появится в текстовой области С помощью атрибута cols указывается ширина элемента textarea. В нашем случае элемент textarea состоит из четырех строк по высоте и 36 символов по ширине. Любой текст по умолчанию, который необходимо разместить внутри textarea, дол- жен входить в этот элемент. Входные данные type = "password" (строка 44) <input name = "email" type = "password" size = "25"> вставляют текстовое поле заданного размера. В поле ввода пароля для пользователей имеется возможность вво- да конфиденциальной информации (нежелательной для просмотра другими пользователями). В браузерах с отображением графики данные, вводимые в поле пароля, видны в виде *. Однако настоящее значение отправля- ется на сервер. В браузерах без возможности отображения графики информация данного поля отображается по- разному. В строках 50—68 вводится другой тип элемента формы — флажки. Каждый элемент input с type = "checkbox” создает в форме новый флажок. Флажки можно использовать по одному или группами. Все позиции в группе должны иметь одинаковое имя name (в данном случае — name = "thingsliked"). Этим сценарий, управляющий формой, уведомляется, что все позиции взаимосвязаны. Распространенная ошибка программирования_________________________________________________ Когда в форме имеется несколько позиций с одинаковым именем name, необходимо убедиться в том, что они имеют разные значения values, иначе сценарий не сможет их различать. Дополнительные элементы формы представлены в листинге П 10.5 (рис. П10.5). В данном примере формы де- монстрируются два новых типа опций ввода. Первый — переключатель — задается в строках 80—97. Опреде- ленные в форме с атрибутом ввода type = "radio" переключатели имеют функции и применение, сходные с флажками. Отличие переключателей от флажков заключается лишь в том, что за один раз можно выбрать толь- ко один элемент. Все атрибуты name в группе переключателей должны быть одинаковыми, а атрибуты value — разными. Вставьте атрибут checked для указания того, какой переключатель должен быть выбран первым. Ат- рибут checked также применим к флажкам. Распространенная ошибка программирования_________________________________________________ Если при использовании в форме группы переключателей не указан одинаковый атрибут name, тогда пользова- тель будет одновременно выбирать все переключатели. = Листинг П10.5. Форма, содержащая переключатели и раскрывающийся список L. ....#.......... -----------------------------------------------------------------------------------;...;.^.........А......—...........J 1 <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN" 2 http://www.w3.org/TR/html4/strict.dtd> 3 <html> 5 <!•*- Листинг П10.5: form.html —> 6 <!— Пример 3 проектирования формы —> 7 8 <head> 9 <title>C# How to Program — Tables</title> 10 </head> 11 12 <body> 13 14 <hl>Feedback Form</hl> 15 16 <p>Please fill out this form to help us inprove our site.</p> 17 18 <form method = "post” action = "/cgi-bin/forrnmail'^ 19
Введение в HTML 4: часть 2 967 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 .64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 <Р> cinput type = "hidden" name = "recipient" value = "deitel@deitel.com"> cinput type = "hidden" name = "subject" value = "Feedback Form"» cinput type = "hidden” name = "redirect" value = "main.html"» c/p> cp>clabel>Name: cinput name = "name" type = "text" size = "25"» c/label>c/p> 4 cp>clabel>Coinments: ctextarea name = "comments" rows = "4" cols = "36"» c/textarea> c/label»c/p> cp>dabel>Email Address: cinput name = "email" type = "password" size «* "25"» c/label>c/p> <p> Cstrong>Things you liked:c/strong>cbr> clabel>Site design cinput name = "thingsliked" type = "checkbox" value = "Design"» c/label> clabel»Links cinput name = "thingsliked" type = "checkbox" value = "Links"» c/label> clabel»Ease of use cinput name = "thingsliked" type = "checkbox" value = "Ease"» c/label> clabel»Images cinput name = "thingsliked" type = "checkbox" value = "Images”> c/label> clabel»Source code cinput name = "thingsliked" type - "checkbox" value = "Code"» c/label> c/p> c!— Ter cinput type = "radio"» создает один переключатель —> с!— Переключатели и флажки отличаются тем, —> с!— что выбрать можно один переключатель в группе —> ср» - <strong»How did you get to our site?:c/strong»cbr> clabel»Search engine cinput name = "how get to site" type = "radio" value - "search engine" checked»c/label>
968 Приложение 10 83 <label>Links from another site 84 <input name = "how get to site" type = "radio" 85 value = "link"x/label> 86 87 <label>Deitel.com Web site 88 <input name = "how get to site” type = "radio" 89 value = "deitel. com"x/label> 90 91 <label>Reference in a book 92 <input name = "how get to site" type = "radio" 93 value = "book"x/label> 94 95 <label>Other 96 cinput name = "how get to site" type = "radio" 97 value = "other"x/label> 98 99 </p> 100 101 <!— Теги <select> определяют раскрывающийся список. —> 102 <!— Выбор указывается тегами <options> —> 103 <р> 104 <label>Rate our site: 105 106 <select name = "rating"> 107 <option selected>Amazing:-)</option> 108 <option>10</option> 109 <option>9</option> 110 <option>8</option> 111 <option>7</option> 112 <option>6</option> 113 <option>5</option> 114 <option>4</option> 115 <option>3</option> 116 <option>2</option> 117 <option>l</option> 118 <option>The Pits:-(</option> 119 </select> 120 121 </label> 122 </p> 123 124 <p> 125 <input type = "submit" value = "Submit Your Entries"> 126 <input type = "reset" value = "Clear Your Entries"> 127 </p> 128 129 </form> 130 131 </body> 132 </html> Последний представляемый здесь тип ввода в форму— элемент select (строки 106—119). С его помощью в форме размещается список элементов с возможностью выбора. <select name = "rating"> <option selected>Amazing:-)</option> <opt i on> 10< / opt ion> <option>9</option> <option>8</option> <option>7</option> <option>6</option> <option>5</option> <option>4</option> <option>3</option> <option>2</option>
Введение в HTML 4: часть 2 969 <option>l</option> <option>The Pits:-(</option> </select> Данный тип ввода в форму создан посредством элемента select. После открывающего тега <select> необходи- мо вставить атрибут паше. Для добавления пункта в список укажите в теге <seiect> элемент option, содержащий текст, который должен быть отображен. Подобно атрибуту checked для переключателей и флажков, атрибут selected применяет к спи- ску выбор по умолчанию. Jji # F >4 to Prog» t i - Ta les Microsoft Internet Explorer File Eck Vbw Favorites Tools Heb •- -» J) _g] gj ' ф Search jgFavprttes Address |^y\Desktop\Current C*Projed\Exa»TiplestApp 3 HTML2\HgJ 05tforrn.htiTil-j C><5o Feedback Form Name [ Please fill out this form to Ьф us improve our site. Comments: Email Address: J Things you liked: Site design Г Links Г Ease of use Г* Images Г Source code P How did yon get to our site? Search engine Links from another site C Deitel.com Web site Reference in a book Other r Rate our site: [Amazing:-) rj Sybrm Your Entries , | > Ctey Yow Entries j ©Done Рис. П10.5. Форма, содержащая переключатели и раскрывающийся список Предыдущий код сгенерирует раскрывающийся список опций в большинстве визуальных браузеров, как пока- зано в листинге П10.5. С помощью атрибута size элемента select можно изменить количество опций списка, отображающихся за один раз. Этим атрибутом можно воспользоваться для отображения расширенной версии списка, вместо однострочной. П10.6. Внутренние ссылки В приложении 9 рассматривалось связывание одной Web-страницы с другой с помощью якорей текста и изо- бражений. В листинге П10.6 описываются внутренние ссылки, обеспечивающие создание именных якорей для создания гиперссылок на те или иные части документа HTML. Результат представлен на рис. П 10.6 и П10.7. 1 C.'DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN" 2 http://www.w3.org/TR/html4/strict.dtd> 3 <html> 4 5 <!— Листинг П10.6: links.html —> 6 <!— Внутренние 'сылки —> 7 8 <head> 9 <title>C# How to Program — Tables</title> 10 </head>
970 Приложение 10 11 12 13 *14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 <body> <!— Элемент <а name = ”.."></а> создает внутренние гиперссылки —> <Р> <а name = "features"></а> </р> <hl>The Best Features of the Intemet</hl> <!— Адрес внутренней ссылки — "xx.html#linkname" —> <P> <a href « "#ceos">Go to <em>Favorite CE0s</emx/a> </p> <ul> <li>You can meet people from countries around the world. </li> <li>You have access to new media as it becomes public: <ul> <li>New games</li> z <li>New applications <ul> <li>For Business</li> <li>For Pleasure</li> </ul> </li> <li>Around the Clock news</li> <li>Search Engines</li> <li>Shipping</li> <li>Programming <ul> <li>HTML</li> <li>Java</li> <li>Dynamic HTML</li> <li>Scripts</li> <li>Nerw languages</li> </ul> </li> </ul> </li> <li>Links</li> <li>Keeping In touch with old friends</li> <li>It is the technology of the future!</li> </ul> <p><a name = "ceos"x/a></p> , <hl>My 3 Favorite <em>CEOs</emX/hl> <P> <a href = "#features">Go to <em>Favorite Features</emx/a> </P> . z
Введение в HTML 4: часть 2 971 14 75 76 77 78 79 80 81 <ol> <li>Lawrence J. Ellison</li> <li>Steve Jobs</li> <li>Michael Dell</li> </ol> </body> </html> Рис. П10.6. Использование внутренних гиперссылок — верхняя часть Web-страницы Рис. П10.7. Использование внутренних гиперссылок — нижняя часть Web-страницы В строках 15—17 <Р> <а name = "features"></а> </р> представлен якорь для внутренней гиперссылки. Именной якорь создается посредством элемента а с атрибутом name. В строке 15 создается якорь с именем features. Из-за того что имя страницы— list.html, URL данного якоря В Web-странице — list .html#features.
972 Приложение 10 В строке 71 <а href = "#features">Go to <em>Favorite Features</em></a> представлена гиперссылка с якорем features в качестве цели. При выборе данной гиперссылки в браузере окно обозревателя перейдет к якорю features (строка 16). Также можно выполнить связку с другой страницей с по- мощью URL местоположения (формата href = "page.html#name”). Замечание о впечатлениях и ощущениях________________________________________________________ Внутренние гиперссылки особенно полезны в длинных файлах HTML с большим количеством информации. Ради экономии времени пользователя можно создавать ссылки на различные точки и объекты страницы. П10.7. Создание и использование карт изображений Уже отмечалось, что изображения можно использовать в качестве ссылок на другие места одного сайта или на другие сайты в Интернете. В данном разделе рассматривается создание карт изображений (листинг П 10.7, рис. П10.8), позволяющих размечать различные части изображения как "горячие точки", которые впоследствии будут использоваться в качестве ссылок. Все элементы карты изображения содержатся в рамках тегов <шар>... </тар>. Необходимым атрибутом для элемента тар является name (строка 18): <map name = "picture"> Как будет видно далее, данный атрибут необходим для ссылок. "Горячая точка" изображения обозначена эле- ментом area. Каждый элемент area имеет следующие атрибуты: href задает цель связки в точке, shape и cords определяют характеристики области, alt задает всплывающую подсказку, как и в элементе img. 1 <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN" 2 http://www.w3.org/TR/html4/strict.dtd> 3 <html> 5 <!— Листинг П10.7: picture.html —> 6 <!— Создание и использование карты изображений —> 7 8 <head> 9 <title>C# How to Program — Tables</title> 10 </head> 11 12 <body> 13 14 <p> 15 16 <!— Ter <map> открывает и присваивает имя области форматирования —> 17 <!— карты изображения, на которую будет сделана ссылка —> 18 <map name = "picture"> 19 20 <!— "shape = rect" обозначает прямоугольную область —> 21 <!— с координатами верхнего левого —> 22 <!— и нижнего правого углов —> 23 <area href = "form.html" shape = "rect" 24 coords = "3, 122, 73, 143" 25 alt = "Go to the feedback form"> 26 27 <area href = "contact.html" shape = "rect" 28 coords = "109, 123, 199, 142" 29 alt = "Go to the contact page"> 30 31 <area href = "main.html" shape = "rect" 32 coords = "1, 2, 72, 17" 33 , alt = "Go to the homepage"> 34
Введение в HTML 4: часть 2 973 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 <area href = "links.html" shape = "rect” coords = "155, 0, 199, 18" alt = "Go to the links page"> <!— "shape = polygon" указывает область —> <!— настраиваемой формы с координатами каждой —> <!— показанной вершины --> <area href = "mailto:deitel@deitel.com" shape = "poly" coords = "28, 22, 24, 68, 46, 114, 84, 111, 99, 56, 86, 13" alt = "Email the Deitels"> <!— "shape = circle" указывает область окружности —> <!— с заданными центром и радиусом --> <area href = "mailto:deitel@deitel.com" shape = "circle" coords = "146, 66, 42" alt = "Email the Deitels"> </map> 52 <!— Ter <img src=... usemap = "#name"> сообщает, что 53 <!— с изображением будет использована указанная карта - 54 <img src = "deitel.gif" width = "200" height = ”144" 55 alt = "Harvey and Paul Deitel" usemap = "#picture">' 56 </p> 57 58 </body> 59 </html> Рис. П10.8. Карта изображения Разметка в строках 23—25 <area href = "form.html" shape = "rect" coords = "3, 122, 73, 143" alt = "Go to the feedback form"> размещает "горячую точку" в виде прямоугольника вокруг координат, перечисленных в атрибуте cords. Точка задается парой координат, являющихся местоположением точки на осях х и у. Ось х проходит горизонтально из верхнего левого угла, ось у — вертикально. Каждая точка на изображении имеет уникальную координату по осям х и у. В случае с прямоугольной горячей областью необходимыми являются координаты левого верхнего и правого нижнего углов прямоугольника. В данном случае верхний левый угол прямоугольника расположен в значении 3 по осих и 122 — по осиу. Правый нижний угол прямоугольника находится в координате (73, 143). Другая область карты размечена в строках 42—44: <area href = "mailto:deitel@deitel.com" shape = "poly" coords = "28, 22, 24, 68, 46, 114, 84, 111, 99, 56, 86, 13" alt = "Email the Deitels"> В данном случае используется значение poly для атрибута shape. При этом в форме многоугольника создается "горячая точка" с помощью координат атрибута coords. Эти координаты обозначают каждую вершину или угол многоугольника. Браузер автоматически связывает эти точки с линиями для формирования области "горячей точки".
974 Приложение 10 shape ="circle" — последний атрибут формы, обычно используемый в картах изображений. С его помощью создается "горячая точка" в виде окружности; данный атрибут нуждается в координате центра окружности и ее радиусе, заданном в пикселах. Для использования карты изображения с элементом img необходимо вставить в тег <img> атрибут usemap = "#name", где name — значение атрибута name в элементе шар. В строках 54 и 55 <img sre = "deitel.gif" width = "200" height * "144" alt = "Harvey and Paul Deitel" usemap = "#picture"> демонстрируется применение карты изображения name - "picture" к элементу img. П10.8. Тег <meta> Поисковые системы используются для поиска интересующих сайтов в Интернете. В таких системах сайты обычно располагаются по ссылкам с одной страницы на другую с сохранением информации идентификации и классификации для каждой страницы, на которой побывал пользователь. Основным элементом HTML, исполь- зуемым поисковой системой для каталогизации страниц, является тег <meta> (листинг П10.8). Тег <meta> содержит два атрибута, которые используются всегда. Первый— name— идентифицирует тип включаемого тега <meta>. Атрибут content обеспечивает информацию о сайте для упорядочения поиска в сис- теме. .... ..... ... ? Листинг П10.8. Использование тега <meta> дляi предоставления ключевых слов и описания • '.J .....J 1 <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN" 2 http://www.w3.org/TR/html4/strict.dtd> 3 <html> 4 5 <!— Листинг П10.8: main.html —> 6 <!— Теги <meta> и <!doctype> —> 7 8 <head> 9 <!— Теги <meta> предоставляют необходимую информацию —> 10 <!— поисковым системам для каталогизации сайта —> 11 <meta name = "keywords" content = "Webpage, design, HTML, 12 tutorial, personal, help, index, form, contact, feedback, 13 list, links, frame, deitel"> 14 15 cneta name = "description" content = "This Web site will help 16 you learn the basics of HTML and Webpage design through the 17 use of interactive examples and instruction."> 18 19 <title>C# How to Program — Tables</title> 20 </head> 21 22 <body> 23 24 <hl>Welcome to Our Web Site!</hl> 25 26 <p> 27 We have designed this site to teach about the wonders of 28 <em>HTML</em>. We have been using <em>HTML</em> since 29 version <strong>2.0</strong>, and we enjoy the features 30 that have been added recently. It seems only a short 31 time ago that we read our first <em>HTML</em> book. 32 Soon you will know about many of the great new 33 features of HTML 4.01. 34 </p> 35 36 <p>Have Fun With the Site!</p> 37 38 </body> 39 </html>
Введение в HTML 4: часть 2 975 В строках 11—13 продемонстрирован тег <meta>. <meta name = "keywords" content = "Webpage, design, HTML, tutorial, personal, help, index, form, contact, feedback, list, links, frame, deitel"> Атрибут content тега meta c name = "keywords" предоставляет поисковые системы co списком слов, описы- вающих ключевые аспекты сайта. Эти слова используются для соответствия поиску: если посетитель сайта пы- тается найти какой-либо термин в теге <meta> с именем keywords, тогда он имеет больше шансов быть уведом- ленным о сайте в выходных данных поисковой системы. Таким образом, включение в теги <meta> информации с помощью атрибута content привлечет больше внимания к сайту. Значение атрибута description (строки 15—17) <meta name = "description" content - "This Web site will help you learn rhe basics of HTML and Webpage design through the use of interactive examples and instruction."> практически сходно co значением элемента keyword. Вместо предоставления списка слов, описывающих стра- ницу, содержимое атрибута content элемента meta должно представлять собой описание сайта, состоящее из 3—4 предложений. Такое описание также используется поисковыми системами для упорядочения информации сайта и ее отображения. Замечание по технологии программирования___________________________________________________ Элементы meta невидимы для пользователей сайта и должны размещаться внутри раздела заголовка докумен- та HTML. П10.9. Тег <frameset> До сих пор все созданные Web-страницы имели возможность ссылаться на другие страницы, но отображать каждый раз только одну страницу. В листинге П 10.9 представлены фреймы, обеспечивающие одновременное отображение нескольких файлов HTML (рис. 10.9). При правильном использовании фреймов повышается удо- бочитаемость и доступность сайтов для пользователей. " к- : Листинг П10.9. Web-страница с использованием двух фреймов: навигации и содержимого 1 <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN" 2 "http://www.w3.org/TR/html4/frameset.dtd"> 3 <html> 4 5 <!— Листинг П10.9: index.html —> 6 <!— Фреймы HTML. Пример 1 —> 7 8 <head> 9 <meta name = "keywords" content = "Webpage, design, HTML, 10 tutorial, personal, help, index, form, contact, feedback, 11 list, links, frame, deitel"> 12 13 <meta name = "description" content = "This Web site will help 14 you learn the basics of HTML and Webpage design through the 15 use of interactive examples and instruction."> 16 17 <title>C# How to Program — Tables</title> 18 </head> I , 19 20 <!— Ter <frameset> задает размеры фрейма —> 21 <frameset cols = ”110,*"> 22 23 <!— Отдельные элементы фрейма указывают —> 24 <!— страницы для отображения в заданных фреймах —> 25 <frame name = "nav" src = "nav.html"> 26 <frame name = "main" src = "main.html"> 27
976 Приложение 10 28 <noframes> 29 <р> 30 This page uses frames, but your browser 31 does not support them. 32 </p> 33 34 <p> 35 Please, <a href = "nav.html">follow this link to 36 browse our site without frames</a>. 37 </p> 38 </noframes> 39 40 </frameset> 41 </html> Рис. П10.9. Web-страница с использованием двух фреймов: навигации и содержимого В строках 1 и 2 <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN" "http://www.w3.org/TR/html4/frameset.dtd" появляется новый тип документа, указывающий на то, что в данном документе HTML используются фреймы. Этот тип документа следует указывать всякий раз при работе с фреймами. Страница с фреймами начинается с открывающего тега <frameset> в строке 21 <frameset cols = "110,*"> Данный тег "сообщает" браузеру, что страница содержит фреймы. Атрибут cols открывающего тега <frameset> представляет схему набора фреймов. Значение атрибута cols (или rows, если фреймы будут написаны с гори- зонтальной разметкой) определяет ширину каждого фрейма — в пикселах или в процентном отношении от ве- личины экрана. В данном случае атрибут cols = "110" сообщает браузеру, что фреймов — два. Первый отстоит на 110 пикселов от левого края экрана, а второй заполняет оставшуюся его часть (указано символом *). Теперь, когда макет страницы определен, необходимо указать файлы, которые образуют набор фреймов. Это выполняется с помощью тегов <f rame> в строках 25 и 26: <frame name = "nav" src = "nav.html"> <frame name = "main" src = "main.html"> В каждом элементе frame атрибут src передает URL страницы, которая будет отображена во фрейме. В преды- дущем примере в первом фрейме (занимающем НО пикселов левой части frameset) отобразится страница
Введение в HTML 4: часть 2 977 nav.html; этот фрейм имеет атрибут name = "nav". Во втором фрейме отобразится страница main.html; фрейм имеет атрибут name = "main". Атрибут name в теге <frame> предназначен для идентификации фрейма, что обеспечит гиперссылкам в элементе f rameset загрузку целевого фрейма. Например, в строке <а href = "links.html" target = "main"> загрузится файл links.html во фрейм, значение атрибута name которого — "main". Цели в якорном элементе также можно задать несколько предварительных значений: target = "_blank" загру- жает страницу в новое пустое окно браузера; target = " self" загружает страницу в то же окно, в котором на- ходится якорный элемент; target = "_parent" загружает страницу в родительский набор фреймов (т. е. во frameset, содержащий текущий фрейм), a target = "_top" загружает страницу в заполненное окно браузера (страница загружается во frameset). В строках 28—38 примера кода в листинге ГН 0.9 элемент noframe отображает текст, предназначенный для браузеров, не поддерживающих фреймы. Совет по повышению переносимости__________________________________________________________ Не все пользуются браузерами, поддерживающими фреймы. Для направления пользователя к версии сайта без фреймов рекомендуется применять элемент noframes в рамках frameset. Замечание о "впечатлениях и ощущениях"____________________________________________________ Фреймы могут расширить Web-страницу, но их часто используют не совсем целесообразно. Фреймы никогда не следует использовать для выполнения задач, которые можно решить с помощью других, более простых методов форматирования HTML. П10.10. Вложенные теги <frameset> Тег <frameset> можно использовать для создания более сложных макетов Web-страниц с фреймами путем вло- жения областей frameset, как показано в листинге П10.10 (рис. П10.10). 1 <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN" 2 "http://www.w3.org/TR/html4/frameset.dtd"> 3 <html> 4 5 <!— Листинг П10.10: index.html --> б <!— Фреймы HTML. Пример 2 —> 7 8 <head> 9 10 <meta name = "keywords" content = "Webpage, design, HTML, 11 tutorial, personal, help, index, form, contact, feedback, 12 list, links, frame, deitel"> 13 14 <meta name = "description" content = "This Web site will help 15 you learn the basics of HTML and Webpage design through the 16 use of interactive examples and instruction."> 17 18 <title>C# How to Program — Tables</title> 19 </head> 20 21 <frameset cols = "110,*"> 22 <frame name = "nav" src = "nav.html"> 23 24 <!— Вложенные фреймы используются для изменения форматирования —> 25 <!— и интервалов набора фреймов в целом —> 26 <frameset rows = "175,*"> 27 <frame name = "picture" src = "picture.html"> 28 <frame name = "main" src = "main.html"> 29 </frameset> 30 62 Зак. 3333
978 Приложение 10 31 <noframes> 32 <р> 33 This page uses frames, but your browser 34 does not support them. 35 </p> 36 37 <p> 38 Please, <a href = "nav.html">follow this link 39 to browse our site without frames</a>. 40 </p> 41 </noframes> 42 43 </frameset> 44 </html> Рис. П10.10. Web-страница с набором вложенных фреймов Первый уровень тегов frameset представлен в строках 21 и 22 <frameset cols = "110,*"> <frame name = "nav" src = "nav.html'*> Здесь элементы frameset и frame построены так же, как и в листинге П10.9. Имеется один фрейм размером в 110 пикселов, начинающийся от левого края экрана. Второй (вложенный) уровень элемента frameset занимает оставшуюся часть области frame, не включенную в первичный элемент frameset. Таким образом, фреймы, включенные во второй элемент frameset, не будут со- держать крайние левые 110 пикселов экрана. В строках 26—29 представлен второй уровень тегов frameset. <frameset rows = "175,*”> <frame name = "picture" src = "picture.html"> <frame name = "main" src = "main, html "> </frameset> В данной области элемента frameset первый фрейм занимает 175 пикселов с верхней части экрана, как указано атрибутом rows = "175,*". Убедитесь в том, что во вторую область frameset включено нужное количество элементов frame. Также следует включить элемент noframe и закрыть обе области frameset в конце Web- страницы. Совет по тестированию и отладке___________________________________________________________ При использовании вложенных элементов frameset каждому уровню тега frame должен предшествовать отступ. При этом страница становится более удобочитаемой, а ее отладка упрощается.
Введение в HTML 4: часть 2 979 Замечание о "впечатлениях и ощущениях1'________________________________________________ Вложенные элементы frameset способствуют созданию красочных, визуально привлекательных и простых в просмотре Web-сайтов. П10.11. Резюме В таблицах HTML данные расположены по строкам и столбцам. Все теги, применяющиеся для оформления, и текст заключаются в теги <table>.. .</table>. Таблицы можно разделять на части по горизонтали и вертикали. Информация заголовков (названия таблиц и столбцов) должны помещаться в теги <thead>... </thead>. С по- мощью элемента tr (от англ, table row — строка таблицы) осуществляется форматирование ячеек отдельных строк. Все ячейки строки заключаются в теги <tr>.. .</tr> этой строки. Наименьшей областью таблицы, под- дающейся форматированию, является ячейка данных. Существуют два типа ячеек данных: размещенные в заго- ловке (<th>.. .</th>) и размещенные в теле таблицы (<td>.. .</td>). Ячейки заголовков, обычно помещаемые в область <thead>, подходят для названий и заголовков столбцов. Подобно <thead>, область <tbody> служит це- лям форматирования информации и группированию данных. В основном в таблицах используются элементы tbody для сохранения большей части содержимого. Ячейки td по умолчанию выровнены по левому краю. Ячей- ки th по умолчанию выровнены по центру. Подобно использованию элементов thead и tbody для форматирова- ния групп строк таблицы, для форматирования и группирования столбцов служит элемент colgroup. Элемент colgroup используется заданием в его открывающем теге количества столбцов, входящих в область его дейст- вия, а также форматирования, применяемого к данной группе столбцов. Каждый элемент col, заключенный между тегами <colgroup>... </colgroup>, может, в свою очередь, отформатировать заданное количество столб- цов. Для сбора информации от посетителей сайтов HTML предоставляет несколько механизмов, в том числе формы. Например, в форме, изменяющей данные сервера при обновлении базы данных, рекомендуется использовать значение method = "post" Данные формы будут переданы на сервер в виде переменной среды, к которой имеют доступ сценарии. Другое возможное значение method = "get" следует использовать, когда форма не вызывает изменений серверных данных, например, при запросе инфор- мации в базе данных. Данные form из атрибута method = "get" добавляются в конец URL. Атрибут action в теге form является путем для сценария, обрабатывающего данные form. Элемент input всегда присутствует в формах и требует наличия атрибута type. Два других атрибута — name, предоставляющее уникальный иденти- фикатор для элемента input, и value, указывающее значение, которое элемент input отправляет при передаче данных на сервер. Входной элемент type = "text" вставляет в форму однострочное текстовое поле. Значение этого элемента input и информация, которую сервер отправляет пользователю из элемента input, представляет собой введенный пользователем текст. Элемент input с атрибутом type = "submit" помещает в форму кнопку, при нажатии которой данные передаются на сервер. Атрибут value входных данных submit изменяет отобра- женный на кнопке текст. Элемент ввода type = "reset" помещает на форму кнопку, при нажатии которой уда- ляются все записи, введенные пользователем в форму. С помощью тега <textarea> в форме размещается мно- гострочное текстовое поле. Размер этого поля (с возможностью прокрутки) задается в начальном теге <textarea> посредством атрибутов rows и cols. Информация, введенная в элемент type = "password", отобра- жается на экране в виде "звездочек". Пароль используется для ввода конфиденциальной информации, не пред- назначенной ни для кого, кроме данного пользователя. В виде "звездочек" данные отображаются только серве- ром; реальные данные передаются на сервер. Каждый элемент input с атрибутом type = "checkbox" создает в форме новый флажок. Флажки можно использовать по одному или группой. Все флажки в группе должны иметь одинаковые атрибуты name. Введенные в форму с помощью атрибута type = "radio" тега <input> переключа- тели отличаются от флажков тем, что за один раз выбрать можно только один переключатель. Атрибуты name всех переключателей должны быть одинаковыми, а атрибуты value — разными. Элемент select помещает в форму раскрывающийся список. Для добавления в этот список пункта рекомендуется вставить тег <option> в область <select>.. .</select> и ввести текст, который пункт списка должен отображать на той же строке. Количество видимых опций списка можно изменять вводом атрибута size = "size” в тег <select>. Этот атри- бут можно использовать, если предпочтительна расширенная версия списка, а не однострочная. Карты изображения позволяют обозначить определенные части изображения как "горячие точки", используе- мые в качестве ссылок. Все элементы карты изображения заключены между тегами <тар>... <7тар>. Необходи- мым для тега <тар> является атрибут name. "Горячая точка” на изображении обозначается элементом area. Каж-
980 Приложение 10 дый элемент area имеет следующие атрибуты: href задает гиперссылку в этой точке, shape и cords определяют характеристики области, alt указывает текст всплывающей подсказки. Для использования карты изображения с графикой в элемент img необходимо ввести атрибут usemap = "#name", где лате— значение атрибута name в элементе тар. Основным элементом, взаимодействующим с поисковыми системами, является тег <meta>. Теги <meta> содер- жат два атрибута, использовать которые следует всегда. Первый атрибут — name — идентификатор типа вводи- мого тега. Атрибут content предоставляет поисковой системе информацию для упорядочения. Содержимое content тега meta с атрибутом name = "keywords" предоставляет поисковой системе список слов, описывающих ключевые аспекты сайта. При наличии тегов <meta> и информации о заключенном в них содержимом поиско- вые системы получают точное представление об искомом сайте, и это помогает привлечь к нему большее коли- чество посетителей. Значение description атрибута name в теге <meta> должно представлять собой описание сайта, состоящее из 3—4 предложений. Такое описание также используется поисковыми системами для упоря- дочения информации о сайте и ее отображения. Элементы meta невидимы для пользователей сайта и должны размещаться внутри раздела заголовка документа HTML. Тег <frameset> "сообщает" браузеру, что страница содержит фреймы. В каждом теге <frame> атрибут src пере- дает URL страницы, которая будет отображена во фрейме. Атрибут name в теге <f rame> предназначен для иден- тификации фрейма, что обеспечит гиперссылкам в теге <frameset> загрузку целевого фрейма. Атрибуту target в якорном элементе задает имя name фрейма frame, в который должна загрузиться новая страница. Не все поль- зователи имеют браузеры, поддерживающие фреймы. По этой причине в элемент frameset необходимо вводить элемент noframes. Обычные теги и элементы HTML необходимо заключать в теги <noframes>... </noframes>. Для направления пользователя к версии сайта без фреймов рекомендуется применять элемент noframes. Для создания более сложных макетов используются вложенные элементы frameset. П10.12. Ресурсы Интернета и WWW □ www.geocities.com/SiliconValIey/Orchard/5212 Adam’s Advanced HTML Page является идеальным ресурсом для желающих освоить более сложные методики HTML. На сайте представлены рекомендации по созданию таблиц, фреймов и бегущих строк, а также рас- сматриваются другие темы. □ www.w3scripts.com/html Вариант сайта "Школа HTML" (см. приложение 9) — сборник примеров с демонстрацией всех функций HTML от начального до профессионального уровня. □ www.blooberry.com/indexdot/html Index Dot HTML, The Advance HTML Reference... — сайт с обширным каталогом и древовидным указателем всех элементов языка HTML и не только. □ www.neiljohan.com/html/advancedhtml.htm В руководстве по усовершенствованным методикам HTML (Advanced HTML Guide) представлены расши- ренные уникальные рекомендации по совершенствованию дизайна Web-сайтов с помощью HTML.
ПРИЛОЖЕНИЕ 11 (ш Введение в XHTML: часть 1 Высокие мысли должны выражаться высоким языком. Аристофан Темы данного приложения: □ важнейшие компоненты документов XHTML; □ использование XHTML для создания Web-страниц; □ добавление изображений в Web-страницы; □ создание и использование гиперссылок для просмотра Web-страниц; □ разметка списков. П11.1. Введение В данном приложении представлен язык XHTML1 (Extensible HyperText Markup Language, расширяемый гипер- текстовый язык разметки). В приложении 12 описываются более сложные методики XHTML, такие как табли- цы, которые представляют особую важность при структурировании информации, получаемой из баз данных (программ со структурированными наборами данных). В данном приложении программирование на C# не рас- сматривается. В отличие от процедурных языков программирования, таких как С, Fortran, Cobol и Visual Basic, XHTML явля- ется языком разметки, задающим формат текста, отображаемого браузерами (Internet Explorer от Microsoft или Communicator от Netscape). Основным моментом при использовании XHTML1 2 является отделение представления документа (т. е. его ото- бражения на экране браузером) от структуры этого документа. В данном и следующем приложениях эти во- просы рассматриваются подробно. П11.2. Редактирование XHTML В настоящем приложении XHTML записывается в форме его исходного кода. Документы XHTML создаются в текстовом редакторе (например, Notepad, Wordpad, vi или emacs) с сохранением файлов с расширениями html или htm О хорошем стиле программирования_____________________________________________ Файлам рекомендуется присваивать имена, описывающие их (файлов) функции. Такая практика помогает быст- рее идентифицировать документы, а также упрощает доступ посетителей к Web-странице, которым легче запо- минать имена нужных файлов. Например, при написании документа XHTML, предназначенного для отображения номенклатуры продукции предприятия, файлу можно присвоить имя products.html 1 Язык XHTML заменил гипертекстовый язык разметки (HTML) в качестве основного средства описания Web-содержимого. XHTML обеспечивает более надежные, 'дополняемые и расширяемые функции, нежели HTML. Подробности о языках XHTML/HTML представлены на сайте www.w3.org/markup. 2 При подготовке книги к печати версия XHTML 1.1 приняла статус Рекомендации W3C. Приведенные в книге примеры XHTML основаны на Рекомендации XHTML 1.0, потому что браузер Internet Explorer 5.5 не поддерживает весь набор функций XHTML 1 1 В будущем Internet Explorer и другие браузеры будут поддерживать версию XHTML 1.1. По данному факту сайт www.deitel.com будет дополнен примерами и информацией по версии XHTML 1.1.
982 Приложение 11 Документы XHTML сохраняются на машинах со специализированным программным обеспечением, называе- мых Web-серверами. Клиенты (т. е. Web-браузеры) запрашивают с сервера ресурсы, например, документы XHTML. Скажем, при вводе в поле адреса браузера строки www.deitel.com/books/downloads.htm на сервер, обслуживающий сайт www.deitel.com, будет направлен запрос документа downloads.htm. Данный документ размещен в каталоге с именем books. П11.3. Первый пример XHTML В данном и следующем приложениях представлена разметка с помощью языка XHTML, а также графические изображения окон, в которых показана визуализация (отображение) XHTML браузером Internet Explorer. Для удобства читателей строки всех документов XHTML пронумерованы. Номера строк не входят в документы XHTML. Первый пример (листинг П11.1) представляет собой документ XHTML с именем main.htm, отображающий в браузере сообщение "Welcome to XHTML!". 1 <?xml version = "1.0"?> 2 <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" 3 "http://www.w3.org/TR/xhtmll/DTD/xhtmll-strict.dtd"> 4 5 <!— Листинг П11.1: main.html —> 6 <!— Первая Web-страница —> 7 8 <html xmlns = "http://www.w3.org/1999/xhtml"> 9 <head> 10 <title>C# How to Program — Welcome</title> 11 </head> 12 13 <body> 14 <p>Welcome to XHTML!</p> 15 </body> 16 </html> Результат работы программы представлен на рис. Ш 1.1. Рис. П11.1. Первый XHTML-пример Главной в программе является строка 14, сообщающая браузеру об отображении строки "Welcome to XHTML!". Рассмотрим все строки программы. Строки 1—3 нужны в документах XHTML для соответствия необходимому синтаксису XHTML. На данном этапе эти строки следует копировать в каждый создаваемый документ XHTML. Смысл этих строк подробно рассматривается в главе 15. Строки 5—6 являются комментариями. Создатели документа XHTML вставляют комментарии для повышения удобочитаемости разметки, а также для описания содержимого документа. Комментарии также помогают дру- гим пользователям понять разметку и материал файла XHTML. Комментарии не обуславливают выполнение каких-либо операций браузером при загрузке пользователем документа XHTML для просмотра. Документы XHTML всегда начинаются с символов <! — и заканчиваются символами —>. В каждый пример XHTML входят комментарии с указанием номера листинга, имени файла, а также с кратким описанием цели примера. В после- дующие примеры входят комментарий по разметке, особенно при представлении новых функций.
Введение в XHTML: часть 1 983 О хорошем стиле программирования _____________________________________________________ В разметке рекомендуется размещать комментарии. Они помогают другим разработчикам понять разметку, слу- жат подспорьем при отладке и содержат полезную информацию, которая не отображается браузером. Коммен- тарии также помогают разобраться в коде самому автору, особенно если какое-то время разработчик к нему не обращался. Разметка XHTML содержит текст, представляющий содержание документа, а также элементы, обозначающие его структуру. В число важных элементов документа XHTML входят элементы html, head и body. В элементе html заключен раздел заголовков (представленный элементом head) и раздел тела (представленный элементом body). В разделе заголовков содержится информация о документе XHTML, например, его заголовок. В разделе заголовков также содержатся специальные инструкции по форматированию, называемые таблицами стилей, а также клиентские программы, называемые сценариями, для создания динамических Web-страниц. В разделе тела содержится материал страницы, отображаемый браузером при входе пользователя на Web-страницу. В документах XHTML элементы разделены начальными и конечными тегами. Начальный тег состоит из назва- ния элемента в угловых скобках (например, <html>). Конечный тег состоит из названия элемента в угловых скобках, перед которым ставится косая черта (/) (например, </html>). В данном примере в строках 8 и 16 опре- деляется начало и конец элемента html. Обратите внимание, что конечный тег в строке 16 имеет то же имя, что и начальный тег, но ему в угловых скобках предшествует символ косой черты. Многие начальные теги имеют атрибуты, предоставляющие дополнительную информацию об элементе. Данная дополнительная информация может использоваться браузерами для определения способа обработки элемента. Каждый атрибут имеет имя и значение, разделенные знаком равенства (=). В строке 8 задается нужный атрибут (xmlns) и значение (http://www.w3.org/1999/xhtmi) для элемента html в документе XHTML. На данном этапе начальные и конеч- ный теги элемента html в строке 8 следует копировать и вставлять в документы XHTML. Подробности атрибута xmlns элемента html рассматриваются в главе 15. Распространенная ошибка программирования______________________________________________ Синтаксической ошибкой является отсутствие одинарных или двойных кавычек у значений атрибутов. Распространенная ошибка программирования______________________________________________ Синтаксической ошибкой является наличие в элементе XHTML или в имени атрибута символов, набранных в верхнем регистре. Документ XHTML делит элемент html на две части: заголовок и тело. В строках 9—11 элементом head опреде- ляется раздел заголовка Web-страницы. В строке 10 вводится элемент title, называемый вложенным, потому что он заключен между начальным и конечным тегами элемента head. Элемент title описывает Web-страницу. Заголовки обычно отображаются в заголовке окна браузера в виде текста, идентифицирующего страницу, когда пользователи добавляют ее в списки Favorites или Bookmarks на своих машинах для оперативного выхода на наиболее часто посещаемые сайты. Поисковые системы (сайты поиска сайтов в Интернете) также используют элемент title для целей упорядочивания информации. О хорошем стиле программирования______________________________________________________ Отступы вложенных элементов подчеркивают структуру документа и делают его более понятным. Распространенная ошибка программирования______________________________________________ В XHTML не предусмотрено перекрытие тегов: конечный тег вложенного элемента должен вводиться в документ перед конечным тегом объемлющего элемента. Например, теги XHTML <headxtitle>hello</head></title> вызывают появление синтаксической ошибки, потому что конечный тег </head> объемлющего элемента head вставлен перед конечным тегом </title> вложенного элемента title. О хорошем стиле программирования______________________________________________________ Для всех страниц сайта рекомендуется придерживаться единых правил присвоения названий. Например, если сайт называется "Сайт Бейли", тогда title главной страницы может быть "Сайт Бейли — ссылки". Таким обра- зом, структура сайта будет более понятной для всех его посетителей. В строке 13 открывается элемент body документа. В разделе тела документа XHTML размешается его содержи- мое, в которое может входить текст и теги. Некоторые теги, например, теги абзацев (<р> и </р>) в строке 14 размечают текст, отображаемый в браузере. Текст, заключенный в теги <р> и </р>, образует один абзац. При считывании браузером абзаца перед ним и по- сле него обычно вставляется пустая строка. Данный документ заканчивается двумя конечными (закрывающими) тегами (строки 15 и 16). Эти теги закрыва- ют элементы body и html соответственно. Конечный тег </html> в документе XHTML информирует браузер о том, что разметка XHTML завершена.
984 Приложение 11 Для просмотра данного примера в Internet Explorer выполните следующие шаги: 1. Скопируйте примеры приложения /7 на свой компьютер (их можно загрузить с сайта www.deitel.com). 2. Запустите Internet Explorer и выберите в меню File команду Open.... Откроется диалоговое окно Open. 3. Нажмите кнопку Browse... в диалоговом окне Open для открытия диалогового окна файла Microsoft Internet Explorer. 4. Перейдите к каталогу, в котором содержатся примеры данного приложения, выберите файл main.html, после чего нажмите кнопку Open. • 5. Нажмите кнопку ОК для открытия документа в Internet Explorer. Другие примеры открываются таким же образом. На данном этапе окно браузера должно выглядеть примерно так, как показано на рис. Ш 1.1. П11.4. Служба контроля XHTML W3C Программирование приложений на базе Web может оказаться довольно сложным процессом, и поэтому следует внимательно следить за корректным написанием документов XHTML для их последующей обработки браузе- рами. Для обеспечения корректности консорциумом W3C предоставлена служба контроля (validator.w3.org), предназначенная для проверки корректности синтаксиса документа. Проверку допустимости документов можно осуществлять либо из URL, указывающего местоположение файла, либо загрузкой файла на сайт valida- tor, w3.org/file-upload.html. При загрузке файл копируется с пользовательского компьютера на другой компью- тер, подключенный к Интернету. На рис. Ш 1.2 показан файл main.html (из листинга П11.1), загружаемый для проверки. Несмотря на то, что в странице W3C указано название службы HTML Validation Service1, она также подходит для проверки синтаксиса документов XHTML. Проверка допустимости всех примеров XHTML в кни- ге успешно осуществлена на сайте (validator.w3.org). Нажатием кнопки Browse... пользователь может выбрать на своем компьютере файл для загрузки. После выбора файла при нажатии кнопки Validate this document он загружается. На рис. П11.3 показаны результаты проверки файла main.html. В данном документе синтаксических ошибок не содержится. При наличии синтаксических ошибок служба контроля выдает сообщения, их описывающие. Совет по тестированию и отладке_________________________________________________________ Для подтверждения синтаксической корректности документа XHTML рекомендуется пользоваться такими служ- бами контроля, как W3C HTML Validation Service. Рис. П11.2. Проверка корректности документа XHTML (предоставлено W3C) 1 HTML (гипертекстовый язык разметки) является предшественником XHTML, разработанным для разметки Web-содержимого. В настоящее время использование технологии HTML не поощряется.
Введение в XHTML: часть 1 985 Д W3I HTML Validation Service Result* Microsoft йЧггшЧ Explorer 3»^ W3C HTML Validation Service Results File; CAvbhtp2e'E jampf₽o\App L.XHTML1 \FlgJ _QL\main’.htrnlI/ Detected Character unknown Encoding. Document Type. Xt ITML i.Q Smet Root Namespace: ittp7/www, w3'.org/1999/xhtml Select Doctype: {(detect automatically) ’j . Options г Show Source , T Outline Parse Tree .no attributes I Revalidate Warnings Warning: No Character Encoding detected! To assure correct validation, processing, and display, it is important that the character encoding Is properly labeled. Further explanations. Below are the results of checking this document for XML well- formedness and validity. No errors found! 2. This document would validate as the document type specified if you updated it to match the Options used. Рис. П11.3. Результаты аттестации XHTML (предоставлено W3C) П11.5. Заголовки Определенные части текста в документе XHTML могут быть более важны, нежели другие. Например, текст в разделе считается важнее ссылки. В XHTML имеется шесть заголовков, называемых элементами заголовков, для определения относительной важности информации. В листинге П11.2 показаны эти элементы (с hl по h6). Совет по повышению портативности_____________________________________*____________________ В разных браузерах размеры шрифта отображаемого заголовка могут варьироваться. ПОНб лизинг П11.2. Элементы заголовка с 1 <?xml version = "1.0"?> 2 <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" 3 "http://www.w3.org/TR/xhtmll/DTD/xhtmll-strict.dtd"> 4 5 <!— Листинг П11.2: header.html —> 6 <!— Заголовки XHTML —> 7 8 <html xmlns - "http://www.w3.org/1999/xhtml"> 9 <head> 10 <title>C# How to Program — Welcome</title> 11 </head> 12 13 <body> 14 15 <hl>Level 1 Header</hl> 16 <h2>Level 2 header</h2> 17 <h3>Level 3 header</h3> 18 <h4>Level 4 header</h4>
986 Приложение 11 19 <h5>Level 5 header</h5> 20 <h6>Level 6 header</h6> 21 22 </body> 23 </html> Элемент hl (строка 15) рассматривается как самый важный заголовок и отображается более крупным шрифтом, чем остальные пять заголовков (строки 16—20). Каждый последующий элемент заголовка (h2, ьз и т. д.) ото- бражается гарнитурой меньшего размера. Результат работы программы представлен на рис. П11.4. Рис. П11.4. Заголовки на Web-странице Замечание о "впечатлениях и ощущениях"__________________________________________________ При наличии заголовков в верхней части каждой страницы XHTML посетителям легче понять их предназначение. Замечание о "впечатлениях и ощущениях"__________________________________________________ Для выделения важных разделов Web-страницы в заголовках рекомендуется пользоваться гарнитурами боль- ших размеров. П11.6. Ссылки Одной из важнейших функций XHTML является гиперссылка* обращающаяся (или связывающаяся) к другим ресурсам сети, например, документам XHTML и графическим изображениям. В XHTML в роли гиперссылок могут выступать текст и изображения. В браузерах текстовые гиперссылки обычно изображаются подчеркну- тыми (цвет по умолчанию — голубой), чтобы пользователям было легче отличить их от обычного текста. В лис- тинге П11.3 созданы текстовые гиперссылки на четыре разных Web-сайта. ----- - ММ*' •• - -г-,.—- -.--чмг- д-—.•< — Листинг П11.3 Ссылки на другие Web-c- аницы Л.... .... * 1 <?xml version = "1.0"?> 2 <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" 3 "http://www.w3.org/TR/xhtmll/DTD/xhtmll-strict•dtd"> 4 5 <!— Листинг П11.3: links.html —> 6 <!— Введение в гиперссылки —> 7 8 <html xmlns =' "http://www.w3.org/1999/xhtml"> 9 <head> 10 <title>C# How to Program — Welcome</title> 11 </head> 12
Введение в XHTML: часть 1 987 13 <body> 14 15 <hl>Here are my favorite sites</hl> 16 17 <p><strong>Click a name to go to that page.</strong></p> 18 19 <!— создание четырех текстовых гиперссылок —> 20 <р> 21 <а href = "http://www.deitel.com">Deitel</a> 22 </p> 23 24 <p> 25 <a href = "http://www.prenhall.com">Prentice Hall</a> 26 </p> 27 28 <p> 29 <a href = "http://www.yahoo.com">Yahoo!</a> 30 </p> 31 32 <p> 33 <a href = "http://www.usatoday.com">USA Today</a> 34 </p> 35 36 </body> 37 </html> В строке 17 вводится тег <strong>. В браузерах текст, отмеченный тегом <strong>, отображается полужирным шрифтом. Результат представлен на рис. Ш 1.5. How to Program - links - Microsoft Internet fxpforer № Frrf ив<н FmrtirteS Tools rrib КЭ *>Back... * -у j! *S| 5£lSear£h xces ^Hseqry * Address roiectlExamplesVApp_K_XHTMll\Fiqi<Jo8\linl<s.html i*|, jj^Gd uhks W Here are my favorite sites Click a name to go to that page. Deitel Prentice Hall Yahoo! USA Today Рис. П11.5. Использование гиперссылок. Ссылки создаются с помощью элемента а (якорь). В строке 21 определяется гиперссылка, связывающая текст "Deitel" с URL, присвоенным атрибуту href, указывающему местоположение связанного ресурса, например Web-страницы, файла или адреса электронной почты. Данный якорный элемент связывается с Web-страницей,
988 Приложение 11 размещенной по адресу http://www.deitel.com. Если URL не указывает документа на Web-сайте, Web-сервер возвращает страницу по умолчанию. Данная страница часто имеет название index.html, однако большинство Web-серверов можно сконфигурировать на использование любого файла в качестве страницы по умолчанию для того или иного сайта. (Откройте http://www.deitel.com в окне браузера, а во втором окне браузера — http://www.deitel.com/index.html для проверки их идентичности.) Если Web-сервер не может обнаружить ме- стоположение требуемого документа, тогда в браузер передается сообщение об ошибке, которое тот и выдает пользователю. Якоря могут осуществлять связывание с электронными адресами посредством URL mailto:. При щелчке посе- тителем сайта по якорной ссылке такого типа в большинстве браузеров по умолчанию открывается программа электронной почты (например, Outlook Express) для того, чтобы пользователь мог составить сообщение для от- правки по связанному адресу. В листинге П11.4 продемонстрирован данный тип якоря. 1 <?xml version = "1.0"?> 2 <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" 3 "http://www.w3.org/TR/xhtmll/DTD/xhtmll-strict.dtd"> 4 5 <!— Листинг П11.4: contact.html —> 6 <!— Добавление гиперссылок на электронный адрес —> 7 8 <html xmlns = "http://www.w3.org/1999/xhtml"> 9 <head> 10 <title>C# How to Program — Welcome</title> 11 </title> 12 </head> 13 14 <body> 15 16 <p>My email address is 17 <a href = "mailto:deitel@deitel.com”> 18 deitel@deitel.com 19 </a> 20 . Click the address and your browser will 21 open an e-mail message and address it to me. 22 </p> 23 </body> 24 </html> В строках 17—19 содержится ссылка на адрес электронной почты. Формат якоря почты — <а href = "mailto: адрес_электронной_почп/‘>... </а>. В данном случае связка осуществляется с электронным адресом deitel@deitel.com. Результат работы программы представлен на рис. Ш 1.6. Зс# Н ч to Prograra Wekorte - Vrcr: so't l»t>'+ 1 । ► .-trtvr J j /J ^searcti ngferurtei p МсНпя jl^1c;tDocurr^s and 5ettlrKlsVnattykDeskwKurr«?I r^Go My email address is deitel@deitel.com. Click the address and your browser will open an e-mail message and address it to me. 6 ”3 Рис. П11.6. Ссылка на электронный адрес: а — ссылка в документе; б — окно для составления элекгронного'письма
Введение в XHTML: часть 1 989 П11.7. Изображения В рассмотренных примерах продемонстрирована разметка документов, содержащих только текст. Однако в большинстве Web-страниц присутствует как текст, так и изображения, которые, фактически, представляют со- бой важную часть дизайна Web-страниц. Двумя наиболее популярными графическими форматами, используе- мыми Web-дизайнерами, являются GIF (Graphics Interchange Format, формат графического обмена) и JPEG (Joint Photographic Experts Group, объединенная группа экспертов в области фотографии). Графические изобра- жения можно создавать с помощью специализированных программных средств, например Adobe Photoshop и Jasc Paint Shop Pro (www.jasc.com). Изображения также можно загружать с различных Web-ресурсов (gal- Iery.yahoo.com). В листинге Ш 1.5 продемонстрировано включение графических изображений в Web-страницы. Результат работы программы представлен на рис. П11.7. 1 <?xml version = "1.0"?> 2 <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" 3 "http://www.w3.org/TR/xhtmll/DTD/xhtmll-strict.dtd"> 4 5 <!— Листинг П11.5: picture.html —> 6 <!— Добавление изображений в XHTML —> 7 8 <html xmlns = "http://www.w3.org/1999/xhtml"> 9 <head> 10 <title>C# How to Program — Welcome</title> 11 </head> 12 13 <body> 14 15 <p> 16 <img src = "csphtp.jpg" height = "238" width = "181" 17 alt = "C# How to Program book cover" /> 18 19 <img src = "jhtp.jpg" height = "238" width = "181" 20 alt = "Java How to Program book cover" /> 21 </p> 22 23 </body> 24 </html> Рис. П11.7. Изображения на Web-странице
990 Приложение 11 В строках 16—17 используется элемент img для вставки в документ изображения. Местоположение файла изо- бражения указывается атрибутом src элемента img. В данном случае изображение помещено в тот же каталог, что и документ XHTML, поэтому требуется только имя файла. Необязательные атрибуты width (ширина) и height (высота) обозначают ширину и высоту изображения соответственно. Автор документа может изменять размер изображения увеличением или уменьшением значений атрибутов width и height. Если эти атрибуты не учитываются, то браузер отображает фактические ширину и высоту изображения. Размеры изображений изме- няются в пикселах (picture element), обозначающих цветовые точки на экране. Изображение в листинге П11.5 имеет ширину 181 пиксел, а высоту — 238 пикселов. О хорошем стиле программирования_________________________________________________________ Значения атрибутов width и height изображения следует всегда указывать внутри тега <img>. При загрузке браузером файла XHTML по этим значениям он сразу определит пространство на экране для корректного раз- мещения изображения даже перед его визуализацией. Совет по повышению производительности_______________________________________________ При размещении значений атрибутов width и height в теге <img> браузеры загружают и визуализируют графи- ческие изображения более оперативно. Распространенная ошибка программирования__________________________________________________ Ввод новых размеров, при которых изменяется соотношение ширины к высоте, может исказить внешний вид изображения. Например, если изображение имеет ширину 200 пикселов, а высоту— 100 пикселов, то при указа- нии новых значений всегда следует соблюдать соотношение ширины и высоты 2:1. Каждый элемент img в документе XHTML имеет атрибут alt. Если браузер не визуализирует графику, то ото- бражается значение атрибута alt. Сбои визуализации изображений обусловлены несколькими причинами. Во- первых, браузер может не поддерживать графические элементы (это имеет место в текстовых браузерах, ото- бражающих только текст), либо клиент мог отключить функцию визуализации изображений для сокращения времени загрузки страниц. В листинге П11.5 продемонстрирована визуализация значения атрибута alt браузе- ром Internet Explorer, когда документ делает ссылку на несуществующий файл изображения (jhtp.jpg). Атрибут alt важен для создания Web-страниц с возможностью доступа к интернет-ресурсам людей с физиче- скими недостатками, особенно с нарушениями зрения, использующими тестовые браузеры. Специализирован- ные программные средства, называемые синтезаторами речи, часто используются инвалидами. Такие про- граммные приложения "проговаривают" значение атрибута alt так, что пользователю становится понятно, что отображено на экране монитора. Аспекты доступности подробно рассмотрены в главе 21. Некоторые элементы XHTML (называемые пустыми элементами) содержат только атрибуты и не размечают текст (т. е. между начальным и конечным тегами текст отсутствует). Пустые элементы (например, img) должны определяться либо вводом символа левой косой черты (/) внутри закрывающей правой угловой скобки (>) на- чального тега, либо явным вводом конечного тега. При использовании символа левой косой черты перед ним делается пробел для удобочитаемости (как показано в конце строк 17 и 20). Вместо использования символа ле- вой косой черты строки 19 и 20 можно записать с помощью закрывающего тега </img> следующим образом: <img src = "jhtp.jpg" height = "238" width = "181" alt = "Java How to Program book cover" /> Используя изображения в качестве гиперссылок, Web-дизайнеры могут создавать графические Web-страницы, связанные с другими ресурсами. В листинге П11.6 создано шесть разных гиперссылок изображений. 1 <?xml version = "1.0"?> 2 <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" 3 "http://www.w3.org/TR/xhtml 1/DTD/xhtmll-strict.dtd"> 5 <!— Листинг П11.6: nav.html —> 6 <!— Использование изображений в качестве ссылок —> 7 8 <html xmlns = "http://www.w3.org/1999/xhtml"> 9 <head> 10 <title>C# How to Program — Welcome 11 </title> 12 </head> 13
Введение в XHTML: часть 1 991 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 <body> <p> <a href = "links.html"> <img src = "buttons/links.jpg" width = "651 height = "50" alt = "Links Page" /> </a><br /> <a href = "list.html"> <img src = "buttons/list. jpg" width » "65" height = "50" alt = "List Example Page” /> </a><br /> <а href = "contact.html"> <img src = "buttons/contact.jpg" width = "65 height = "50" alt = "Contact Page" /> </a><br /> <a href = "header.html"> <img src = "buttons/header.jpg" width = "65 height = "50" alt = "Header Page" /> </a><br /> <a href = "table.html"> <img src = "buttons/table.jpg" width = "65" height = "50" alt = "Table Page" /> </a><br /> <a href = "form.html"> <img src = "buttons/form.jpg" width = "65" height = "50" alt = "Feedback Form" /> </a><br /> </p> </body> </html> Результат работы программы представлен на рис. П11.8. j How to Program Welcome - Microsoft Ini... [* ГМ Mt view "avorfcw tods ‘flOH «4Г-.1Й’ iff . 3 Lirks n х»«>аде| Features Ct71' ui Me H. a- tr Example* Tao>s Page Feedback For. Sjte:///C;/Documents. _K _XHTMl 1 \FigK _08\nav htrnj Рис. П11.8. Использование изображений в качестве ссылок
992 Приложение 11 В строках 17—20 создается гиперссылка изображения путем вложения элемента img в якорный элемент (а). Значение атрибута src элемента img указывает на то, что это изображение (links.jpg) находится в каталоге buttons Каталог buttons и документ XHTML находятся в одном каталоге. Можно сделать ссылки на изображе- ния из других Web-документов (после получения разрешения от владельца документа) установкой атрибута src на имя и местоположение графического объекта. В строке 20 представлен элемент Ьг, визуализируемый в большинстве браузеров в виде разрыва строки. Любая разметка или текст, следующие за элементом Ьг, отображаются на следующей строке. Подобно элементу img, br является примером пустого элемента, закрываемого левой косой чертой. Для удобочитаемости перед символом левой косой черты вводится пробел. П11.8. Специальные символы и разрывы строк При разметке текста могут возникнуть трудности с вводом некоторых символов или знаков (например, <) в до- кумент XHTML. На некоторых клавиатурах такие символы попросту отсутствуют, либо их ввод вызывает появ- ление синтаксических ошибок. Например, результатом разметки <p>if х < 10 then increment х by 1</р> является синтаксическая ошибка, потому что в ней (в разметке) используется знак "меньше", зарезервирован- ный для начальных и конечных тегов, в данном случае <р> и </р>. В языке XHTML для обозначения таких сим- волов существуют специальные символы, или ссылки на запись символов (в форме &code;). Предыдущую строку можно исправить следующим образом: <p>if х &lt; 10 then increment x by l</p> Здесь вместо математического знака "меньше" указан специальный символ &lt;. В листинге П 11.7 продемонстрировано использование специальных символов в документе XHTML. Перечень специальных символов приведен в приложении 13. 5-,, --V ...... • . ..............-л.' Г’ - Г i Листинг П11.7. Вставка специальных символов в документ XHTML .. -..: . ;....... . л-...;..-..,..* 1 <?xml version = "1.0"?> 2 <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN” 3 "http://www.w3.org/TR/xhtmll/DTD/xhtmll-strict.dtd"> 4 5 <!— Листинг П11.7: contact2.html —> 6 <!— Вставка специальных символов —> 7 8 <html xmlns = "http://www.w3.org/1999/xhtml"> 9 <head> 10 <title>C# How to Program — Welcome 11 </title> 12 </head> 13 14 <body> 15 16 <!— специальные символы вводятся —> 17 <!— с помощью формы &code; —> 18 <р> 19 Click 20 <а href = "mailto:deitel@deitel.com">here 21 </а> to open e-mail message addressed to 22 deitel@deitel.com. 23 </p> 24 25 <hr /> <!— вставка горизонтальной линии —> 26 27 <p>All information on this site is <strong>&copy;</strong> 28 Deitel <strong>&amp;</strong> Associates, Inc. 2002.</p> 29
Введение в XHTML: часть 1 993 30 <!— для зачеркивания текста используются теги <del> *~> 31 <!— для создания нижнего индекса текста служат теги <sub> —> 32 <!— для создания верхнего индекса текста нужны теги <sup> —> 33 <!— эти теги вкладываются в другие теги —> 34 <p><del>You may download 3.14 х 10<sup>2</sup> 35 characters worth of.information from this site.</del> 36 Only <sub>one</sub> download per hour is permitted.</p> 37 38 <p>Note: <strong>&lt; &fracl4;</strong> of the information 39 presented here is updated daily.</p> 40 41 </body> 42 </html> В строках 27 и 28 содержатся другие специальные символы, выраженные либо в виде сокращений слов (напри- мер, аир— ampersand (&) и сору— copyright), либо в виде шестнадцатеричных (hex) значений (например, &#38; — шестнадцатеричное представление &ашр;). Шестнадцатеричные числа имеют основание 16; цифры в шестнадцатеричном числе имеют значения от 0 до 15 (всего 16 разных значений). Буквы А—F обозначают шестнадцатеричные цифры, соответствующие десятичным значениям 10—15. Таким образом, в шестнадцате- ричной записи можно получить, например, число 876, целиком состоящее из цифр, подобных десятичным, чис- ло в виде DA19F, состоящее из цифр и букв, и число DCB, состоящее только из букв. Подробно шестнадцате- ричные цифры рассмотрены в приложении 2. В строках 34—36 вводятся три новых элемента. В большинстве браузеров элемент del визуализируется в виде зачеркнутого текста. С помощью данного формата пользователи могут легко обозначать исправления в доку- менте. Для превращения текста в верхний индекс (т. е. его поднятия вертикально на строке и уменьшения разме- ра шрифта) или в нижний индекс (опускание текста в строке ниже и уменьшения размера шрифта) воспользуй- тесь элементом sup или sub соответственно. Здесь также используются специальные символы &lt; для обозна- чения математического знака "меньше” и &fraci4; для обозначения дроби % (строка 38). Помимо специальных символов в документе представлена горизонтальная линия, обозначенная тегом <hr /> в строке 25. Тег <hr /> также вставляет обрыв строки над горизонтальной линией и под ней. Результат работы программы представлен на рис. П11.9. C# How to Program - Welcome • Microsoft Internet Explorer Links «-• Back Fe’'0,tw То* - Jlfl Oj Д search ^Favcxtes ^history & Click hereto open an e-mail message addressed to deitel@deitel.com. AH information on this site is © Deitel & Associates, Inc. 2002. rwnk ed 3. M x 1eh voters wvrth ifrafonnafeeu fr tn titer Only * download per hovr is permitted Note: < % of the information presented here is updated daily. J Рис. П11.9. Использование специальных символов на Web-странице П11.9. Маркированные списки До сих пор в приложении описывались базовые элементы и атрибуты XHTML для ссылок на ресурсы, создания заголовков с помощью специальных символов, а также для вставки в документы XHTML графических изобра- жений. В данном разделе рассматривается упорядочение информации на Web-странице с помощью списков. В приложении 12 рассматривается еще один способ упорядочения информации— таблицы. В листинге П11.8 показан текст в маркированном списке (т. е. в списке, пункты которого не отсортированы по буквам или номе- рам). С помощью элемента маркированного списка ul создается список, в котором каждый пункт начинается с маркера в виде точки. 63 Зак. 3333
994 Приложение 11 г v’t^r.......- •’игаажггзкг’ггт— Листинг П11,8. Маркированные списки в XHTML С...к.^?Л1».-...<. ..;.«• ....Л /.* ..к. ЛЛ.«.«............. »........-.м^«.. л». 1 <?xml version = ”1.0”?> 2 <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" 3 ’’http: / /www. w3. org/TR/xhtml 1 / DTD/xhtmll -st r ict. dtd" > 4 5 <!— Листинг П11.8: links2.html —> 6 <!— Маркированный список, содержащий гиперссылки —> 7 8 <html xmlns = "http://www.w3.org/1999/xhtml"> 9 <head> 10 <title>C# How to Program — Welcome</title> 11 </head> 12 13 <body> 14 15 <hl>Here are my favorite sites</hl> 16 17 <p><strong>Click on a name to go to that page.</strong></p> 18 19 <!— создание маркированного списка —> 20 <ul> 21 22 <!— добавление четырех пунктов списка —> 23 clixa href = "http://www.deitel.com">Deitel</ax/li> 24 25 <li><a href = "http://www.w3.org">W3C</ax/li> 26 27 <lixa href = "http://www.yahoo.com">Yahoo!</a></li> 28 29 <li><a href = "http://www.cnn.com">CNN</ax/li> 30 31 </ul> 32 33 </body> 34 </html> Каждая запись маркированного списка (элемент ul в строке 20) является элементом li (от англ, list item) (стро- ки 23, 25, 27 и 29). В большинстве Web-браузеров визуализируют эти элементы с помощью обрыва строки и символа маркера с отступом от начала каждой новой строки. Результат работы программы представлен на рис. П11.10. Рис. П11.10. Использование маркированных списков
Введение b XHTML: часть 1 995 П11.10. Вложенные и нумерованные списки Списки могут быть вложенными для обозначения иерархических отношений, как в эскизном формате. В лис- тинге ПИ.9 представлены вложенные и нумерованные списки (т. е. списки с пунктами, упорядоченными по буквам или цифрам). 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 25а 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 <?xml version = "1.0"?> <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1..0 Transitional//EN" "http: //www.w3. org/TR/xhtml 1 /DTD/xhtmll-transitional. dtd"> <!— Листинг П11.9: list.html —> <!— Усовершенствованные списки: вложенные и нумерованные —> <html xmlns = "http://www.w3.org/1999/xhtml"> <head> <title>C# How to Program — Welcome</title> </head> <body> <hl>The Best Features of the Internet</hl> <!— создание маркированного списка —> <ul> <li>You can meet new people from countries around the world.</li> <li> You have access to new media as it becomes public: <!— начало вложенного списка, —> <!— использование модифицируемых маркеров —> , <!— список заканчивается закрывающим тегом <tg> —> <ul> <li>New games</li> <li> New applications <!— вложенный нумерованный список —> <ol type = "I"> <li>For business</li> <li>For pleasure</li> <ol/> </li> <li>Around the clock news</li> <li>Search engines</li> <li>Shopping</li> <li> Programming <!— другой вложенный нумерованный список —> <ol type = "а"> <li>XML</li> <li>Java</li> <li>XHTML</li> <li>Scripts</li> <li>New languages</li> </ol>
996 Приложение 11 55 </li> 56 57 </ul> <!— окончание вложенного списка, начатого —> 57а <!— в строке 27 —> 58 59 </li> 60 61 <li>Links</li> 62 <li>Keeping in touch with old friends</li> 63 <li>It is the technology of the future!</li> 64 65 </ul> <!— окончание маркированного списка, начатого —> 65а <!— в строке 18 —> 66 67 <hl>My 3 Favorite <em>CEOfe</emx/hl> 68 69 <!— элементы ol без типа; атрибут соответствует —> 70 <!— числовой последовательности (1, 2, ...) —> 71 <о1> 72 <li>Lawrence J.Ellison</li> 73 <li>Steve Jobs</li> 74 <li>Michael Dell</li> 75 </ol> 76 77 </body> 78 </html> Результат работы программы представлен на рис. П11.11. Рис. 1*111.11. Демонстрация вложенных и нумерованных списков Первый нумерованный список начинается в строке 33. Атрибутом type задается тип последовательности (т. е. набор цифр или букв, используемый в этом списке). В данном случае присвоением атрибуту type значения "I"
Введение в XHTML: часть 1 997 задаются римские прописные цифры. В строке 47 начинается второй нумерованный список, и атрибуту type задается значение "а", указывая на строчные буквы, по которым будут упорядочиваться пункты списка. В по- следнем нумерованном списке (строки 71—^75) атрибут type не используется. По умолчанию порядок пунктов данного списка — от 1 до 3. Для обозначения иерархических взаимоотношений для каждого вложенного списка браузером делается отступ. По умолчанию пунктам самого внешнего маркированного списка (строка 18) предшествуют маркеры-точки. Пунктам списка, вложенного в маркированный список в строке 18, предшествуют маркеры-кружки. Перед пунктами следующего вложенного списка ставятся маркеры-квадраты (в данном примере не показаны). Перед пунктами маркированного списка можно искусственно (явно) задать установку точке, кружков или квадратов присвоением атрибуту type элемента ul значения "disc", "circle" или "square" соответственно. Язык XHTML создан на базе языка HTML (HyperText Markup Language) — патентованной технологии консор- циума W3C. В HTML было принято задавать содержимое, структуру и форматирование. При форматировании можно было указать место Web-страницы, в которое браузер должен поместить элемент, либо задать гарнитуры шрифтов и цвета для отображения любого элемента. Так называемая строгая форма XHTML обеспечивает ото- бражение в корректном документе XHTML только его содержимого и структуры, но не форматирования. В не- скольких первых примерах использовалась только строгая форма XHTML. На самом деле, целью строк 1 и 2 в каждом из примеров до листинга П11.9 было "сообщение" браузеру о том, что каждый из документов соответ- ствует строгому определению XHTML. Этим подтверждалась синтаксическая корректность документа. Сущест- вуют и другие типы документов XHTML. В данном примере выбран переходный тип документа XHTML в по- мощь создателям документов XHTML при использовании патентованных технологий HTML в документах XHTML. В последнем примере атрибут type элемента о1 (строки 33 и 47) является патентованной технологией HTML. Изменение строк 2 и 3 позволяет продемонстрировать упорядоченные списки с разными форматами нумерации. Обычно такое форматирование задается с помощью таблиц стилей. Большинство примеров в книге придерживаются строгой формы HTML. Совет по тестированию и отладке__________________________________________________________ В большинстве существующих браузеров по-прежнему делаются попытки визуализации документов XHTML, да- же если они некорректны синтаксически. П11.11. Резюме XHTML (Extensible HyperText Markup Language) — это язык разметки для создания Web-страниц. Ключевым аспектом XHTML является разделение представления документа (т. е. внешнего вида документа после визуали- зации браузером) и структуры информации этого документа. В XHTML текст размечается элементами, заклю- ченными в теги, представляющие собой имена в угловых скобках. Некоторые элементы могут содержать до- полнительную разметку, называемую атрибутами, предоставляющими дополнительную информацию об эле- менте. Документы XHTML сохраняются на компьютерах со специализированными программными средствами, называемых Web-серверами. Синтаксически корректные документы XHTML визуализируют элементы гаранти- рованно корректно. Службы контроля (например, validator.w3.org) обеспечивают синтаксическую коррект- ность документа XHTML. В каждом документе XHTML содержит начальный тег <htmi> и конечный тег </htmi>. Комментарии в XHTML всегда начинаются с символов <!— и заканчиваются символами —>. Текст комментария браузерами не рас- сматривается. Каждый документ XHTML имеет элемент head, содержащий такую информацию, как заголовки, а также элемент body с материалами Web-страницы. Информация элемента head обычно не визуализируется в окне страницы, но пользователь может получить к ней доступ другими средствами. Элемент title присваивает название Web-страницы. Название отображается в заголовке окна браузера, а также в виде текста, идентифици- рующего страницу, при добавлении страницы в список Favorites или Bookmarks. Телом документа XHTML является область, в которой помещено содержимое документа. В содержимое документа может входить текст и теги. Текст, размещенный между тегами <р> и </р>, образует один абзац. В XHTML имеется шесть заголовков (с hl по h6) для обозначения относительной важности информации. Элемент заголовка hl рассматривается как самый важный заголовок и обозначается более крупной гарнитурой шрифта, чем остальные пять заголовков. Каждый последующий элемент заголовка (т. е. h2, h3 и т. д.) отображается более мелкой гарнитурой шрифта. В браузерах гиперссылки по умолчанию подчеркиваются и выделяются голубым цветом. С помощью тега <strong> в окне браузера визуализируется полужирный текст. Пользователь может вставлять в Web-страницы ссылки с помощью элемента а (якоря). Важнейшим атрибутом элемента а является атрибут href, указывающий ресурс (т. е. страницу, файл или адрес электронной почты), с которым устанавливается связь. Якоря могут осуществлять ссылку на электронные адреса с помощью URL
998 Приложение 11 mailto:. При щелчке пользователем кнопкой мыши на якорной ссылке такого типа в большинстве браузеров по умолчанию запускается программа электронной почты (например, Outlook Express) для создания электронных сообщений на "привязанный" адрес. Атрибут src элемента img указывает местоположение графических объектов. Необязательные атрибуты — width (ширина) и height (высота) обозначают ширину и высоту изображения соответственно. Размеры изобра- жений изменяются в пикселах (picture element), обозначающих цветовые точки на экране. Каждый элемент img в синтаксически корректном документе XHTML должен иметь атрибут alt, содержащий текст, отображаемый вместо графического объекта (если последний визуализировать нельзя). Атрибут alt делает Web-страницы бо- лее доступными для людей с ограниченными физическими возможностями, особенно с нарушениями зрения. Некоторые элементы XHTML — пустые; в них содержатся только атрибуты, и с их помощью текст не размеча- ется. Пустые элементы (например, img) должны завершаться либо с помощью символа левой косой черты (/), либо явным вводом конечного тега. При наличии элемента Ьг в большинстве браузеров визуализируется обрыв строки. Любая разметка или текст, идущие за элементом Ьг, отображаются на следующей строке. В языке XHTML представлены специальные символы или ссылки на символы (в форме &code;) для обозначения знаков, которые нельзя разметить. В большинстве браузеров визуализируется горизонтальная линия, обозначенная те- гом <hr /> в виде горизонтальной черты. Также элементом hr над горизонтальной чертой и под ней вставляется обрыв строки. С помощью элемента ul маркированного списка создается список, каждый пункт которого начинается с марке- ра в виде точки. Каждый пункт маркированного списка представляет собой элемент li. В большинстве Web- браузеров эти элементы визуализируются в виде обрыва строк и маркера перед началом строки. Списки могут вкладываться друг в друга, обозначая иерархическую структуру данных. Атрибутом type задается тип последо- вательности (т. е. набор цифр или букв, использованный в нумерованном списке). П11.12. Ресурсы Интернета и WWW □ www.w3.org/TR/xhtmll В XHTML 1.0 Recommendation содержится общая информация, сведения по вопросам совместимости, опре- делению типов документов, терминология и многое другое по теме XHTML. □ www.xhtml.org На сайте XHTML.org представлены новости разработки XHTML, а также ссылки на другие ресурсы XHTML, в число которых входят книги и статьи. □ www.w3schools.com/xhtml/default.asp XHTML School представляет вопросники-викторины, а также ссылки по XHTML. На данной странице со- держатся ссылки на вопросы синтаксиса XHTML, контроля и определения типов документов. □ validator.w3.org Сайт службы контроля документов XHTML, разработанной консорциумом W3C. □ hotwired.lycos.com/webmonkey/00/50/index2a/html На данном сайте представлена статья об XHTML. В основных разделах статьи описана технология XHTML с обсуждением тегов, атрибутов и якорей. □ wdvl.com/Authoring/Languages/XML/XHTML Виртуальная библиотека Web-дизайнеров (Web Developers’ Virtual Library) представляет введение в XHTML. Среди материалов сайта — статьи, примеры и ссылки на другие технологии. □ www.w3.0rg/TR/l 999/xhtml-modularization-19990406/DTD/doc На сайте документации XHTML 1.0 DTD представлены ссылки на документацию DTD для строгих, пере- ходных и фреймовых определений типов документов.
ПРИЛОЖЕНИЕ 12 Введение в XHTML: часть 2 И со скрижали памяти своей Сотру я все ничтожные любовные посланья. Уильям Шекспир Темы данного приложения: □ создание таблиц со строками и столбцами данных; □ управление форматированием таблиц; □ создание и использование форм; □ создание и использование карт изображений для повышения доступности Web-страниц; □ обеспечение доступа к Web-страницам для поисковых систем посредством тегов <meta>; □ применение элемента frameset для отображения нескольких Web-страниц в одном окне браузера. П12.1. Введение В предыдущем приложении был представлен язык XHTML и построено несколько законченных Web-страниц с текстом, гиперссылками, графическими изображениями, горизонтальными линиями и разрывами строк. В дан- ном приложении функции XHTML рассматриваются более подробно; сюда входит представление информации в виде таблиц, а также включение форм для сбора информации от посетителей Web-страниц. Авторы представ- ляют внутреннее связывание, карты изображений для расширения возможностей просмотра Web-страниц, а также фреймы для отображения браузером нескольких документов. В конце приложения читатели познакомятся с наиболее распространенными функциями XHTML и смогут создавать более сложные Web-документы. Про- граммирование C# не рассматривается. П12.2. Базовые таблицы XHTML В данном разделе представлена таблица XHTML — часто используемая функция упорядочивания данных по строкам и столбцам. В первом примере (листинг П12.1) используется таблица, состоящая из шести строк и двух столбцов, в которой представлена информация о ценах на фрукты. 1 <?xml version = "1.0"?> 2 <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" 3 ”http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"> 4 5 <!— Листинг П12.1: tablel.html —> 6 <!— Создание базовой таблицы —> 7 8 <html xmlns = "http://www.w3.org/1999/xhtml"> 9 <head> 10 <title>A simple XHTML table</title> 11 </head> 12 13 <body> 14
1000 Приложение 12 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55, 56 57 58 59 60 61 62 63 64 65 66 67 68 <!— тег <table> открывает таблицу —> <table border = "1" width = "40%" summary = "This table provides information about the price of fruit"> < !— тег <caption> представляет общий смысл —> < !— таблицы для людей с нарушениями зрения —> <caption><strong> Price of Fruit</strongx/caption> < !— <thead> — первый раздел таблицы —> < !— форматирование области заголовка таблицы —> <thead> <tr> <!— тег <tr> вставляет одну строку таблицы <th>Fruit</th> <!— вставка ячейки заголовка <th>Price</th> </tr> </thead> < !— все содержимое таблицы заключено в тег <tbody> —> <tbody> <tr> <td>Apple</td> <!— вставка ячейки данных —> <td>$0.25</td> </tr> <tr> <tdX)range</td> <td>$0.50</td> </tr> <tr> <td>Banana</td> <td>$1.00</td> </tr> <tr> <td>Pinapple</td> <td>$2.00</td> </tr> </tbody> <!— <tfoot> — последний раздел таблицы —> <!— форматирование нижнего колонтитула таблицы —> <tfoot> <tr> <th>Total</th> <th>$3.75</th> </tr> </tfoot> </table> </body> </html> Таблицы определяются элементом table. В строках 16—18 вводится начальный тег для элемента table, имею- щего несколько атрибутов. Атрибутом border задается ширина границ таблицы в пикселах. Для создания таб- лицы без рамок задайте атрибуту border значение "0". В данном примере атрибут width имеет значение "40%" для установки ширины таблицы на 40% от ширины окна браузера. Разработчик может задать атрибуту width любое значение в пикселах. Совет по тестированию и отладке_______________________________________________________ Попробуйте изменить размеры окна браузера, чтобы выяснить, как ширина окна влияет на ширину таблицы.
Введение в XHTML: часть 2 1001 По названию атрибута summary (строка 17) понятно, что им описывается содержимое таблицы В устройствах имитации речи данный атрибут используется для повышения доступности таблицы для людей с нарушениями зрения. С помощью элемента caption (строка 22) описывается содержимое таблицы; данный элемент также помогает интерпретировать табличные данные текстовыми браузерами. В большинстве браузеров текст внутри тега <caption> отображается над таблицей. Атрибут summary и элемент caption— две из многих функций XHTML, обеспечивающих доступность Web-страниц для пользователей с ограниченными физическими воз- можностями. Программирование функций доступности подробно рассматривается в главе 21. Таблица имеет три раздела: заголовок, тело и нижний колонтитул. Раздел заголовка (или ячейка заголовка) определяется с помощью элемента thead (строки 26—31); в нем содержится информация заголовков, например, названия столбцов. Каждый элемент tr (строки 27—30) определяет отдельную строку таблицы. Столбцы в разделе заголовков задаются с помощью элементов th. В большинстве браузеров текст, отформатированный элементом th (ячейка заголовка таблицы), выравнивается по центру и выделяется полужирным стилем. Элемен- ты заголовков таблицы вкладываются в элементы строк таблицы. В разделе тела, или тела таблицы, содержатся собственно данные таблицы. Тело таблицы (строки 34—54) оп- ределяется в элементе tbody. В ячейках данных находятся отдельные части данных, и такие ячейки определяют- ся с помощью элементов td (данные таблицы). Нижняя часть таблицы (строки 58—63) определяется с помощью элемента tfoot (от англ, table foot) и обознача- ет нижний колонтитул. В нижней части таблицы обычно размещаются общие результаты вычислений и колон- титулы. Как и в других разделах, здесь могут быть строки таблицы, и в каждой строке — столбцы. Результат работы программы представлен на рис. П12.1. Заголовок таблицы Тело таблицы Нижний раздел таблицы Рамка Рис. П12.1. Пример таблицы П12.3. Сложные таблицы XHTML и их форматирование В предыдущем разделе рассмотрена структура простейшей таблицы. В листинге П 12.2 продолжено изучение таблиц представлением элементов и атрибутов, позволяющих Web-дизайнерам создавать более сложные таб- лицы. 1 <?xml version = "1.0"?> 2 <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" 3 "http://www.w3.org/TR/xhtmll/DTD/xhtmll-strict.dtd"> 4 5 <!— Листинг П12.2: table2.html —> 6 <!— Проектирование сложной таблицы —> 7 8 <html xmlns = "http://www.w3.org/1999/xhtml"> 9 <head> 10 <title>C# How to Program - Tables</title> 11 </head> 12
1002 Приложение 12 13 <body> 14 15 <h’l>Table Example Page</hl> 16 17 <table border = "1"> 18 <caption>Here a more complex sample table.</caption> 19 20 <!— теги <colgroup> и <col> используются —> 21 <!— для форматирования целых столбцов —> _ 22 <colgroup> 23 24 <!-- атрибут span определяет, на сколько —> 25 <!— столбцов влияет тег <со1> —> 26 <col align = "right" span = "1" /> 27 </colgroup> 28 29 <thead> 30 31 <!— указаны интервалы строк и объединение —> 32 а <!— интервалов столбцов —> 32 <!— количество столбцов по вертикали или горизонтали —> 33 <tr> 34 <!— объединение двух строк —> <th rowspan = "2"> <img src = "camel.gif" width = "205" height = "167" alt = "Pucture of a camel" /> </th> 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74- <!— объединение четырех столбцов —> <th colspan = "4" valign = "top"> <hl>Camelid comparison<7hl><br /> <p>Approximate as of 9/2002</p> </th> </tr> <tr valign = "bottom"> <th># of Humps</th> <th>Indigenous region</th> <th>Spits?</th> <th>Produces Wool?</th> </tr> </thead> <tbody> <tr> <th>Camels.(bactrian)</th> <td>2</td> <td>Africa/Asia</td> <td>rowspan = "2">Llama</td> <td>rowspan = "2”>Llama</td> </tr> <tr> <th>Llamas</th> <td>l</td> <td>Andes Mountains</td> , </tr> </tbody>
Введение в XHTML, часть 2 1003 75 </table> 76 77 </body> 78 </html> Результат работы программы представлен на рис. П12.2. Рис. П12.2. Пример сложной таблицы Таблица начинается в строке 17. С помощью элемента colgroup (строки 22—27) столбцы группируются и фор- матируются. Элемент col (строка 26) задает в данном примере два атрибута. Атрибутом align задается вырав- нивание текста в столбце. Атрибут span определяет количество столбцов, форматируемых элементом col. В данном случае значение атрибута align— "right", а атрибута span— "1" для выравнивания текста в первом столбце по правому краю (в данном столбце содержится изображение верблюда). Размер ячеек таблицы может изменяться для размещения в них данных. Авторы документов Moiyr создавать ячейки большего размера с помощью атрибутов rowspan и colspan. Присвоенными этим атрибутам значениями определяется количество строк или столбцов, занимаемых ячейкой. Элементом th в строках 36—39 использует- ся значение "2" атрибута rowspan для того, чтобы ячейка, в которой содержится изображение верблюда, могла занимать две ячейки, смежные вертикально (т. е. данная ячейка "захватывает" две строки). Элементом th в строках 42—45 используется значение "4" атрибута colspan для расширения ячейки заголовка, содержащей текст "Camelid comparison" и "Approximate as of 9/2002", для охвата четырех ячеек. Распространенная ошибка программирования_________________________________________________ При использовании атрибутов colspan и rowspan для настройки размеров ячеек данных таблицы следует пом- нить. что ячейки с измененными размерами будут занимать более одного столбца или строки, дополнительные строки или столбцы, занятые отдельными ячейками, должны "компенсироваться" за счет других строк или столбцов. Если этого не сделать, то форматирование таблицы собьется, и по ошибке разработчик может соз- дать больше строк или столбцов, чем необходимо. В строке 42 вводится атрибут valign, выравнивающий данные по вертикали, которому можно присвоить одно из четырех значений: при выборе "top" данные выравниваются по верхней границе ячейки; при выборе значе- ния "middle" данные выравниваются вертикально по центру (выравнивание по умолчанию для всех ячеек дан- ных и заголовков); при выборе значения "bottom" данные выравниваются по нижней границе ячейки, а значение "baseline" игнорирует гарнитуры шрифтов, выбранные для данных строк, и устанавливает нижнюю границу текста в строке по базовой строке (т. е. по горизонтальной линии, по которой выровнены все символы в слове). П12.4. Базовые формы XHTML При посещении Web-сайтов от пользователей часто требуется определенная информация, как то* адрес элек- тронной почты, ключевые слова поиска, почтовые индексы и т. д. Для сбора такой информации в XHTML име- ется механизм, называемый формой.
1004 Приложение 12 Данные, вводимые пользователем в Web-страницу, оправляются на Web-сервер, обеспечивающий доступ к ре- сурсам сайта (например, к документам XHTML или графическим изображениям). Эти ресурсы размещены либо на той же машине, что и Web-сервер, либо на компьютере, к которому сервер может получить доступ по сети. При запросе браузером Web-страницы или файла, размещенного на сервере, последний обрабатывает запрос и возвращает нужный ресурс. В запросе содержится имя и путь доступа к ресурсу, а также способ связи (назы- ваемый протоколом). Документы XHTML используют протокол HTTP (HyperText Transfer Protocol, протокол передачи гипертекста). В листинге П12.3 данные формы передаются на Web-сервер, который, в свою очередь, передает их в сценарий CGI (Common Gateway Interface, общий шлюзовой интерфейс), написанный на Perl, С или другом языке про- граммирования. Сценарий обрабатывает полученные с сервера данные и, как правило, возвращает информацию на сервер. Затем сервер передает информацию в форме документа XHTML в Web-браузер. Примечание_______________________________________________________________________________ Данный пример демонстрирует функциональность программы-клиента. При отправке формы нажатием кнопки Submit Your Entries будет иметь место ошибка Формы могут содержать визуальные и невизуальные элементы. В число визуальных компонентов входят кноп- ки и другие компоненты графического пользовательского интерфейса, с которыми взаимодействует пользова- тель. В невизуальных компонентах, называемых скрытыми входными данными, сохраняются любые указанные пользователем данные, например, электронные адреса и имена файлов документов XHTML, выступающие в качестве ссылок. Форма начинается в строке 23 представлением элемента form. Атрибутом method определяется способ отправки данных формы на Web-сервер. Листинг П12.3. Простая форма со скрытыми и текстовыми полями - ........Л...Л «... ........ ......'J.....xli........h.a..... 1 <?xml version = "1.0"?> 2 <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" 3 "http: //www.w3.org/TR/xhtmll/DTD/xhtmll-stnct.dtd"> 4 5 <!— Листинг П12.3: form.html —> 6 <!— Пример 1 проектирования формы —> 7 8 <html xmlns = "http://www.w3.org/1999/xhtml"> 9 <head> 10 <title>C# How to Program — Forms</title> 11 </head> 12 13 <body> 14 15 <hl>Feedback Form</hl> 16 17 <p>Please fill out this form to help 18 us improve our site.</p> 19 20 <!— тег <form> открывает форму, предоставляет — 21 <!— способ отправки информации —: 22 <!— и местоположение сценариев формы —: 23 <form method = "post" action = "/cgi-bin/formail"> 24 25 <p> 26 27 <!— скрытые входные данные содержат —> 28 <!— невизуальную информацию —> 29 <input type = "hidden" name = "recipient" 30 value = "deitel@deitel.com" /> 31 32 <input type = "hidden" name = "subject" 33 value = "Feedback Form" /> 34 35 <input type = "hidden" name = "redirect" 36 value = "main.html" /> 37 </p> 38
Введение в XHTML: часть 2 1005 39 <!— <input type = "text"> вставляет текстовое поле —> 40 <р> 41 <label>Name: 42 <input name = "name" type = "text" size « "25" 43 maxJjength = "30" /> 44 </label> 45 </p> 46 47 <p> 48 49 <!— элементы ввода "submit" и "reset" —> 50 <!— вставляют кнопки для отправки —> 51 <!— и удаления содержимого формы —> 52 <input type = "submit" value = 53 "Submit Your Entries" /> 54 55 cinput type = "reset" value = 56 "Clear Your Entries" /> 57 </p> 58 59, </form> 60 61 </body> 62 </html> Использованием атрибута method="post" данные формы добавляются в запрос браузера, содержащий протокол HTTP и URL запрошенного ресурса. Сценарии, размещенные на машине сервера (или на компьютере, доступ к которому может быть осуществлен по сети), могут получить доступ к данным формы, отправленным в виде части запроса. Например, сценарий может принять информацию формы и обновить список электронной рас- сылки. При другом возможном значении атрибута method— "get" — данные формы добавляются напрямую в конец URL. Например, к URL /cgi-bin/formmail можно добавить информацию формы name=bob. Атрибут action в теге <form> задает URL сценария, размещенного на Web-сервере; в данном случае определя- ется сценарий, отправляемый данные формы на адрес электронной почты. Большинство провайдеров интернет- услуг имеют такой сценарий на своих сайтах; для корректного использования сценария проконсультируйтесь с системным администратором на предмет настройки документа XHTML. В строках 29—36 указаны три элемента input, определяющие данные для передачи в сценарий, обрабатываю- щий форму (он еще называется обработчиком формы). Эти три элемента input имеют атрибут type="hidden", позволяющий автору документа передавать данные формы, не введенные пользователем в сценарий. Три скрытых элемента ввода — это электронный адрес, на который будут отправлены данные, строка темы электронного сообщения и URL, на который будет направлен браузер после передачи формы. Двумя другими атрибутами input являются: name, идентифицирующий элемент input, и value, обеспечивающий значение, ко- торое будет отправлено на Web-сервер. О хорошем стиле программирования_____________________________________________________- Элементы input должны размещаться в начале формы сразу после открывающего тега <form>. При таком раз- мещении авторам документов будет проще находить местоположение элементов input. Другой тип type элемента input представлен в строках 38 и 39. Элемент input="text" вставляет в форму тек- стовое поле. Пользователи могут вводить данные в эти текстовые поля. Элемент label (строки 37—40) обеспе- чивает пользов'ателей информацией о назначении того или иного элемента input. Распространенная ошибка программирования__________________________________________________ Ошибкой проектирования является отсутствие элемента label для, каждого элемента формы. Без этих элемен- тов пользователи не смогут определить назначение тех или иных элементов формы. Атрибутом size элемента input задается количество символов, отображаемых в текстовом поле. Необязатель- ным атрибутом maxlength ограничивается количество символов, введенных в текстовое поле. В данном случае количество вводимых символов ограничивается 30. В строках 52—56 представлены два типа элементов input. Элемент ввода "submit" является кнопкой. При на- жатии пользователем кнопки "submit" браузер отправляет данные формы на сервер для обработки. Атрибутом value определяется текст, отображаемый на кнопке (значение по умолчанию — Submit). Элемент ввода "reset"
1006 Приложение 12 позволяет задать всем элементам формы значения по умолчанию. Атрибутом value элемента ввода "reset" на- значается отображаемый на кнопке текст (значение по умолчанию — Reset). Результат работы программы представлен на рис. П12.3. Рис. П12.3. Простая форма XHTML П12.5. Более сложные формы XHTML В предыдущем разделе представлены простейшие формы. В данном разделе рассматриваются элементы и атри- буты для создания более сложных форм. В листинге П12.4 содержится форма, запрашивающая впечатления пользователя о Web-сайте. Г..... «чти-» ' - Листинг Л 12,4, Форма, содержащая области текста, поле ввода паролей и флажки 1 c?xml version = "1.0"?> 2 <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" 3 "http://www.w3.org/TR/xhtmll/DTD/xhtmll-strict.dtd"> 4 5 <!— Листинг П12.4: form2.html —> 6 <!— Пример 2 проектирования формы —> 7 8 <html xmlns = "http://www.w3.org/1999/xhtml"> 9 <head> 10 <title>C# How to Program — Forms</title> 11 </head> 12 13 <body> 14 15 <hl>Feedback Form</hl> 16 17 <p>Please fill out this form to help 18 us improve our site.</p> 19 20 <form method = "post" action = "/cgi-bin/formmail"> 21 22 <p> 23 <input type = "hidden" name = "recipient" 24 value = "deitel@deitel.com" /> 25 26 cinput type = "hidden" name = "subject" 27 value = "Feedback Form" /> 28 29 cinput type = "hidden" name = "redirect" 30 value = "main.html" /> 31 c/p> 32
Введение в XHTML: часть 2 1007 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 <Р> clabel>Name: cinput name = "name" type == "text" size e "25" /> </label> </p> <!— <textarea> создает многострочную текстовую область —> <р> <label>Comments:<br /> <textarea name = "comments" rows - "4" cols = "36">Enter your commentsдhere. </textarea> </labelx/p> <!— <input type = "password"> отображает текстовое поле, —> <!— данные которого будут показаны специальными —> <!— символами "***" —> <Р> <label>Email Address: <input name = "email" type = "password" size = "25" /> </label> </p> <P> cstrong>Things you liked:</strong><br /> <label>Site design <input name = "thingsliked" type = "checkbox" value « "Design" /x/label> <label>Links <input name = "thingsliked" type = "checkbox" value "Links" /x/label> <label>Ease of use <input name "thingsliked" type = "checkbox" value = "Ease" /x/label> <label>Images <input name = "thingsliked" type « "checkbox" value = "Images" /x/label> <label>Source code cinput name = "thingsliked" type = "checkbox" value = "Code" /></label> </p> <P> cinput type = "submit" value = "Submit Your Entries" /> cinput type = "reset" value = "Clear Your Entries" /> c/p> c/form> c/body> c/html> Результат работы программы представлен на рис. П12.4.
1008 Приложение 12 i c# How to Program I orms - Microsoft internet btptorer E. „ . , .....,............. He Edr Mew:. Favertas Took нй -> И jrwr Jrwry Й - J h^CurreftC# ProJectl£xamples\A₽pJ.. XHTML2\H» CH\forriu html M |?Go links Feedback Form 11 Comments:__________________ Enter your connencs here. Submn YQurtntnas | Cteor Ywr Entnei Tilings yon liked: Site design Г~ Links Г Ease of use Г Images Г Please fill out this form to help us mjprove our site. Name: | E-mail Address: f Рис. П12.4. Сложная форма XHTML С помощью элемента textarea (строки 42—44) осуществляется вставка в форму многострочных текстовых по- лей, называемых текстовыми областями. Количество строк задается атрибутом rows, а количество столбцов (т. е. символов) — с помощью атрибута col. В данном примере элемент textarea имеет высоту четыре строки и ширину 36 символов. Для отображения текста в текстовой области по умолчанию его следует поместить между тегами <textarea> и </textarea>. Текст по умолчанию можно задать в других типах input, таких как текстовые поля, с помощью атрибута value. Элемент ввода "password" в строках 52 и 53 вставляет текстовое поле заданного размера size. Пароль обеспе- чивает возможность ввода конфиденциальной информации, например, номеров кредитных карт и других паро- лей, путем отображения их в виде *. На Web-сервер вместо звездочек передается фактическая информация. В строках 60—78 вводится элемент позиции набора флажков для формы. Позиции обеспечивают возможность выбора опций из этого набора. При выборе флажок отмечается ’’галочкой". В противном случае он остается пустым. С помощью каждого элемента ввода "checkbox" создается новая позиция набора. Их можно использо- вать по отдельности или группой. Позициям, принадлежащим к одной группе, присваивается одно имя паше (в данном случае — "thingsliked"). Распространенная ошибка программирования ____________ _ __ Когда в форме имеется несколько позиций с одинаковым именем паше, необходимо убедиться, что они имеют разные значения values, иначе сценарий, выполняющийся на Web-сервере, не сможет их различить. Продолжим рассмотрение форм приведением третьего примера, где представлены дополнительные элементы для выбора пользователями (листинг П12.5, рис. П12.5). В данном примере вводятся два новых типа input. Первый тип — переключатель (строки 90—113) — обозначен типом "radio". Переключатели похожи на флаж- ки, за исключением того, что за один раз выбрать можно только один переключатель из группы. Все элементы в группе имеют один и тот же атрибут паше; различаются они атрибутами value. Пара checked="checked" (стро- ка 92) указывает изначально выбранный переключатель (если он существует). Атрибут checked также применя- ется к позициям набора флажков. яйривзтовашвпр**.....................—.....—.... —...:."7...... ......... Листинг П12.5. Форма, содержащая переключатели и раскрывающийся список ________________________________;£^.ШВЛа№Ва&ЗЖЯ!___________.............u с 1 <?xml version = "1.0"?> 2 <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" 3 "http://www.w3.org/TR/xhtmll/DTD/xhtmll-strict.dtd"> 4
- Введение в XHTML: часть 2 1009 5 б 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 <1—’ Листинг П12.5: form3.html —> <!— Пример 3 проектирования формы —>. <html xmlns » "http://www.w3.org/1999/xhtml"> <head> <title>C# How to Program — Forms</title> </head> - <body> <hl>Feedback Form</hl> <p>Please fill out this form to help us improve our site.</p> <form method = "post" actioft = "/cgi-bin/formmail"> <P> <input type = "hidden" name = "recipient" value = "deitel@deitel.com" /> cinput type = "hidden" name = "subject" value = "feedback Form" /> <input type = "hidden" name = "redirect" value = "main.html" /> </p> <P> <label>Name: ' <input name = "name" type » "text" size = "25" /> </label> </p> <p> <label>Comments: <br /> ctextarea name = "comments" rows = "4" cols = "36"></textarea> </label> </p> <P> <label>Email Address: <input name = "email” type = "password" size = "25" /> </label> </p> <P> <strong>Things you liked:</strong><br /> <label>Site design <input name = "thingsliked" type = "checkbox" value = "Design" /> </label> <label>Links <input name = "thingsliked" type = "checkbox" value = "Links" /> </label> 'X. <label>Ease of use <input name « "thingsliked" type = "checkbox" value = "Ease" /> </label> 64 Зак. 3333
1010 Приложение 12 70 71 <label>Images 72 cinput name = "thingsliked" type = "checkbox" 73 value = "Images" /> 74 </label> 75 76 <label>Source code 77 cinput name = "thingsliked" type = "checkbox" 78 value = "Code" /> 79 c/label> 80 81 c/p> 82 83 c!— элемент cinput type = "radio"> создает один —> 84 с!— переключатель. Переключатели и флажки — 85 с!— различаются тем, что выбрать можно —> 86 с!— один переключатель в группе —> 87 ср> 88 cstrong>How did you get to our site?:c/strong>cbr /> 89 90 clabel>Search engine 91 cinput name = "howtosite" type = "radio" 92 value = "search engine" checked = "checked" /> 93 c/label> 94 95 clabel>Links from another site 96 cinput name = "howtosite" type - "radio" 97 value = "link” /> 98 c/label> 99 100 clabel>Deitel.com Web site 101 cinput name = "howtosite"' type « "radio"- 102 value = "deitel.com" /> 103 c/label> 104 105 dabel>Reference in a book 106 cinput name = "howtosite" type « "radio" 107 value = "book" /> 108 c/label> 109 110 clabel>Other 111 cinput name = "howtosite" tyjpe = "radio" 112 value = "other" /> 113 c/label> 114 115 c/p> 116 117 cp> 118 clabel>Rate our site: 119 120 c!— тег cselect> представляет раскрывающийся —> 121 с!— список с выбором, указываемым —> 122 с!— тегами coptions> —> 123 cselect name = "rating"> 124 coption selected « "selected">Amazingc/option> 125 coption>10c/option> 126 coption>9c/option> 127 coption>8c/option> 128 coption>7c/option> 129 coption>6c/option> 130 coption>5c/option> 131 coption>4c/option>
Введение в XHTML: часть 2 1011 132 <option>3</option> 133 <option>2</option> 134 <option>l</option> 135 <option>Awful</option> 136 </select> 137 138 </label> 139 </p> ' 140 141 <p> 142 cinput type = "submit" value = 143 "Submi?: Your Entries" /> 144 145 • cinput type = "reset" value = "Clear Your Entries" /> 146 c/p> 147 148 c/form> 149 150 c/body> 151 c/html> C4 How t< Program Form* Microsoft JntetTM'бкркие* ^10*2 He EC few Fy rke. Tools -Mf r>9 i - - J jJ ttSeercn ^aventei >пяагу J - jj] ;Ad*«s b J\Curr^C#Pr^act\Exanpi«s\A<ipJ.J<HTn2tF».O5Vonn3.htlri £j <>Go ; 'link» »| □ Feedback Form Please fill out this form to help us improve our site. Name: Г Comments: E-mail Address: | Things you liked: Site design F" Links Г Ease of use П Images Г How did you get to our site?: Search engine <? Links from another site in a book Other Г Rate our site: | Amazing Г ubmit¥ou> Entries OeerVourEnme «JOon. Рис. П12.5. Сложная форма XHTML с переключателями и раскрывающимся списком Распространенная ошибка программирования______________________________________________ Если при использовании в форме группы переключателей не указан одинаковый атрибут name, тогда пользова- тель будет одновременно отмечать все переключатели. Элемент select (строки 123—136) вводит раскрывающийся список, в котором пользователь может выбрать нужный пункт. Этот список идентифицируется атрибутом name. С помощью элемента option (строки 124—135) в раскрывающийся список можно добавлять пункты. Атрибут selected элемента option указывает изначально (по умолчанию) выбранный пункт в элементе select.
1012 Приложение 12 П12.6. Внутренние ссылки В приложении 11 рассматривалось создание гиперссылок между Web-страницами. В листинге П 12.6 (рис. П12.6) представлены внутренние ссылки— механизм, обеспечивающий пользователя возможностью пе- реходить из одного места документа в другое. Внутренние ссылки удобно использовать в объемных докумен- тах, содержащих много разделов. При щелчке кнопкой мыши на внутренней ссылке пользователи переходят к нужному месту документа без его пролистывания. Листинг П12.6. Использование внутренних ссылок для ускорения доступа к частям страницы .....г»™?. зег.— ------------------«—»..——..—----- .... ...Л 1 <?хш1 version = "1.0"?> 2 C.DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" 3 "http://www.w3.org/TR/xhtmll/DTD/xhtmll-strict.dtd"> 4 5 <!— Листинг П12.6: links.html —> 6 <!— Внутренние ссылки —> 7 8 <html xmlns = "http://www.w3.org/1999/xhtml"> 9 <head> 10 <title>C# How to Program — List</title> 11 </head> 12 13 <body> 14 15 <!— элемент <a name = ".."></a> создает внутренние гиперссылки —> 16 <p><a name = "features"></a></p> 17 18 <hl>The Best Features of the Internet</hl> 19 20 <!— адрес внутренней ссылки — "#linkname" —> 21 <p> 22 <a href = "#ceos">Go to <em>Favorite CE0s</em></a> 23 </p> 24 25 <ul> 26 <li>You can meet people from countries 27 around the world.</li> 28 29 <li>You have access to new media as it becomes public: 30 31 <ul> 32 <li>New games</li> 33 <li>New applications 34 35 <ul> 36 <li>For Business</li> 37 <li>For Pleasure</li> 38 </ul> 39 40 </li> 41 42 <li>Around the Clock news</li> 43 <li>Search Engines</li> 44 <li>Sh±pping</li> 45 <li>Programming 46 47 <ul> 48 <li>HTML</li> 49 <li>Java</li> 50 <li>Dynamic HTML</li> 51 <li>Scripts</li> 52 <li>New languages</li> 53 </ul> 54
Введение в XHTML: часть 2 1013 55 56 57 58 59 60 61 62 63 64 65 €6 67 68 69 70 71 72 • 73 74 75 76- 77 78 79 80 81 82 83 84 85 </li> <ul> </li> <li>Links</li> <li>Keeping in touch with old friends</li> <li>It is the technology of the future!</li> </ul> <!— именованный якорь —> <p><a name = ’,ceos"x/a></p> <hl>My 3 Favorite <em>CEOs</em></hl> <p> <!— внутренняя гиперссылка на функции —: <а href = "#features"> Go to <em>Favorite Features</em> </a> </p> <ol> <li>Lawrence J. Ellison</li> <li>Steve Jobs</li> <li>Michael Dell</li> </ol> </body> </html> Hk jJP nt*» Трок мкр Щ .jHstory j@□ Adies^^«klTO\Q>rer*C#Projert\Exampte«\App L XHTHL2W-06Unki.htrrt rj fifUQfe 1L# How to Program Ust Mirrowtt Internet Ixfawer The Best Features of the Internet Go to Favorite CEOs • You can meet people from countries around the world. • You have access to new media as it becomes public: о New games о New applications For Business For Pleasure о Around the clock news о Search Engines о Shopping о PrcjjrjMiun ^ Dono - Рис. П12.6. Использование внутренних ссылок
1014 Приложение 12 В строке 16 содержится именованный якорь (с именем features) для внутренней гиперссылки. Для связывания в одной Web-странице с якорем такого типа атрибут href другого якорного элемента вводит именованный якорь, перед которым ставится знак # (как в элементе #features). В строках 63—74 содержится гиперссылка с якорем features в качестве цели. При выборе данной гиперссылки в браузере содержимое окна переходит к якорю features в строке 16. Замечание о "впечатлениях и ощущениях"______________________________________________________ Использовать внутренние гиперссылки полезно в документах XHTML, содержащих большие объемы информа- ции. Внутренние ссылки на разделы страницы упрощают работу с ней: пользователям не приходится пролисты- вать весь документ, чтобы найти нужное место. В приведенном примере не показано, что можно задать внутреннюю ссылку, находящуюся в другом документе, вводом его имени, за которым следует знак # и именованный якорь, как, например, в выражении href = "page.html#name" Например, для связки с именованным якорем с именем booklist в файле books.html атрибуту href присваивает- ся ссылка "books.html#booklist". П12.7. Создание и использование карт изображений В приложении 11 демонстрировалось использование изображений в качестве гиперссылок на другие ресурсы Интернета. В данном разделе рассматривается другая методика связывания изображений, называемая картой изображения, которая обозначает в качестве ссылок определенные участки графического объекта ("горячие точки"). В листинге П12.7 представлены карта изображения и "горячие точки". <?xml version = "1.0"?> <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtmll/DTD/xhtmll-strict.dtd"> 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 <!— Листинг П12.7: picture.html —> <!— Создание и использование карт изображений —> chtml xmlns = "http://www.w3.org/1999/xhtml"> <head> <title>C# How to Program — Image Map </title> </head> <body> <P> <!— Ter <map> определяет карту изображения —> <map id = "picture"> <!— shape = "rect" обозначает прямоугольную область —> <!— с координатами верхнего левого —> <!— и нижнего правого углов —> <area href = "form.html" shape = "rect" coords = "2, 123, 54, 143." alt = "Go to the feedback form" /> <area href = "contact.html” shape = "rect" coords = "126, 122, 198, 143" alt = "Go to the contact page" /> <area href "main.html" shape = "rect" coords = "3, 7, 61, 25" alt "Go to the homepage" />
Введение в XHTML: часть 2 1015 35 <area href = "links.html" shape = "rect" 36 coords = "168, 5, 197, 25" 37 alt « "Go to the links page" /> 38 39 <!— значение "poly" создает "горячую точку" —> 40 <!— многоугольника, определенного координатами —> 41 <area shape = "poly" alt = "E-mail the Deitels" 42 coords = "162,225,154,39,158,54,169,51,183,39,161,26" 43 href = "mailto:deitel@deitel.com" /> 44 45 <!— "shape = circle" указывает область окружности —> 46 <!— с заданными центром и радиусом —> 47 <area href = "mailto:deitel@deitel.com" 48 shape = "circle" coords = "100, 36, 33” 49 alt = "Email the Deitels" /> 50 </map> 51 52 <!— элемент <img src= usemap = "#name"> указывает, что—> 53 <!— с изображением будет использована указанная карта —> 54 <img src = "deitel.gif" width = "200" height = ”144" 55 alt = "Deitel logo" usemap = "#picture" /> 56 </p> 57 58 </body> 59 </html> Результат работы программы представлен на рис. П12.7. а Рис. П12.7. Использование карты изображения: а — страница с картой изображений; б — страница, на которую выполнен переход В строках 19—50 посредством элемента шар определяется карта изображения. Атрибутом id (строка 19) иден- тифицируется эта карта. Если элемент id опущен, тогда изображение не может содержать ссылки. Процесс ссылки на карту изображения будет рассмотрен ниже. "Горячие точки" определяются с помощью элементов area (как показано в строках 24—26). Атрибутом href (строка 24) задается цель ссылки (т. е. ресурс, на кото- рый она делается). Атрибутами shape (строка 24) и coords (строка 25) обозначается форма "горячей точки" и координаты соответственно. Атрибутом alt (строка 26) вводится альтернативный текст ссылки. Распространенная ошибка программирования_________________________________________________ Если не указать атрибут id для элемента тар, то элемент img не сможет использовать элементы area элемента шар для определения "горячих точек". Разметка в строках 24—26 создает прямоугольную "горячую точку" (shape="rect") для координат, указанных в атрибуте coord. Пара координат состоит из двух чисел, являющихся местоположением точки на осях х и у. Ось х проходит горизонтально из верхнего левого угла, ось у — вертикально. Каждая точка на изображении имеет уникальную координату по осям х и у. В случае с прямоугольной "горячей точкой" необходимыми являются координаты левого верхнего и правого нижнего углов прямоугольника. В данном случае левый верхний угол
1016 Приложение 12 прямоугольника расположен в значении 2 по оси х и 123 по оси у (записано как 2, 123). Нижний правый угол прямоугольника находится в точке с координатами (54, 143). Координаты измеряются в пикселах. Распространенная ошибка программирования__________________________________________________ При перекрытии координат карты изображений браузер отображает первую заданную "горячую точку" данной области. Карта area (строки 41—43) присваивает атрибуту shape значение "poly" для создания "горячей точки" в форме многоугольника с помощью координат в атрибуте coords. Эти координаты обозначают каждую вершину или угол многоугольника. Браузер связывает эти точки с линиями для образования области "горячей точки". Карта area (строки 47—49) присваивает атрибуту shape значение "circle" для создания "горячей точки" в форме окружности. В данном случае атрибутом coords задаются координаты центра окружности и ее радиуса в пикселах. Для использования карты изображения с элементом img атрибуту usemap элемента img присваивается id карты тар. В строках 54 и 55 делается ссылка на карту изображения с именем "picture". Карта изображения размеще- на в том же документе, поэтому используется внутреннее связывание. П12.8. Тег <meta> Поисковые системы применяются для поиска интересующих сайтов в Интернете. В таких системах сайты обычно ищутся по ссылкам с одной страницы на другую с сохранением информации идентификации и класси- фикации для каждой страницы, которую посетил пользователь. Одним из способов упорядочения страниц явля- ется считывание содержимого элементов meta каждой страницы, в которых приведена информация о документе. Двумя важными атрибутами элемента meta являются атрибут name, идентифицирующий тип элемента meta, и content, предоставляющий поисковым системам информацию для каталогизации Web-страниц. В листин- ге П12.8 представлен элемент meta. <?xml version = "1.0"?> <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtmll/DTD/xhtmll-strict.dtd"> 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 <!— Листинг П12.8: main.html —> <!— Использование тегов <meta> —> <html xmlns = "http://www.w3.org/1999/xhtml"> <head> <title>C# How to Program — Welcome</title> <!— Теги <meta> предоставляют необходимую информацию —> <!— поисковым системам для каталогизации сайта. —> <meta name = "keywords" content = "Webpage, design, XHTML, tutorial, personal, help, index, form, contact, feedback, list, links, frame, deitel" /> <meta name = "description" content = "This Web site will help you learn the basics of XHTML and Web page design through the use of interactive examples and instruction." /> </head> <body> <hl>Welcome to Our Web Site!</hl> . <P> We have designed this site to teach about the wonders of <strong><em>XHTML</em><strong>. <em>XHTML</em> is better equipped than <em>XHTML</em> to represent complex
Введение в XHTML: часть 2 1017 33 data on the Internet. <em>XHTML</em> takes advantage of 34 XML's strict syntax to ensure well-formedness. Soon you 35 will know about many of the great new features of 3 6 <em>XHTML. </em> " 37 </p> 38 39 <p>Have Fun With the Site!</p> 40 41 </body> 42 </html> В строках 14—16 представлен meta-элемент "keywords”. Атрибут content данного элемента meta передает по- исковым системам список слов, описывающих страницу. Эти слова сравниваются со словами в строках ключе- вых слов поиска. Таким образом, включение элементов meta и их информации (content) привлекает к сайту больше посетителей. В строках 18—21 представлен meta-элемент "description". Атрибут content данного элемента предоставляет описание сайта, состоящее из 3—4 предложений. Поисковые системы используют данное описание для катало- гизации сайта и иногда отображают его в виде части результатов поиска. Замечание по технологии программирования_________________________________________ Элементы meta невидимы для пользователей сайта и должны размещаться внутри раздела заголовка (head) до- кумента XHTML. Если элементы meta не размещены в.данном разделе, тогда они не будут считываться поиско- выми системами. П12.9. Тег <frameset> - Все до сих пор созданные Web-страницы имели возможность ссылаться на другие страницы, но отображали за одно обращение только одну страницу. В листинге П12.9 представлены фреймы, обеспечивающие одновремен- ное отображение нескольких документов XHTML — файлов из листингов П12.8 и П12.10. | Листинг П12.9. Web-документ с использованием двух фреймов: навигации и содержимого 1 ’ <?xml version = "1.0"?> 2 <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Frameset//EN" 3 "http://www.w3.org/TR/xhtmll/DTD/xhtmll-frameset.dtd"> 4 5 <!— Листинг П12.9: index.html —> 6 <!— Фреймы XHTML (пример 1) —> 7 8 <html xmlns = "http://www.w3.org/1999/xhtml"> 9 <head> 10 <title>C# How to Program — Main</title> 11 12 <meta name = "keywords" content = "Webpage, design, 13 XHTML, tutorial, personal, help, index, form, 14 contact, feedback, list, links, frame, deitel" /> 15 16 <meta name = "description" content = "This Web site will 17 • help you learn the basics of XHTML and Webpage design 18 through the use of interactive examples 19 ' and instruction." /> 20 21 </head> 22 23 <!— Ter <frameset> задает размеры фрейма —> 24 <frameset cols = "110,*"> 25 26 <!— отдельные элементы фрейма указывают —> 27 <!— страницы для отображения в заданном фрейме —> 28 <frame name = "leftframe" src = "nav.html" /> 29 <frame name = "main" src = "main.html" /> 30
1018 Приложение 12 31 <noframes> 32 <p>This page uses frames, but your browser does not 33 support them.</p> 34 35 <p>Please, <a href = "nav.html">follow this link to 36 browse our site without frames</a>.</p> 37 </noframes> 38 39 </frameset> 40 </html> Результат представлен на рис. П12.8. Левый -фрейм leftframe фрейм Правый rightframe Рис. П12.8. Использование фреймов: а — исходное состояние; б— страница, на которую выполнен переход В большинстве предыдущих примеров авторы придерживались строгого типа документов XHTML. В листин- ге П12.9 применяется frameset — особый тип документа, предназначенный специально для наборов фреймов. Новый тип документа обозначен в строках 2—3; он необходим .пля документов, определяющих наборы фреймов.
Введение в XHTML: часть 2 1019 Как правило, документ, определяющий набор фреймов, состоит из элемента html, содержащего элементы head и frameset. Тег frameset (строка 24) "сообщает” браузеру о том, что страница содержит фреймы. Атрибут cols определяет макет столбцов набора фреймов. Значение атрибута cols представляет ширину каждого фрейма в пикселах или в процентном отношении от величины экрана. В данном случае атрибут cols="110" сообщает браузеру, что имеются два вертикальных фрейма. Первый „отстоит на 110 пикселов от левого края экрана, а вто- рой заполняет оставшуюся его часть (указано *). Аналогичным же образом атрибут rows элемента frameset оп- ределяет количество строк и размер каждой строки в наборе фреймов. Документ, который будет загружен в элемент frameset, определяется элементами frame (в данном примере 28 и 29). Атрибутом src задается URL страницы, которая будет отображена во фрейме. Каждый фрейм имеет ат- рибуты name и src. Первый фрейм (охватывающий НО пикселов в левой части элемента frameset) имеет имя left frame и отображает страницу nav.html (листинг П12.10). Второй фрейм — main— отображает страницу main.html. Атрибут name идентифицирует фрейм, и это позволяет гиперссылкам в элементе frameset задать целевой фрейм (target frame), в котором должен отобразиться связанный документ при выборе пользователем ссылки. Напри- мер, строкой <а href = "links.html" target = "main"> во фрейм с именем "main" загружается Документ links.html. Фреймы поддерживаются не всеми браузерами. В XHTML имеется элемент noframes (строки 31—37) для того, чтобы Web-дизайнеры могли предоставить альтернативное содержимое для браузеров, не поддерживающих фреймы. Совет по повышению переносимости_________________________________________________________ Некоторые браузеры не поддерживают фреймы. Для направления пользователя к версии сайта без фреймов рекомендуется применять элемент noframes в рамках элемента frameset. В листинге П12.10 представлена Web-страница, отображенная в левом фрейме листинга П12.9. В данном доку- менте XHTML имеются кнопки просмотра страниц, при нажатии которых определяется документ, отображае- мый в правом фрейме. ...... ..... .... ... тэдмдемшк Листинг П12 10. Документ XHTML, отображенный в левом фрейме листинга П12.У ''j 1 <?xml version = "1.0"?> 2 <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" 3 "http://www.w3.org/TR/xhtmll/DTD/xhtmll-transitional.dtd"> 4 5 <!— Листинг П12.10.: nav.html —> 6 <!— Использование изображений в качестве ссыпок —> 7 8 <html xmlns = "http://www.w3.org/1999/xhtml"> 9 10 <head> 11 <title>C# How to Program — Navigation Bar 12 </title> 13 </head> 14 15 <body> 16 17 <p> 18 <a href = "links.html" target = "main"> 19 <img src = "buttons/links.jpg" width = "65" 20 height = "50" alt = "Links Page" /> 21 </a><br /> 22 23 <a href = "list.html" target = "main"> 24 <img src = "buttons/list.jpg" width = "65" 25 height = "50” alt = "List Example Page" /> 26 </a><br /> 27
1020 ( Приложение 12 28 <а href = ’’contact.html" target = "main"> 29 <img src = "buttons/contact.jpg" width = "65" 30 height = "50" alt = "Contact Page" /> 31 </a><br /> 32 33 <a href = "header.html" target = "main"> 34 <img src = "buttons/header.jpg" width - "65" 35 height = "50" alt = "Header Page" /> 36 </axbr /> 37 38 <a href = "tablel.html" target = "main"> 39 <img src = "buttons/table.jpg" width = "65" 40 height = "50" alt = "Table Page" /> 41 </a><br /> 42 43 <a href = "form.html" target = "main"> 44 <img src = "buttons/form.jpg" width = "65" 45 height = "50" alt = "Feedback Form" /> 46 </axbr /> 47 ' </p> 48 49 </body> 50 </html> В строке 29 (см. листинг П12.9) указана страница XHTML, представленная в листинге П12.10. Якорный атрибут target (строка 18 листинга П12.10) указывает, что связанный документ загружается во.фрейм main (строка 30 листинга П 12.9). Элементу target можно задать несколько предварительных значений: "_blank” загружает страницу в новое окно браузера, "_self" загружает страницу во фрейм, в котором появляется якорный элемент, a "_top" загружает страницу в полное окно браузера (т. е. удаляет элемент frameset). П12.10. Вложенные теги <frameset> Элемент frameset можно использовать для создания более сложных макетов страниц на Web-сайте путем вло- жения элементов frameset, как показано в листинге П 12.11. Вложенный элемент frameset в данном примере отображает документы XHTML из листингов П12.7, П12.8 и П12.10. Внешний элемент frameset (строки 23—41) определяет два столбца. Левый фрейм отстоит на 110 пикселов от левого края окна браузера, а правый фрейм занимает оставшуюся часть Окна. Элемент frame в строке 24 указы- вает, что документ nav.html (листинг П12.10) будет отображен в левом столбце. В строках 28—31 определяется вложенный элемент frameset для второго столбца внешнего набора фреймов. Данный элемент frameset определяет две строки. Первая строка отстоит на 175 пикселов от верхней границы окна браузера, а вторая занимает оставшуюся часть высоты окна браузера, как указано значением rows="175, Элемент frame в строке 29 указывает, что в первой строке вложенного элемента frameset отобразится документ picture.html (см. листинг П12.7). Элемент frame в строке 30 указывает, что второй строкой вложенного элемента frameset отобразится документ main.html (см. листинг П12.8). Совет по тестированию и отладке____________________________________________________ При использовании вложенных элементов frameset каждый уровень тега <frame> следует вводить с отступом; при этом страница становится более понятной, и упрощается ее отладка. 1 <?xml version = "1.0"?> 2 <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Frameset//EN" 3 "http://www.w3.org/TR/xhtmll/DTD/xhtmll-frameset.dtd"> 4 5 <!— Листинг П12.11.: index2.html —> 6 <!— Фреймы XHTML (пример 2) —> 7 8 <html xmlns = "http://www.w3.org/1999/xhtml"> 9 <head> 10 <title>C# How to Program — Main</title>
Введение в XHTML: часть 2 1021 11 12 <meta name = "keywords" content = "Webpage, design, 13 XHTML, tutorial, personal, help, index, form, 14 contact, feedback, list, links, frame, deitel" /> 15 16 <meta name = "description" content = "This Web site will 17 help you learn the basics of XHTML and Webpage design 18 through the use of interactive examples 19 and instruction." /> 20 21 </head> 22 23 <frameset cols = "110,*"> 24 <frame name = "leftframe" src = "nav.html" /> 25 26 <!— вложенные наборы фреймов используются-для изменения —> 27 <!— форматирования и макетирования набора фреймов —> 28 <frameset rows = "175,*"> 29 <frame name = "picture" src “ "picture.html" /> 30 <frame name = "main" src = "main.html" /> 31 </frameset> 32 33 <noframes> 34 <p>This page uses frames, but your browser does not 35 support them.</p> 36 37 <p>Please, <a href = "nav.html">follow this link to 38 browse our site without frames</a>.</p> 39 </noframes> 40 41 </frameset> 42 </html> Результат представлен на рис. П12.9. Левый фрейм leftframe Рис. П12.9. Использование вложенных фреймов Правый фрейм содержит два вложенных фрейма
1022 Приложение 12 П12.11. Резюме i Таблицы XHTML размечают табличные данные и являются одной из наиболее часто используемых функций языка. Таблица XHTML определяется элементом table. Атрибутом border задается толщина рамок таблицы, измеряемая в пикселах. В таблицах без рамок значение данного атрибута равно 0. Элементом summary в общем виде описывается содержимое таблицы, и он используется электронными средствами синтеза речи для обеспе- чения доступности таблиц для пользователей с нарушениями зрения. Элементом caption описывается содер- жимое таблицы. В большинстве браузеров текст внутри тега <caption> отображается над таблицей. Таблицу можно разделить на три части: заголовок (thead), тело (tbody) и нижнюю часть (tfoot). В разделе заголовка содержатся заголовки таблицы и входящих в нее столбцов. В разделе тела размещена информация таблицы, а в разделе нижней части содержится второстепенная информация, например, сноски. Элементом tr (от англ, table row — строка таблицы) определяются отдельные строки таблицы. Элементом th определяется ячейка заголовка. Текст между элементами th обычно выравнивается по центру и в большинстве браузеров выделяется полужир- ным стилем. Данный элемент может присутствовать в любом разделе таблицы. Информация строки определяет- ся элементами td (от англ, table data — данные таблицы). С помощью элемента colgroup осуществляется груп- пирование и форматирование столбцов таблицы. Каждый элемент col может отформатировать любое количест- во столбцов (заданное атрибутом span). Автор документа имеет возможность объединения ячеек данных с помощью атрибутов rowspan и colspan. Значения, присвоенные этим атрибутам, задают количество строк или столбцов, занимаемых ячейкой. Данные атрибуты можно помещать в любой тег ячейки данных. Язык XHTML предоставляет формы для сбора информации от посетителей сайтов. В формах содержатся визу- альные компоненты, например, кнопки, которые нажимает пользователь, а также невизуальные компоненты, называемые скрытыми элементами ввода и используемые для сохранения любых данных — электронных адре- сов и имен файлов документов XHTML, выступающих в качестве ссылок. Форму открывает элемент form. Ат- рибутом method задается способ передачи данных формы на Web-сервер. С помощью элемента ввода "text" в форму вставляется текстовое поле. Текстовые поля предназначены для ввода данных. Атрибутом size элемента input задается количество символов, которые будут отображены в элементе input. Необязательным атрибутом maxlength ограничивается количество вводимых в текстовое поле символов. С помощью элемента "submit" данные, введенные в форму, передаются на Web-сервер для обработки. В большинстве Web-браузеров создает- ся кнопка, передающая данные при щелчке кнопкой мыши на форме. Элемент ввода "reset" дает пользователю возможность задания всем элементам form значений по умолчанию. С помощью элемента textarea осуществля- ется вставка в форму многострочного текстового поля, называемого текстовой областью. Количество строк в текстовой области задается атрибутом rows, а количество столбцов (т. е. символов) — атрибутом cols. С по- мощью элемента "password" в форму вставляется поле ввода пароля. В него пользователи вводят конфиденци- альную информацию, например, номера кредитных карт и пароли путем замены одних символов другими. В большинстве полей ввода паролей таким символом является *. На Web-сервер отправляется "открытая" (не закодированная) информация. Ввод набора флажков обеспечивает пользователя возможностью выбора опций из этого набора. При выборе флажок отмечается "галочкой"; если флажок не выбран, он остается пустым. Пункты набора можно использовать как по одному, так и группой. Пункты набора, принадлежащие одной группе, име- ют одно значение атрибута name. Переключатели похожи по своей функциональности на флажки, за исключени- ем того, что переключатели выбираются только по одному. Все переключатели в группе имеют одинаковое зна- чение атрибута name, но разные значения атрибута value. Элементом ввода select предоставляется раскрываю- щий список, атрибутом name которого он и идентифицируется. С помощью элемента option в раскрывающийся список добавляются новые пункты. Атрибутом selected, как и атрибутом checked для переключателей и флаж- ков, определяется пункт (позиция), отмеченный по умолчанию. В картах изображений в качестве ссылок размечены части графического изображения. Такие ссылки более точ- но характеризуются как "горячие точки". Карты изображений определяются элементами тар. Идентифицирует карту изображения атрибут id. "Горячие точки" определяются элементом area. Атрибутом href задается цель ссылки. Атрибутами shape и coord задаются форма и координаты "горячей точки" соответственно, а атрибут alt обеспечивает альтернативный текст. Одним из способов каталогизации Web-страниц поисковыми системами является считывание содержимого эле- мента meta. Двумя важными атрибутами элемента meta являются атрибут name, идентифицирующий тип элемен- та meta, и content, предоставляющий поисковым системам информацию для каталогизации Web-страниц. Фреймы обеспечивают одновременное отображение браузером нескольких документов XHTML. Элемент frameset "информирует" браузер о том, что страница содержит фреймы. Фреймы поддерживаются не всеми браузерами. Для обеспечения альтернативного содержимого для браузеров, не поддерживающих фреймы, в XHTML имеется элемент noframes.
Введение в XHTML: часть 2 « у 023 П12.12. Ресурсы Интернета и WWW □ courses.e-survey.net.au/xhtml/index.html На сайте Web Page Design XHTML представлены описания и примеры различных функций XHTML: ссы- лок, таблиц, фреймов и форм. Посетители имеют возможность задавать вопросы и направлять по электрон- ной почте свои комментарии обслуживающему персоналу Web Page Design. □ www.vbxml.com/xhtml/articles/xhtml_tables На сайте VBXML.com содержатся учебные материалы по созданию таблиц XHTML. □ www.webreference.com/xml/reference/xhtml.html На данной странице размещен список наиболее часто используемых тегов XHTML: тегов заголовков, таб- лиц, фреймов и форм, а также их описания. <
ПРИЛОЖЕНИЕ 13 Специальные символы HTML/XHTML В табл. П13.1 представлены специальные символы HTML/XHTML, называемые W3C ссылками на запись сим- волов. Полный список ссылок на запись символов приведен на Web-сайте www.w3.org/TR/REC-html40 /sgml/entities.html. Таблица П13.1. Специальные символы HTML/XHTML Символ Код HTML/XHTML Символ Код HTML/XHTML Неразрывный пробел &#1607 ё &#234; § &#167; &#236; © &#169{ ' . i &#237; &#174; I &#238; У« &#188; 0 &#241; у2 &#189; 6 &#242; % &#190; б &#243; а &#224; б &#244; а &#225; б &#245; & &#226; + &#247; а &#227; U &#249; & &#229; й &#250; &#231; й &#251; ё &#232; &#8226; ё &#233; тм &#8482;
ПРИЛОЖЕНИЕ 14 Цвета HTML/XHTML Цвета можно задавать использованием стандартного названия (например, aqua), либо шестнадцатеричного зна- чения RGB (например, #OOFFFF для цвета aqua). Из шести шестнадцатеричных цифр в значении RGB первые две обозначают объем красной составляющей в цвете, средние две цифры — объем зеленой составляющей, а по- следние две цифры — голубой. Например, black (черный) представляет собой отсутствие всех трех составляю- щих и имеет код #000000, тогда как white (белый) —- максимальный объем красного, зеленого и голубого ком- понентов — обозначается кодом #ffffff. Чистый красный цвет имеет код #ffoooo, чистый зеленый — (назы- ваемый lime)— #00FF00, а чистый голубой— #0000FF; Обратите внимание, что цвет green определен как #008000. В табл. П14.1 приведен стандартный набор цветов HTML/XHTML. В табл. П14.2 содержится расши- ренный набор цветов HTML/XHTML. Таблица П14.1. Стандартные цвета HTML/XHTML и их шестнадцатеричные значения RGB Название цвета Значение Название цвета Значение aqua (цвет морской волны) I00FFFF navy (темно-синий) #000080 black (черный) #000000 olive (оливковый) #808000 blue (синий) IOOOOFF purple (темно-фиолетовый) #800080 fuchsia (фуксия) IFF00FF red (красный) #FF0000 gray (серый) #808080 silver (серебряный) #сососо green (зеленый) #008000 teal (песчаный) #008080 lime (ярко-лаймовый1) #00FF00 yellow (желтый) #FFFF00 maroon (красно-коричневый) #800000 white (белый) #FFFFFF Таблица П14.2. Расширенные цвета HTML/XHTML и их шестнадцатеричные значения RGB Название цвета Значение Название цвета Значение aliceblue #F0F8FF cadetblue #5F9EA0 antiquewhite #FAEBD7 chartreuse #7FFF00 aquamarine #7FFFD4 chocolate #D2691E х azure #F0FFFF coral #FF7F50 beige #F5F5DC cornflowerblue #6495ED bisque #FFE4C4 cornsilk #FFF8DC blanchedalmond #FFEBCD crimson #DC1436 blueviolet #8A2BE2 cyan #00FFFF brown #A52A2A darkblue #00008B burlywood #DEB887 darkcyan #008B8B 1 Цвет зеленого лимона. — Ред. 65 Зак. 3333
1026 Приложение 14 Таблица П14.2 (продолжение) Название цвета Значение Название цвета Значение darkgray #A9A9A9 lightsalmon #FFA07A darkgreen #006460 lightseagreen #20B2AA darkkhaki #BDB76B lightskyblue #87CEFA darkmagenta #8B008B lightslategray #778899 darkoldenrod #B8860B lightsteelblue #B0C4DE darkolivegreen #556B2F lightyellow #FFFFE0 darkorange #FF8C00 limegreen #32CD32 darkorchid #9932CC mediumaquamarine #66CDAA darkred #8B0000 mediumblue #0000CD darksalmon #E9967A mediumorchid #BA55D3 darkseagreen #8FBC8F mediumpurple #9370DB darkslateblue #483D8B mediumseagreen #3CB371 darkslategray #2F4F4F mediumslateblue #7B68EE darkturquoise #00CEDl mediumspringgreen #00FA9A darkviolet #9400D3 mediumturquoise #48D1CC - deeppink #FF1493 mediumvioletred #C71585 deepskyblue #00BFFF midnightblue #191970 dimgray #696969 mintcream #F5FFFA dodgerblue #1E9OFF mistyrose #FFE4E1 firebrick #B22222 moccasin #FFE4B5 floralwhite #FFFAF0 navajowhite #FFDEAD forestgreen «228В22 oidlace #FDF5E6 gainsboro #DCDCDC olivedrab #6B8E23 ghostwhite #F8F8FF orandered #FF4500 gold #FFD700 orange #FFA500 goldenrod #DAA520 orchid palegoldenrod #DA70D6 greenyellow #ADFF2F #EEE8AA honeydew #F0FFF0 palegreen #98FB98 hotpink #FF69B4 paleturquoise #AFEEEE indianred #CD5C5C palevioletred #DB7093 indigo #4B0082 papayawhip #FFEFD5 ivory #FFFFF0 peachpuff #FFDAB9 khaki #F0E68C peru #CD853F lavender #E6E6FA pink #FFC0CB lavenderblush #FFF0F5 plum #DDA0DD lawngreen #7CFC00 powderblue #B0E0E6 lemonchiffon #FFFACD rosybrown #DC8F8F lightblue #ADD8E6 royalblue #4169E1 lightcoral #F08080 saddlebrown #884513 lightcyan #E0FFFF salmon #FA8072 lightgoldenrodyellow #FAFAD2 sandybrown #F4A460 lightgreen *“ #90EE90 seagreen #2E8B57 lightgrey #D3D3D3 x seashell #FFF5EE lightpink #FFB6C1 sienna #A0522D
Цвета HTML/XHTML 1027 Таблица П14.2 (окончание) Название цвета Значение Название цвета Значение skyblue #87CEEB thistle #D8BFD8 slateblue #6A5ACD tomato #FF6347 slategray #708090 turquoise #40E0D0 snow #FFFAFA <; violet #EE82EE springgreen #00FF7F wheat #F5DEB3 steelblue #4682B4 whitesmoke #F5F5F5 tan / #D2B48C yellowgreen #9ACD32
ПРИЛОЖЕНИЕ 15 (и Поразрядные операции Темы данного приложения: □ концепция поразрядных операций; □ использование побитовых операций; □ использование класса BitArray для выполнения поразрядных операций. П15.1. Введение В данном приложении подробно рассматриваются действия над разрядами, выполняемые с помощью побито- вых операций. Также представлен класс BitArray, из которого создаются объекты, необходимые для манипуля- ций наборами битов. П15.2. Побитовые операции В C# представлены расширенные возможности выполнения поразрядных операций для программистов, рабо- тающих на уровне "битов и байтов". Операционные системы, программные средства тестирования, организации сетей и многие другие виды программного обеспечения требуют от разработчиков "непосредственного взаимо- действия с аппаратными средствами". В данном и следующем разделах рассматриваются возможности побито- вых операций в С#. После представления побитовых операций их использование будет продемонстрировано на примерах. Компьютеры представляют данные в виде последовательности битов. Арифметико-логические устройства (Arithmetic Logic Units, ALU), центральные процессоры (Central Processor Unit, CPU) и другие компоненты ком- пьютеров обрабатывают информацию в виде битов или групп битов. Каждый бит принимает значение либо О, либо 1. На всех системах последовательность из 8 битов образует байт — стандартную единицу сохранения для переменной типа byte. Другие типы данных требуют для сохранения информации больше количества байтов. Побитовые операции выполняют действия с битами вычисляемых операндов (т. е. sbyte, byte, char, short, ushort, int, uint, long И ulong). Обратите внимание, что обсуждение в данном разделе побитовых операций иллюстрирует двоичные представ- ления целочисленных операндов. Подробное описание двоичной системы счисления (также называемой "систе- мой с основанием 2") представлено в приложении 2. Побитовые операции "И" (&), "ИЛИ" (|) и исключающего "ИЛИ" (л) функционируют аналогично их условным эквивалентам, но поразрядные версии работают на уровне битов (табл. П15.1). Поразрядная операция "И" (&) задает каждому биту результата значение 1, если соответствующие биты в обоих операндах имеют значение 1 (табл. П 15.2). Поразрядная операция "ИЛИ" (|) задает каждому биту результата значение 1, если соответст- вующие биты в любом (или в обоих) операндах также имеют значение 1 (табл. П15.3). Поразрядная операция исключающего "ИЛИ" (л) задает каждому биту результата значение 1, если соответствующий бит только в од- ном операнде имеет значение 1 (табл. П15.4). С помощью операции сдвига влево («) биты левого операнда сдвигаются влево на количество битов, указанное в правом операнде. С помощью операции сдвига вправо (») биты левого операнда сдвигаются вправо на коли- чество битов, указанное в правом операнде. Если левый операнд имеет отрицательное значение, тогда единицы двигаются слева, а если левый операнд имеет положительное значение, тогда слева сдвигаются нули. Поразряд- ная операция дополнения (~) задает всем битам результата со значением 0 в операнде значение 1, а всем битам со значением 1 — значение 0; данный процесс иногда называется дополнением значения до единицы. Подробно каждая побитовая операция описывается в примерах, приводимых далее.
Поразрядные операции 1029 Таблица П15.1. Побитовые операции Операция Название Описание & Побитовое "И" (AND) Каждому биту результата присваивается значение 1, если соответст- вующие биты в двух операндах имеют значение 1; в противном случае, задается значение 0 1 Поразрядное "ИЛИ" (OR) Каждому биту результата задается значение 1, если хотя бы один из соответствующих битов в двух операндах имеет значение 1; в противном случае, задается значение 0 ' Поразрядное исключающее "ИЛИ" (XOR) Каждому биту результата задается значение 1, если только один из со- ответствующих битов в двух операндах имеет значение 1; в противном случае, задается значение 0 « Сдвиг влево Сдвиг битов первого операнда влево на количество битов, заданное вторым операндом; справа вводятся биты со значением 0 Сдвиг вправо Сдвиг битов первого операнда вправо на количество битов, заданное вторым операндом. Если первый операнд имеет отрицательное значе- ние, тогда слева сдвигаются единицы; в противном случае, слева сдви- гаются нули Дополнение Всем битам со значением 0 присваивается значение 1, а битам со зна- чением 1 — значение 0 Таблица П15.2. Результаты объединения двух битов с помощью операции побитового "И" Бит 1 Бит 2 Бит 1 & Бит 2 0 0 0 1 0 0 0 1 0 1 1 1 Таблица П15.3. Результаты объединения двух битов с помощью операции побитового "ИЛИ" Бит 1 Бит 2 Бит 1 | Бит 2 0 0 0 1 0 1 0 1 1 1 1 1 Таблица П15.4. Результаты объединения двух битов с помощью операции побитового исключающего "ИЛИ" Бит 1 Бит 2 Бит 1 А Бит 2 . 0 i 0 0 0 1 0 1 1 1 1 0 При использовании побитовых операций имеет смысл отображать значения в двоичной форме для наглядной демонстрации действий этих операций. В листинге П15.1 целые числа представлены в двоичной форме в виде групп с восьмью битами в каждой. Метод GetBits (строки 67—91) класса PrintBits использует побитовую операцию "ИЛИ" (строка 79) для объединения переменной number с переменной displayMask. Побитовая опера-
1030 Приложение 15 ция "И" часто используется с операндом маски— целочисленным значением, определенные биты которого равны 1. Маски скрывают некоторые биты значения и выбирают другие биты. Метод GetBits присваивает пе- ременной маски dispiayMask значение 1«31 (юоооооо оооооооо оооооооо оооооооо). Операция сдвига влево сдвигает значение 1 от младшего бита (крайнего справа) в старший бит (крайний слева) в переменной dispiayMask и заполняет правую часть битами со значением 0. Из-за того, что значение второго операнда рав- но 31, правая часть заполняется 31 битами (со значением 0). Слово "заполнять" в данном контексте означает, что справа один бит добавляется, а слева — удаляется. Всякий раз при добавлении нуля справа один бит слева удаляется. В операторе в строке 79 определяется, бит с каким значением (1 или 0) должен быть добавлен в вывод StringBuilder для крайнего слева бита переменной number. Для данного примера предположим, что переменная number содержит 11111 (00000000 00000000 00101011 01100111). При объединении переменных number и dispiayMask с помощью операции & все биты, за исключением старшего бита в переменной number, будут "за- маскированы" (скрыты), потому что любой бит, "прибавленный" (с помощью операции "И") к 0, дает нулевой результат. Если крайний слева бит имеет значение 0, тогда выражение number & dispiayMask вычисляет 0, и добавляется 0; в противном случае добавляется значение 1. (Данное выражение эквивалентно number=number«l.) Эти шаги повторяются для каждого бита в переменной number. В конце метода GetBits в строке 89 StringBuilder преобразуется в возвращаемую методом строку. 1 // Листинг П15.1: PrintBits.cs 2 // Печать битов, составляющих целое число 3 4 using System; 5 using System.Drawing; 6 using System.Collections; 7 using System.ComponentModel; 8 using System.Windows.Forms; 9 using System.Data; 10 using System.Text; 11 12 // отображение битового представления данных, 13 // введенных пользователем 14 public class PrintBits : System.Windows.Forms.Form 15 { 16 private System.Windows.Forms.Label promptLabel; 17 private System.Windows.Forms.Label viewLabel; 18 19 // для данных, вводимых пользователем 20 private System.Windows.Forms.TextBox inputTextBox; 21 22 // отображенное битовое представление 23 private System.Windows.Forms.Label displayLabel; 24 25 private System.ComponentModel.Container components = null; 26 27 // конструктор по умолчанию 28 public PrintBits() 29 { 30 InitializeComponent(); 31 } 32 33 // код, сгенерированный Visual Studio .NET 34 35 [STAThread] 36 static void Main() 37 { 38 Application.Run(new PrintBits()); 39 } 40
Поразрядные операции 1031 41 // обработка целого числа при нажатии клавиши <Enter> 42 private void inputTextBox_KeyPown( 43 object sender, System.Windows.Forms.KeyEventArgs e) 44 { 45 // при нажатии пользователем клавиши <Enter> 46 if (e.KeyCode = Keys.Enter) 47 { 48 // проверить, целое ли число введено пользователем 49 try 50 { 51 displayLabel.Text = GetBits( 52 Convert.Tolnt32(inputTextBox. Text)) ; 53 } 54 55 // если введенное значение — не целое, то выдается исключение 56 catch (FormatException) 57 { 58 MessageBox.Show("Please Enter an Integer", 59 "Error", MessageBoxButtons.OK, 60 MessageBoxIcon.Error); 61 } 62 } 63 64 } // окончание метода inputTextBox_KeyDown 65 66 // преобразование целого числа в битовое представление 67 public string GetBits(int number) 68 { 69 int displayMask = 1 « 31; 70 71 StringBuilder output = new StringBuilder(); 72 73 // получение каждого бита, ввод пробела через каждые 74 //8 битов для отображения форматирования 75 for (int с = 1; с <= 32; C++) 76 { 77 // добавление 0 или 1, в зависимости от результата 77а // маскирования 7 8 output. Append ( 79 (number & displayMask) == 0 ? "0" : "1"); 80 81 // сдвиг влево так, что маска обнаружит бит 82 // следующей цифры во время очередной итерации цикла 83 number «= 1; 84 85 if (с % 8 = 0) 86 output.Append{" "); 87 } 88 89 return output.ToString(); 90 Рис. П15.1. Демонстрация 91 )// окончание метода GetBits битового представления целого числа 92 93 } // окончание класса PrintBits Результат работы программы представлен на рис. П15.1. Распространенная ошибка программирования________________________________________________ Использование условной операции "И" (&&) вместо побитовой операции "И" (&) является распространенной ошибкой. Распространенная ошибка программирования_____________________________________________ Использование условной операции "ИЛИ" (11) вместо побитовой операции "ИЛИ" (|) является распространенной ошибкой.
1032 Приложение 15 В программе, представленной в листинге П 15.2, демонстрируются побитовые операции "И", "ИЛИ”, исклю- чающего "ИЛИ" и дополнения. В программе используется метод GetBits, возвращающий строку, содержащую битовое представление целочисленного аргумента. Пользователь вводит значения в текстовые поля и нажимает кнопку, соответствующую операции, которую необходимо проверить. Программа выдает результат как в цело- численном, так и битовом представлении. 1 // Листинг П15.2: Bitoperations.cs 2 // Класс, демонстрирующий различные операции с битами 3 4 using System; 5 using System.Drawing; 6 using System.Collections; 7 using System.ComponentModel; 8 using System.Windows.Forms; 9 using System.Data; 10 using System.Text; 11 12 // пользователь может тестировать битовые операции 13 public class Bitoperations : System.Windows.Forms.Form 14 { 15 private System.Windows.Forms.Label promptLabel; 16 private System.Windows.Forms.Label representationLabel; 17 private System.Windows.Forms.Label valuelLabel; 18 private System.Windows.Forms.Label value2Label; 19 private System.Windows.Forms.Label resuitLabei; 20 21 // отображение битового представления 22 private System.Windows.Forms.Label bitlLabel; 23 private System.Windows.Forms.Label bit2Label; 24 private System.Windows.Forms.Label resultBitLabel^ 25 26 // пользователь может выполнять операции с битами 27 private System.Windows.Forms.Button andButton; 28 private System.Windows.Forms.Button inclusiveOrButton; 29 private System.Windows.Forms.Button exclusiveOrButton; 30 private System.Windows.Forms.Button complementButton; 31 32 // пользователь вводит два целых числа 33 private System.Windows.Forms.TextBox bitlTextBox; 34 private System.Windows.Forms.TextBox bit2TextBox; 35 36 private System.Windows.Forms.TextBox resultTextBox; 37 38 private int valuel, value2; 39 40 private System.ComponentModel.Container components = null; 41 42 // конструктор по умолчанию 43 public Bitoperations() 44 { 45 InitializeComponent(); 46 } 47 48 // код, сгенерированный Visual Studio .NET 49 50 [STAThread] 51 static void Main() 52 { 53 Application.Run(new Bitoperations()); 54 } 55
Поразрядные операции 1033 56 57 58 " 59 60 61 62 63 64 65 ёб 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 8-2 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 116а 117 118 // AND private void andButton_Click( _ object sender, System.EventArgs e) { SetFields(); // обновление поля resultTextBox resultTextBox.Text = string.Format("{0}", valuel & value2); resultBitLabel.Text = GetBits(valuel & value2); } // OR private void inclusiveOrButton_Click( object sender, System.EventArgs e) { SetFields(); // обновление поля resultTextBox resultTextBox.Text = string.Format("{0}", valuel I value2); resultBitLabel.Text = GetBits(valuel I value2); // XOR private void exclusiveOrButton_Click( object sender, System.EventArgs e) { SetFields() ; // обновление поля resultTextBox resultTextBox.Text = string.Format("(0}", valuel Л value2); resultBitLabel.Text = GetBits(valuel л value2); ) // дополнение первого целого числа private void complementBntton_Click( object sender, System.EventArgs e) { valuel = Convert .Tolnt32(bitITextBox.Text); bitlLabel.Text = GetBits(valuel); W* // обновление поля resultTextBox resultTextBox.Text = string.Format("{0}", -valuel); resultBitLabel.Text = GetBits(-valuel); } // преобразование целого числа в битовое представление private string GetBits(int number) { int dispiayMask = 1 « 31; StringBuilder output = new StringBuilder(); // получение каждого бита, ввод пробела через каждые 8 битов // для отображения форматирования for (int с » 1; с <= 32; C++) { // добавление 0 или 1, в зависимости от результата // маскирования output.Append( (number & dispiayMask) == 0 ? "0" : "1");
1034 Приложение 15 119 120 // сдвиг влево так, что маска обнаружит бит следующей цифры 121 //в очередной итерации цикла 122 number «= 1; 123 124 if (с % 8 == 0) 125 output.Append(" "); 126 } 127 128 return output.ToString(); 129 130 } // окончание метода GetBits 131 132 // задание полей формы 133 private void SetFieldsf) 134 { 135 // извлечение значений ввода 136 valuel = Convert.Tolnt32(bitITextBox.Text); 137 value2 = Convert.Tolnt32(bit2TextBox.Text); 138 139 // задание меток для отображения битового представления 139а // целых чисел 140 bitlLabel.Text = GetBits(valuel); 141 bit2Label.Text = GetBits(value2); 142 } 143 144 } // окончание класса Bitoperations На рис. П 15.2, а представлены результаты объединения значений 17 и 20 с помощью побитовой операции "И" (&); результат— 16. На рис. П 15.2, б показаны результаты объединения значений 17 и 20 с помощью побитовой операции "ИЛИ" (|); результат— 21. На рис. П 15.2, в продемонстрированы результаты объединения значений 17 и 20 с помощью операции исключающего "ИЛИ" (л); результат— 5. А на рис. П15.2, г представлены ре- зультаты дополнения до единицы значения 17. Результат равен -18. а б в Рис. П15.2. Демонстрация побитовых операций: а — "И"; б — "ИЛИ"; в — исключающее "ИЛИ"; г — дополнение до единицы В программе листинга ГН5.3 продемонстрировано использование операций сдвига влево («) и сдвига вправо (»). Метод GetBits возвращает строку, содержащую битовое представление целочисленного значения, пере-
Поразрядные операции 1035 данного ему в качестве аргумента. При вводе пользователями в текстовое поле целого числа и нажатия клавиши <Enter> программа отображает в классе Label битовое представление указанного целого числа. 1 // Листинг П15.3: BitShift.cs 2 // Демонстрация операций сдвига разрядов 3 4 using System; 5 using System.Drawing; 6 using System.Collections; 7 using System.CornponentModel; 8 using System.Windows.Forms; 9 using System.Data; 10 using System. Text; 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 -28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50. 51 52 53 54 55 56 57 58 59 // сдвиг битов вправо или влево public class Bitshift : System.Windows.Forms.Form { private System.Windows.Forms.Label inputLabel; // принятие введенных пользователем данных private System.Windows.Forms.TextBox inputTextBox 11 отображение целого числа в битах private System.Windows.Forms.Label displayLabel; private System.Windows.Forms.Button rightButton; private System.Windows.Forms.Button leftButton; private System.ComponentModel.Container components = null; // конструктор по умолчанию public BitShift() { InitializeComponent(); } 11 код, сгенерированный Visual Studio .NET [STAThread} static void Main() { Appliation.Run(new BitShift()); 11 обработка введенных пользователем данных private void inputTextBox_KeyDown( object sender, System.Windows.Forms.KeyEventArgs e) { if (e.KeyCode == Keys.Enter) displayLabel.Text = GetBits(Convert.Tolnt32(inputTextBox.Text)); ) // выполнение сдвига влево private void leftButton_Click( object sender. System.EventArgs e) ( 11 извлечение выведенных пользователем данных int number = Convert.Tolnt32(inputTextBox.Text); 11 выполнение сдвига влево number «= 1;
1036 Приложение 15 60 // преобразование в целое число и отображение в текстовом поле 61 inputTextBox.Text = number.Tostring(); 62 63 // отображение битов в метке 64 . displayLabel.Text = GetBits(number); 65 } (56 67 // выполнение сдвига вправо 68 private void rightButton_Click( 69 object sender, System.EventArgs e) 70 { 71 // извлечение выведенных пользователем данных 72 int number = Convert.Tolnt32(inputTextBox.Text); 73 74 // выполнение сдвига вправо 75 number »= 1; 76 77 // преобразование в целое число и отображение в текстовом поле 78 inputTextBox.Text = number.ToString(); 79 80 // отображение битов в метке 81 displayLabel.Text = GetBits(number); 82 } 83 84 // преобразование целого числа в битовое представление 85 private string GetBits(int number) 86 { 87 int dispiayMask = 1 << 31; 88 89 StringBuilder output = new StringBuilder(); 90 91 // получение каждого бита, ввод пробела через каждые 92 //8 битов для отображения форматирования 93 for (int с = 1; с <= 32; C++) ' 94 { 95 // прибавления 0 или 1, в зависимости от результата 96а // маскирования 96 output.Append( 97 (number & dispiayMask) ==* 0? "0" : "1"); 98 99 // сдвиг влево так, что маска обнаружит бит 100 // следующей цифры во время очередной итерации цикла 101 number «= 1; 102 103 if (с % 8 == 0) 104 output.Append(" "); 105 } 106 107 return output.ToString(); 108 109 } // окончание метода GetBits 110 111 } // окончание класса BitShift / Каждая операция сдвига имеет в форме приложения предназначенную для нее кнопку. При нажатии любой кнопки биты целого числа смещаются на один бит влево или вправо. Текстовые поля и метки отображают новое значение целого числа и новое битовое представление соответственно. Операция сдвига влево («) смещает битБГлевого операнда влево на количество битов, указанное в правом опе- ранде. Крайние биты справа заменяются нулями; единицы, сдвинутые слева, теряются. На рис. П15.3' а и П15.3, б показан оператор сдвига влево. Для создания выходных данных пользователь ввел значение 23 и нажал кнопку «; в результате получено значение 46.
Поразрядные операции 1037 Операция сдвига вправо (») смещает биты левого операнда вправо на количество битов, указанных в его пра- вом операнде. Нули заполняют освобождающиеся биты слева, если число положительное; если число отрица- тельное, то освобождающиеся биты заполняют единицы. Любое значение 1, сдвинутое справа, теряется. На рис. П15.3, в и П15.3, г показан результат однократного вмещения числа 184 вправо. в Рис. П15.3. Операции сдвига: а — сдвиг вправо для числа 23; б— результат сдвига вправо; в — сдвиг влево для числа 184; а — результат сдвига влево Каждая побитовая операция (за исключением побитовой операции дополнения) имеет соответствующую опера- цию присваивания. В табл. П15.5 описаны побитовые операции присваивания, используемые так же, как и ма- тематические операции, представленные в главе 3. Таблица П15.5. Побитовые операции присваивания Операция Описание &= Побитовый оператор присвоения AND 1 = Побитовый оператор присвоения включающего OR л= Побитовый оператор присвоения исключающего OR «= Оператор присвоения сдвига влево »= Оператор присвоения сдвига вправо П15.3. Класс BitArray Класс BitArray облегчает создание наборов битов, используемых программистами для обозначения набора бу- левых признаков, и манипуляции ими. Булев признак — это переменная, отслеживающая определенное булево решение. Изменять размер класса BitArray можно динамически: после создания объекта BitArray в него можно добавлять биты, и объект будет расширяться для принятия новых битов. В классе BitArray представлено несколько конструкторов, один из которых в качестве аргумента принимает переменную int. Эта переменная обозначает количество битов, представляемых классом BitArray, каждый из которых изначально имеет значение false. С помощью метода Set класса BitArray можно изменять значения отдельных битов; метод принимает указатель изменяемого бита и новое значение bool. В класс BitArray также входит указатель, позволяющий получать и задавать значения отдельным битам. Этот указатель возвращает значение true, если некоторый бит "включен" (т. е. имеет значение 1), и значение false, если бит имеет значение 0 или "отключен". Метод And класса BitArray выполняет побитовую операцию "И" между двумя массивами BitArray и возвращает результат BitArray операции. Методы Or и Хог выполняют побитовые операции "ИЛИ" и исключающего "ИЛИ" соответственно. В классе BitArray также представлено свойство Length, возвращающее количество элементов в классе BitArray. В листинге П15.4 представлено решето Эратосфена— алгоритм поиска простых чисел. Простым называется целое число, делимое без остатка на самое себя и на единицу.
1038 Приложение 15 Решето Эратосфена работает следующим образом. Создайте массив, в котором все элементы имеют значение 1 (true). Элементы массива с простыми индекса- ми сохраняют значение 1. Остальным элементам массива задается значение 0. Начиная с индекса массива 2 (индекс 1 не должен быть простым), всякий раз при обнаружении элемента массива со значением 1, через оставшуюся часть массива проходит цикл с присвоением значения 0 каждому элементу, индекс которого является кратным индексу элемента со значением 1. Например, для индекса мас- сива 2 всем элементам после 2, кратным 2, задается значение 0 (индексы 4, 6, 8, 10 и т. д.); для индекса мас- сива 3 всем элементам после 3, кратным 3, задается значение 0 (индексы 6, 9, 12,15) и т. д. В конце процесса индексы элементов массива, имеющие значение 1, являются простыми числами. После этого список простых чисел можно отобразить обнаружением местоположения индексов и их распечаткой. 1 // Листинг П15.4: BitArrayTest.cs 2 ft Демонстрация класса BitArray 3 4 using System; 5 using System.Drawing; 6 using System.Collections; 7 using System.CornponentModel; 8 using System.Windows.Forms; 9 using System.Data; 10 11 // реализация решета Эратосфена 12 public class BitArrayTest : System.Windows.Forms.Form 13 { 14 private System.Windows.Forms.Label prqmptLabel; 15 16 // целое число, введенное пользователем 17 private System.Windows.Forms.TextBox inputTextBox; 18 19 // отображение простых чисел 20 private System.Windows.Forms.TextBox outputTextВеж; 21 22 // является ли введенное целое число простым 23 private System.Windows.Forms.Label displayLabel; 24 25 „ private BitArray sieve; 26 27 private System.ComponentModel.Container components = null; 28 29 . // конструктор по умолчанию 30 public BitArrayTest() 31 { 32 InitializeComponent() ; 33 34 // создание BitArray и задания всем битам значения true 35 sieve = new BitArray(1024); 36 sieve.SetAll(true); 37 38 int finalBit = (int) Math..Sqrt (sieve.Length) ; 39 40 11 выполнение операции решета 41 for (int i = 2; i < finalBit; i++) 42 if (sieve.Get(i)) 43 for (int j = 2 * i; j < sieve.Length; j += i) 44 sieve.Set(j, false); 45
Поразрядные операции 1039 46 int counter = 0; 47 48 // отображение простых чисел 49 for (int i = 2; i < sieve.Length;" i++) 50 if (sieve.Get(i)) 51 outputTextBox.Text += i + 52 (++counter % 7 == 0 ? "\r\n" : " ”); 53 } 54 55 // код, сгенерированный Visual Studio .NET 56 57 [STAThread] 58 static void Main() 59 { 60 Application.Run(new BitArrayTest()); 61 } 62 63 private void input*fextBox_KeyDown ( 64 object sender, System.Windows.Forms.KeyEventArgs e) 65 { 66 // при нажатии пользователем клавиши <Enter> 67 if (e.KeyCode == Keys.Enter) 68 { 69 int number = Convert.Tolnt32(inputTextBdx.Text); 70 71 // если решето имеет значение true в указателе введенного 72 // пользователем целого числа, тогда это число простое 73 if (sieve.Get(number)) 74 displayLabel.Text = number + " is a prime number”; 75 else » 76 displayLabel.Text - 77 number + " is not a prime number"; 78 } 79 } // окончание метода inputTextBox_KeyDown 80 81 } // окончание класса BitArrayTest Рис. П15.4. Решето Эратосфена Результат выполнения программы представлен на рис. П15.4. Для реализации данного алгоритма используется класс BitArray. Программа отображает в текстовом поле про- стые числа в диапазоне от 1 до 1023. В программе также имеется текстовое поле для ввода пользователем любо- го числа от 1 до 1023 для определения, является ли оно простым (в этом случае выводится сообщение с указа- нием, что число — простое). Оператор в строке 35 создает класс BitArray, состоящий из 1024 битов. Методом SetAll класса BitArray в строке 36 всем битам присваивается значение true; затем в строках 41—44 определяются все простые числа, появляющиеся в диапазоне от 1 до 1023. Целое число finalBit задает завершение алгоритма. При вводе пользователем числа и нажатии клавиши <Enter> в строке 73 проверяется введенное число (простое оно или нет). В данной строке используется метод Get класса BitArray, принимающий это число и возвращаю- щий в массив значение данного бита. В строках 74 и 76 распечатывается соответствующий ответ. П15.4. Резюме Компьютеры представляют данные в виде последовательности битов. Каждый бит принимает значение либо 0, либо 1. На всех системах последовательность из 8 битов образует байт — стандартную единицу сохранения для переменной типа byte. Другие типы данных требуют для сохранения информации больше количества байтов. Поразрядная операция "И" (AND) задает каждому биту результата значение 1, если соответствующие биты в обоих операндах имеют значение 1. Поразрядная операция "ИЛИ" (OR) задает каждому биту результата значе- ние 1, если соответствующие биты хотя бы в одном из операндов имеют значение 1. Поразрядная операция ис- ключающего "ИЛИ" (XOR) задает каждому биту результата значение 1, если соответствующий бит только в одном операнде имеет значение 1.
1040 Приложение 15 С помощью операции сдвига влево («) биты левого операнда сдвигаются влево на количество битов, указанное в правом операнде. С помощью операции сдвига вправо (») биты левого операнда сдвигаются вправо на коли- чество битов, указанное в правом операнде. Если левый операнд имеет отрицательное значение, тогда единицы двигаются слева, а если левый операнд имеет положительное значение, тогда слева сдвигаются н>ли. Поразряд- ная операция дополнения (~) задает всем битам результата со значением 0 в операнде значение 1, а всем битам со значением 1 — значение 0; данный процесс иногда называется дополнением значения до единицы. Побито- вая операция "И" часто используется с операндом маски — целочисленным значением, определенные биты ко- торого равны 1. Маски скрывают некоторые биты значения и выбирают другие биты. Каждая побитовая опера- ция (за исключением побитовой операции дополнения) имеет соответствующую операцию присваивания. Класс BitArray облегчает создание наборов битов, используемых программистами для обозначения набора бу- левых признаков, и манипуляции ими. Булев признак — это переменная, отслеживающая определенное булево решение. Изменять размер класса BitArray можно динамически: после создания объекта BitArray в него можно добавлять биты, и объект будет расширяться для принятия новых битов. С помощью метода Set класса BitArray можно изменять значения отдельных битов; метод принимает указатель изменяемого бита и новое значение bool, на которое должен измениться бит. Метод And класса BitArray выполняет побитовую операцию "И” меж- ду двумя массивами BitArray и возвращает результат BitArray выполнения операции. Методы Or и Хог выпол- няют побитовые операции "ИЛИ" и исключающего "ИЛИ" соответственно. Методом SetAll класса BitArray всем битам присваивается значение true.
ПРИЛОЖЕНИЕ 16 Crystal Reports для Visual Studio .NET П16.1. Введение Сбор и поддержание необходимых данных осуществляются во всех производственных областях. Например, в компаниях-производителях ведется учет складских запасов товаров и объемов производства, на предприятиях розничной торговли документируются объемы продаж, в организациях здравоохранения регистрируется каж- дый пациент, а издательства отслеживают количества продаваемых книг и их наличие на складах. Однако про- стого сохранения данных недостаточно; руководители должны ими пользоваться для принятия компетентных решений. Информация должна быть четко структурированной и легкодоступной для отдельных лиц, отделов и филиалов. При этом упрощается анализ данных, в ходе которого может обнаружиться чрезвычайно важная стратегическая информация, например, тенденции будущего спроса или потенциальный дефицит запасов про- дукции. С этой целью разработчики пользуются программными средствами отчетности — основным инстру- ментом, обеспечивающим представление источников сохраненных данных. Впервые пакет Crystal Reports появился на рынке в 1992 году в виде генератора отчетов на базе Windows, а в 1993 году корпорация Microsoft приняла Crystal Reports в качестве стандарта для Visual Basic1. В настоящее время в Visual Studio .NET интегрирован специальный выпуск программного пакета Crystal Reports с целью его совместимости со всеми языками программирования .NET, включая С#, а также для разработки Windows- и Web-программ. В данном приложении описаны ресурсы, предлагаемые на сайте Crystal Decisions — компании- производителя системы Crystal Reports, а также представлен обзор уникальных функций Crystal Reports в Visual Studio .NET. П16.2. Ресурсы Web-сайта Crystal Reports Компания Crystal Decisions предлагает разработчикам Web-сайтов в Visual Studio .NET ресурс www.crystaldecisions.com/net. На сайте представлены обновления версий Visual Studio .NET на английском, упрощенном китайском, классическом китайском, французском, немецком, итальянском, японском, корейском и испанском языках. Crystal Decisions также обеспечивает техническую поддержку разработчикам Crystal Reports на C# по телефону или электронной почте. На сайте имеются материалы критического анализа, интерак- тивный информационный бюллетень, мультимедийная демонстрационная версия продукта, раздел для разра- ботчиков, а также общий обзор пакета Crystal Reports в Visual Studio .NET. П16.3. Crystal Reports и Visual Studio .NET С помощью программы Crystal Reports программисты, работающие в интегрированной среде разработки Visual Studio .NET, могут создавать и внедрять отчеты в программные приложения. Выпуск Crystal Reports для Visual Studio .NET обеспечивает разработчиков невероятными возможностями. В число функций пакета Crystal Reports для Visual Studio .NET входит API (Application Program Interface, программный интерфейс приложения), обеспечивающий контроль со стороны программистов за сохранением отчетов на серверах: настройку истече- ния сроков хранения, ограничений и т. д. Отчеты можно создавать на многих языках, потому что Crystal Reports полностью поддерживает кодировку Unicode. Пользователь может преобразовать отчет в формат Microsoft Word, PDF (Portable Document Format, мобильный формат документов) от компании Adobe, гипертекстовый язык разметки (HTML) и др. так, что информацию отчета можно легко распространять и считывать в докумен- 1 См. www.crystaldecisions.com/about/ourcompany/history.asp. 66 Зак. 3333
1042 Приложение 16 тах практически любого типа. На рис. П16.1 показан образец отчета Crystal Reports, экспортированного в доку- мент PDF. Любой отчет Crystal Reports, созданный в Visual Studio .NET, может стать встроенным ресурсом для использования в Windows-, Web-приложениях, а также в Web-службах. В данном разделе в общих чертах рас- сматривается начальная стадия создания отчетов, а также некоторые особые функции. World Safes Report vnaua TobdSHM USA SUMMSM* Pram tUtr MWJMA* England WH.ztt.t7 Omimiw Мп.тгалг Others Sum of Last Yaw's Sism f C*rtry (МММ* Hua SNUMkiUMJN Total SaMAmaau ма.амк>мм.ма g ra.tMwiaam гор $ Сйип'и*®» SaU» c 4ttl0*’4“o**&i₽’“rt *** PaadbocooneawolaabrfrtMf Clyildl • &СаптдМЖ01С>уш1 Самим Mr МЙдМаПнагиИ. < Рис. П16.1. Образец Crystal Reports в формате PDF (предоставлено компанией Crystal Decisions — www.crystaldecisions.com) В помощь созданию отчетов в Visual Studio .NET в пакете Crystal Reports имеется приложение Report Expert. Экспертные системы похожи на шаблоны или мастера: они помогают пользователям пройти все этапы создания различных отчетов с одновременным контролем корректности, не зависящим от разработчика. Доступные экс- пертные системы создают отчеты нескольких типов, включая стандартные, циркулярные, на специальных блан- ках, с перекрестными ссылками, подчиненные отчеты, ярлыки рассылки и сквозные отчеты (рис. П16.2). На рис. П16.3 представлен интерфейс Standard Report Expert. Пакет Crystal Reports для Visual Studio .NET состоит из нескольких компонентов. После задания формы отчета вручную или с помощью программы-эксперта разработчики пользуются конструктором Crystal Reports в Visual Studio .NET для внесения изменений, добавления и форматирования объектов и полей, а также для форматиро- вания макета отчета и окончательного дизайна (рис. П16.4). После этого программа-конструктор создает файлы с расширением rpt (расширение файлов Crystal Reports). Эти файлы обрабатываются механизмом Crystal Reports с выводом отчета в одну из двух программ просмотра Crystal Reports: элемент просмотра Windows Forms или элемент просмотра Web Forms, в зависимости от заданного разработчиком типа приложения. В программах просмотра пользователь получает отформатированную информацию. Подробный "пошаговый" анализ новейших функций представлен на сайте компании Crystal Decisions — www.crystaldecisions.com/netzone. Сюда входит интегрирование и просмотр Web-отчетов в Windows-приложе- ниях, создание интерактивных отчетов в Web-приложениях, просмотр отчетов через Web-службы, а также отче- ты о данных ActiveX Data Objects .NET (ADO)’. В данном разделе в общем виде представлена функциональ- ность некоторых Web-приложений и Web-служб. 1 Элементы сквозного контроля на сайте компании Crystal Decisions протестированы в Visual Studio .NET на С#, однако разработ- чики должны иметь возможность использования анализа на любом языке, поддерживаемом Visual Studio .NET.
Crystal Reports для Visual Studio .NET 1043 Рис. П16.2. Выбор экспертных систем для создания отчета (предоставлено компанией Crystal Decisions — www.crystaldecisions.com) Рис. П1в.З. Элементы, подлежащие форматированию (предоставлено компанией Crystal Decisions — www.crystaldecisions.com) Рис. П16.4. Интерфейс системы проектирования отчетов в Crystal Reports (предоставлено компанией Crystal Decisions — www.crystaldecisions.com) П16.3.1. Crystal Reports в Web-приложениях С помощью Crystal Reports для Visual Studio .NET разработчики имеют возможность интегрирования интерак- тивных отчетов в Web-страницы. Технология ASP.NET, встроенная в Visual Studio .NET, обеспечивает интерак- тивность создания кроссплатформных динамических Web-приложений. Подробно эти технологии рассмотрены в главе 17. В Web-формы входят файлы HTML с вложенными элементами Web-управления и фоновые файлы, содержащие логику программирования. В программе Crystal Reports имеется программа просмотра отчетов Web-форм (Report Viewer), представляющая собой Web-форму с отчетом. При доступе клиента к такой Web-форме обра-
1044 Приложение 16 ботчик события может обновить и отформатировать информацию отчета с последующей отправкой его пользо- вателю*. В анализе на сайте Crystal Decision для программистов представлены инструкции по активизации интерактивно- сти Web-страницы, а также по использованию технологии ASP.NET и ее функциональности. Здесь пользователь получает информацию о разных странах вводом названия страны в текстовое поле. После ввода этой информа- ции название страны передается в элемент просмотра Web-формы; отчет обновляется в формате HTML и пере- дается на браузер клиента. П16.3.2. Crystal Reports и Web-службы Любой отчет, созданный на языке С#, может быть опубликован в XML Report Web Service с помощью програм- мы Crystal Reports для Visual Studio (рис. П16.5). Web-служба предоставляет методы, доступные в Интернете для любого приложения, независимо от языка, на котором оно написано, и платформы. Рис. П16.5. Отчет Crystal Reports, опубликованный виде Web-службы (предоставлено компанией Crystal Decisions — www.crystaldecisions.com) Web-служба отчетов XML представляет собой прекрасный "двигатель", с помощью которого компании- партнеры получают доступ к специфической информации отчетов. Компания Crystal Decisions обеспечивает критический анализ конкретных шагов, выполнением которых пользователь может составить отчет в виде Web- службы. (Подробно Web-службы рассматриваются в главе 18.) После публикации отчета в виде Web-службы XML Report Visual Studio .NET создает dll-файл, содержащий этот отчет, а также файл XML. Оба файла выкладываются на сервер для доступа к ним клиентов. Сообщение протокола SOAP (Simple Object Access Protocol, простой протокол доступа к объектам) на базе XML осуществ- ляет передачу данных из Web-службы и обратно. При использовании Visual Studio .NET для создания и публикации Web-службы разработчик может "связать" ее с Windows- или с Web-приложением для отображения данных, возвращаемых Web-службой. В анализе подроб- но описывается процесс создания и генерирования Web-службы, ее связывания с системами просмотра Windows или Web, а также построение клиентского приложения для просмотра Web-службы1 2. 1 См. "Interactivity and Reports in Web Applications" в Crystal Reports for Visual Studio .NET, www.crystaldecisions.com/netzone 2 Cm. "Exposing Reports as Web Services" в Crystal Reports for Visual Studio .NET. www.crystaldccisions.com/netzone.
Библиография 1. Albahari, В., Р. Drayton and В. Merrill. C# Essentials, Second Edition. — Cambridge, MA: O'Reilly & Associates, 2002. 2. Anderson, R., A. Homer, R. Howard and D. Sussman. A Preview of Active Server Pages+. — Birmingham, UK: Wrox Press, 2000. 3. Anderson, R., B. Francis, A. Homer, R. Howard, D. Sussman and K. Watson. ASP .NET. — Chicago, IL: Wrox Press, Inc., 2001. 4. Archer, T. Inside C#, Second Edition. — Redmond, WA: Microsoft Press, 2002. 5. Blaha, M. R., W. J. Premerlani and J. E. Rumbaugh. Relational Database Design Using an Object-Oriented Meth- odology. II Communications of the ACM. — 1988. — No. 4, April. — Vol. 31. — P. 414—427. 6. Carr, D. F. Dave Winer: The President of Userland and SOAP Co-Creator Surveys the Changing Scene. I I Internet World. — 2001. — March. — P. 53—58. 7. Carr, D. Hitting a High Note. // Internet World. — 2001. — March. — P. 71. 8. Carr, D. Slippery SOAP. // Internet World. — 2001. — March. — P. 72—74. 9. Chappel, D. A Standard for Web Services: SOAP vs. ebXML.// Application Development Trends.— 2001.— February. — P. 17. 10. Chappel, D. Coming Soon: The Biggest Platform Ever. // Application Development Trends Magazine. — 2001. — May. —P. 15. 11. Codd, E. F. A Relational Model of Data for Large Shared Data Banks. I I Communications of the ACM. — 1970. — June. 12. Codd, E. F. Fatal Flaws in SQL. И Datamation. — 1988. — No. 16, August 15. — Vol. 34. — 45—48. 13. Codd, E. F. Further Normalization of the Data Base Relational Model. // Courant Computer Science Symposia. — Vol. 6, Data Base Systems. — Upper Saddle River, N.J.: Prentice Hall, 1972. 14. Conard, J., P. Dengler, B. Francis, J. Glynn, B. Harvey, B. Hollis, R. Ramachandran, J. Schenken, S. Short and C. Ullman. Introducing .NET. — Birmingham, UK: Wrox Press, 2001. 15. Correia, E. J. Visual Studio .NET to Speak in Tongues.// Software Development Times.— 2001.— April.— P. 12. 16. Appleman, D. Moving to VB .NET: Strategies, Concepts, and Code. — Berkeley, CA: Apress Publishing, 2001. 17. Date, C. J. An Introduction to Database Systems, Seventh Edition. — Reading, MA: Addison-Wesley Publishing, 2000. 18. Davydov, M. The Road to the Future of Web Services. I I Intelligent Enterprise. — 2001. — May. — P. 50—52. 19. Deitel, H. M. Operating Systems, Second Edition. — Reading, MA: Addison Wesley Publishing, 1990. 20. Deitel, H. M. and Deitel, P. J. Java How To Program, Fourth Edition. — Upper Saddle River, NJ: Prentice Hall, 2001. 21. Deitel, H. M., Deitel, P. J. and T. R. Nieto. Visual Basic 6 How To Program. — Upper Saddle River, NJ: Prentice Hall, 1999. 22. Deitel, H. M., P. J. Deitel, T. R. Nieto, T. M. Lin and P. Sadhu. XML How To Program. — Upper Saddle River, NJ: Prentice Hall, 2001. 23. Dejong, J. Microsoft's Clout Drives Web Services. I I Software Development Times. — 2001. — March. — P. 29— 31. 24. Dejong, J. One-Stop Shopping: A Favored Method.// Software Development Times.— 2001.— February.— P.20.
1046 Библиография 25. Dejong, J. Raising the Bar. // Software Development Times. — 2001. — March. — P. 29—30. 26. Erlanger. L. Dissecting .NET. I I Internet World. — 2001. — March. — P. 30—36. 27. Erlanger. L. .NET Services. H Internet World. — 2001. — March. — P. 47. 28. Esposito, D. Data Grid In-Place Editing. П MSDN Magazine. — 2001. — June. — P. 37—45. 29. Esposito, D. Server-Side ASP .NET Data Binding: Part 2: Customizing the Data Grid Control. П MSDN Maga- zine. — 2001. — April. — P. 33—45. 30. Finlay, D. GoXML Native Database Clusters Data, Reduces Seek Time. // Software Development Times. — 2001. —March. —P. 5. 31. Finlay, D. New York Prepares for .NET Conference. И Software Development Times. — 2001. — June. — P. 23. 32. Finlay, D. UDDI Works on Classification, Taxonomy Issues.// Software Development Times.— 2001.— March. — P. 3. 33. Fontana, J. What You Get in .NET. // Network World. — 2001. — April. — P. 75. 34. Galli, P. and R. Holland. .NET Taking Shape, But Developers Still Wary. I I eWeek. — 2001. — June. — P. 9, 13. 35. Gillen, A. Sun's Answer to .NET. I I EntMag. — 2001. — March. — P. 38. 36. Gillen, A. What a Year It's Been. П EntMag. — 2000. — December. — P. 54. 37. Gladwin, L. C. Microsoft, eBay Strike Web Services Deal. // Computer World. — 2001. — March. — P. 22. 38. Grimes, R. Make COM Programming a Breeze with New Feature in Visual Studio .NET. // MSDN Magazine. — 2001. — April. — P. 48—62. 39. Gunnerson, E. A Programmer’s Introduction to C#: Second Edition. — New York, NY: Apress, 2001. 40. Harvey, B., S. Robinson, J. Templeman and K. Watson. C# Programming With the Public Beta. — Birmingham, UK: Wrox Press, 2000. 41. Holland, R. Microsoft Scales Back VB Changes. // eWeek. — 2001. — April. — P. 16. 42. Holland, R. Tools Case Transition to .NET Platform. I I eWeek. — 2001. — April. — P. 21. 43. Hulme, G, V. XML Specification May Ease PKI Integration. // Information Week. — 2000. — December. — P. 38. 44. Hutchinson, J. Can’t Fit Another Byte. // Network Computing. — 2001. — March. — P. 14. 45. Jepson, B. Applying .NET to Web Services. I I Web Techniques. — 2001. — May. — P. 49—54. 46. Jones, B. Sams Teach Yourself C# in 21 Days. — Indianapolis, IN: Sams Publishing, 2002. 47. Kamey. J. .NET Devices. I I Internet World. — 2001. — March. — P. 49—50. 48. Kiely, D. Doing .NET In Internet Time.// Information Week.— 2000.— December.— P. 137—138,142— 144,148. 49. Kirtland, M. The Programmable Web: Web Services Provides Building Blocks for the Microsoft .NET Frame- work. И MSDN Magazine. — 2000. — September. msdn.microsoft.com/msdnmag/issues/0900/WebPlatform/WebPlatform.aspx 50. Levitt, J. Plug-And-Play Redefined. // Information Week. — 2001. — April. — P. 63—68. 51. McCright, J. S. and D. Callaghan. Lotus Pushes Domino Services. // eWeek. — 2001. — June. — P. 14. 52. Michaelis, M. and P. Spokas. C# Developer's Headstart. — New York, NY: Osboume/McGraw-Hill, 2001. 53. Microsoft Chimes in with New C Sharp Programming Language. Xephon Web site. June 30,2000. www.xephon.com/news/00063019.html 54. Microsoft Corporation, Microsoft C# Language Specifications. — Redmond, VA: Microsoft Press, 2001. 55. Microsoft Developer Network Documentation. Visual Studio .NET. П CD-ROM, 2001. 56. Microsoft Developer Network Library. .NET Framework SDK. Microsoft Web site: msdn.microsoft.com/library/default.asp 57. Moran, B. "Questions, Answers, and Tips." SQL Server Magazine, April 2001, 19—20. MySQL Manual. MySQL Web site: www.mysql. com/doc/ 58. Oracle Technology Network Documentation. Oracle Web site: otn.oracle.com/docs/content.html 59. Otey, M. Me Too .NET. // SQL Server Magazine. — 2001. — April. — P. 7.
Библиография 1047 60. Papa, J. Revisiting the Ad-Hoc Data Display Web Application. П MSDN Magazine. — 2001. — June. — P. 27— 33. 61. Powell, R. and R. Weeks. C# and the .NET Framework: The C++ Perspective. — Indianapolis, IN- Sams Publish- ing, 2002. 62. Pratschner, S. Simplifying Deployment and Solving DLL Hell with the .NET Framework. // MSDN Library. — 2000. — September. msdn.microsoft.com/library/techart/dply-withnet.htmx 63. Prosise, J. Wicked Code. // MSDN Magazine. — 2001. — April. — P. 121—127. 64. Relational Technology, INGRES Overview. — Alameda, CA: Relational Technology, 1988. 65. Ricadela, A. IBM Readies XML Middleware. П Information Week. — 2000. — December. — P. 155. 66. Ricadela, A. and P. McDougall. eBay Deal Helps Microsoft Sell .NET Strategy. I I Information Week. — 2001. — March. —P. 33. 67. Richter, J. An Introduction to Delegates. I I MSDN Magazine. — 2001. — April. — P. 107—111. 68. Richter, J. Delegates, Part 2. // MSDN Magazine. — 2001. — June. — P. 133—139. 69. Rizzo, T. Let's Talk Web Services. // Internet World. — 2001. — April. — P. 4—5. 70. Rizzo, T. Moving to Square One. I I Internet World. — 2001. — March. — P. 4—5. 71. Robinson, S., O. Comes, J. Glynn, B. Harvey, C. McQueen, J. Moemeka, C. Nagel, M. Skinner and K. Watson. Professional C#, Second Edition. — Birmingham, UK: Wrox Press, 2002. 72. Rollman, R. XML Q & A. // SQL Server Magazine. — 2001. — April. — P. 57—58. 73. Rubinstein, D. Suit Settled, Acrimony Remains. I I Software Development Times. — 2001. — February. — P. 1,8. 74. Rubinstein, D. Play It Again, XML. I I Software Development Times. — 2001. — March. — P. 12. 75. Scott, G. Adjusting to Adversity. П EntMag. — 2001. — March. — P. 38. 76. Scott, G. Putting on the Breaks. // EntMag. — 2000. — December. — P. 54. 77. Sells, C. Managed Extensions Bring .NET CLR Support to C++.// MSDN Magazine.— 2001.— July.— P. 115—122. 78. Seltzer, L. Standards and .NET. I I Internet World. — 2001. — March. — P. 75—76. 79. Shohoud, Y. Tracing, Logging, and Threading Made Easy with .NET.// MSDN Magazine. — 2001. — July.— P. 60—72. 80. Sliwa, C. Microsoft Backs Off Changes to VB .NET. I I Computer World. — 2001. — April. — P. 14. 81. Songini, Marc. Despite Tough Times, Novell Users Remain Upbeat.// Computer World.— 2001.— March.— P.22. 82. Spencer, K. Cleaning House. I I SQL Server Magazine. — 2001. — April. — P. 61—62. 83. Spencer, K. Windows Forms in Visual Basic .NET. П MSDN Magazine. — 2001. — April. — P. 25—45. 84. Stonebraker, M. Operating System Support for Database Management.// Communications of the ACM.— 1981. — No. 7, July. — Vol. 24. — P. 412-^118. 85. Surveyor J. .NET Framework. I I Internet World. — 2001. — March. — P. 43—44. 86. Tapang, С. C. New Definition Languages Expose Your COM Objects to SOAP Clients. // MSDN Magazine — 2001. — April. — P. 85—89. 87. Thai, T. and H. Q. Lam. .NET Framework Essentials. — Cambridge, MA: O'Reilly & Associates, Inc., 2002. 88. Troelsen, A. C# and the .NET Platform. — New York, NY: Apress, 2001. 89. Utley, C. A Programmer's Introduction to Visual Basic .NET. — Indianapolis, IN: Sams Publishing, 2001. 90. Visual Studio .NET ADO .NET Overview. Microsoft Developers Network Web site: msdn.microsoft.com/vstudio/nextgen/technology/adoplusdefault. asp 91. Ward, K. Microsoft Attempts to Demystify .NET. I I EntMag. — 2000. — December. — P. 1. 92. Waymire, R. Answers from Microsoft. // SQL Server Magazine. — 2001. — April. — P. 71—72. 93. Whitney, R. XML for Analysis. // SQL Server Magazine. — 2001. — April. — P. 63—66. 94. Wille, C. Presenting C#. — Indianapolis, IN: Sams Publishing, 2000. 95. Winston, A. A Distributed Database Primer. H UNIX World. — 1988. — April. — P. 54—63. 96. Zeichick, A. Microsoft Serious About Web Services. // Software Development Times. — 2001. — March. — P. 3.
Предметный указатель А Abstract Data Type (ADT) 33,159 Active Accessibility 819 ActiveX 37,933 ActiveX Data Objects (ADO) 580 ADO.NET 580 Advanced Research Projects Agency of the Department of Defense (ARPA) 25 ASP.NET 29 О директива 603 О комментарий 603 AuralCSS 820 в BizTalk 35,558 Business-to-business (B2B) 667 c CallXML 36,814 Cascading Style Sheets (CSS) 27,795 CAST eReader 804 Clicker 4 826 Code-behind file 600,833 Collection classes 773 Common Gateway Interface (CGI) 963 Common Language Infrastructure (CLI) 30 Common Language Runtime (CLR) 242 Common Language Specification (CLS) 30 compact Hypertext Markup Language (cHTML) 833 Component Object Model (COM) 37,932 Control 265 Cookies 629 Ctystal Reports 1041 D Database Management System (DBMS) 563 Delegate 230 Dequeue 160 Discovery files (DISCO) 677 Document Object Model (DOM) 528 Document Type Declaration (DTD) 604 Document Type Definition (DTD) 543 Domain Name Server (DNS) 600 Dynamic Link Library (DLL) 666,932 E Eagle Eyes 820 ECMA 30 emacs 942 Emacspeak 794 Enqueue 160 Extensible HyperText Markup Language (XHTML) 27, 981,999 Extensible Markup Language (XML) 27,522 0 вложение элементов 523 0 комментарий 523 0 комплексный тип элемента 549 0 конфликт имен 526 0 корневой элемент 523 О префикс пространства имен 526 О простой тип элемента 549 О пространство имен 526 ° по умолчанию 527 0 пустой элемент 526 0 разметка 523 0 словари 526 0 тег 523 Extensible Stylesheet Language (XSL) 553,795 0 исходное дерево 553 0 переменная 556 О результирующее дерево 553 F First Input First Output (FIFO) 160 Forms aunthentication 655 Framework Class Library (FCL) 28,96,522 G General path 434 Global Unique Identifier (GUID) 936 Graphical Device Interface (GDI+) 410 Graphical User Interface (GUI) 32,44,264 Graphics Interchange Format (GIF) 54,989 Guest book 644 Gunning Fog Index 795 H Hash code 381 Hash table 381
Предметный указатель 1049 Host 600 HTML-Kit942 HyperText Markup Language (HTML) 26,941 Hypertext Transfer Protocol (HTTP) 600,629 R Rapid Application Development (RAD) 30,160 Relational Database Management System (RDBMS) 563,602 1 Remote machine 667 Remote Procedure Call (RPC) 667 IBM Home Page Reader (HPR) 795 Image Collection Editor 322 Information hiding 159 Integrated Development Environment (IDE) 30 InteiliSense 93 Internet Information Server (IIS) 600 Internet Protocol (IP) 26 IP address 716 s Scalable Vector Graphics (SVG) 808 Self-referential class 743 Session ID 629 Session traking 629 Sessions item 640 Sibling nodes 317 J Simple Object Access Protocol (SOAP) 27,666,670 Single Document Interface (SDI) 331 Job Access with Sound (JAWS) 819 Joint Photographic Experts Group (JPEG) 54,989 Singlecast delegate 230 Stack 159 Standard Generalized Markup Language (SGML) 26 Stateless protocol 629 String Collection Editor 309 Last Input First Output (LIFO) 159 Linked list 744 Structured Query Language (SQL) 35,563,569 Synchronized Multimedia Integration Language (SMIL) 808,820 м Menu Designer 298 Method overloading 111 Microsoft Agent 450 Microsoft Intermediate Language (MSIL) 31,910 Microsoft Magnifier 821 Microsoft Mobile Explorer (MME) 835 Microsoft Narrator 825 Microsoft XML Schema 547 Mobile Internet Toolkit (MIT) 36,832 Mobile Web Form Designer 838 msxml 547 Multicast delegate 230 Multiple Document Interface (MDI) 299,331 Multipurpose Internet Mail Extensions (MIME) 601 Multi-tier application 602 T Text-to-speech (TTS) 814 Toolbox 46 Tracing 661 Transmission Control Protocol (TCP) 26,716 Tree 317 TreeNode Editor 319 и Unicode 463,924 Unicode Transformation Format (UTF) 931 Uniform Resource Identifier (URI) 527,604 Uniform Resource Locator (URL) 600 Universal Description, Discovery and Integration (UDDI) N 677 Universal Resource Locator (URL) 527 Namespace 161 Node 317 User Datagram Protocol (UDP) 716 V о Validation controls 620 Ocularis 820 On-Screen Keyboard 826 Validator 620 vi942 ViaVoice 794,808 p VoiceXML 36,808 Portable Network Graphics (PNG) 54 w Q W3C XML Schema 548 Web Accessibility Initiative (WAI) 36 Queue 160 Web Service Description Language (WSDL) 668 WebSphere Voice Server SDK 1.5 808
1050 Предметный указатель Web: World Wide Web 26 0 сервер 982 0 сервис 29,667 0 служба 29,666,667 0 форма 599 WeMediaBrowser 794 Widget 265 Windows Media Player 448 WinForms 265 Wire format 669 Wire protocol 669 Wireless Application Protocol (WAP) 832 Wireless Markup Language (WML) 833 World Wide Web Consortium (W3C) 26 X XML Path Language (XPath) 537 0 выражение 537 XML Schema 35 XML Validator 546 XML serialization 706 XML-анализатор 524 XSL Transformations (XSLT) 553 A Всплывающая подсказка 45 Абстрактные типы данных 159 Абстракция 169 0 данных 159 Агрегирование 132 Адрес: Вставка кода в программы 212 Вторичный носитель 462 Входное устройство 833 г 0 IP 600,716 0 сетевой 716 Алгоритм поиска простых чисел 1037 Анализатор проверяющий 543 Анимация серии изображений 438 Аппаратно-зависимая визуализация 855 Арифметическое переполнение 240 Аутентификация форм 655 Гиперссылка 600 Глиф 926 Гостевая книга 644 Грамматика EBNF 543 Графический контекст 412 Графический пользовательский интерфейс 264 Группа 279 Б д Дейтаграмма 36, 725 База данных 464,563 0 Books 564 0 полностью определенное имя 575 0 реляционная 563 Байт 463,1028 Библиотека: Делегат 230,267 0 MouseEventHandler 290 0 комбинированный 230 0 одиночный 230 Дерево 317,760 0 двоичного поиске! 760 0 динамически подключаемая 161 0 классов .NET Framework 56,169 ° создание 163 0 типов 936 Бит 462,1028 Блок 70,100 0 catch 242 0 finally 242,248 0 try 242 0 скомпонованный 59 Блокирование объекта 353 Брандмауэр 670 Буфер 465 Буферизация 465 Быстрая разработка приложений 30 0 двоичное 742,760 » сортировка 766 0 корневой узел 760 V узел концевой 760 0 узел-брат 760 0 узел-потомок 760 Деструктор 148,190 Динамически подключаемая библиотека (DLL) 164, 932 Директива: 0 #endregion 278 0 #region278 0 using 103 Документ XML: 0 допустимый 543 0 правильно Построенный 543 в Долговременные данные 462 Домашняя страница 942 Виртуальный код клавиш Windows 295 Вкладки 327 Дополнение значения до единицы 1028 Дуга 425
Предметный указатель 1051 3 0 Font 418 0 FontFamily 421 Заголовок HTTP 601 Запись 463,564 0 ключ 464 Запрос 563 0 DELETE 580 0 INNER JOIN 575 0 INSERT 578 0 LIKE 571 0 ORDER BY 572 0 SELECT 569 0 UPDATE 579 0 WHERE 570 Знак переключения кода 59 Значения ARGB 413 0 FontStyle 283 0 Form 266 0 Graphics 410,423 0 GraphicsPath 434 0 GroupBox 279 0 Hashtable 705,784 0 HttpCookie 636 0 HttpSessionState 637 0 Image 288 0 KeyPressEventArgs 293 0 Label 274 0 LinkLabel 304 0 List 745 0 ListBox 308 И 0 ListBoxTest 309 0 ListView 322 Идентификатор 56 0 сеанса 629 Иерархия данных 463 Изображение, загрузка 436 Индексатор 154 Инкапсуляция 125, 128 Интегрированная среда разработки 30 Интерактивный анимированный персонаж 450 Интерфейс 126,221 0 IComponent 265 Информация класса 149 Исключение 33,239 0 во время прогона 246 0 Match 401 0 Monitor 347,353 0 MouseEventArgs 290 0 MulticastDelegate 267 0 Object 776 0 PictureBox 288 0 Queueinheritance 758 0 RadioButton 282,284 0 ReadSequentialAccessFileFormat 482 0 Regex 401 0 Socket 716,725 0 Stack 754,780 0 String 376 к 0 StringBuilder 387,388 0 SystemException 246 Карта изображений 972,1014 Каскадная таблица стилей 27,795 Каталог, виртуальный 600 Квантификация 402 Квантование времени 349 Квантор 402 Класс 28,56, 126 0 Appli cat ion Except ion 246 0 Array 773 0 ArrayList 775 0 BinaryFormatter 464,481,482 0 BinaiyWriter 723 0 BitArray 1037 0 Brush 431 0 Button 266,275 0 ButtonBase 275 0 CheckBox 282 0 CheckedListBox 311 0 ComboBox 314 0 Console 96 0 Control 271,341 0 Directoiy465 0 EventArgs 269 0 Exception 246,254 0 File 465 0 TcpListener 723 0 TextBox 274 0 Thread 347 0 TraceContext 661 0 TransactionProcessorForm 507 0 Tree 761 0 TreeNode 317 0 UdpClient 725 0 UserContrOl 341 0 ValueType395 0 WebService 675 0 абстрактный 204 0 абстрактный базовый 204 0 базовый 33,90, 168, 170 0 добавление в проект 127 0 конкретный 204 0 контейнер 128 0 косвенный базовый 168 0 производный 33,90, 168, 170 0 прямой базовый 168 0 самоотносимый 743 0 символов 401 0 тело 128 Класс-итератор 205 Клиент-серверное взаимодействие 715
1052 Предметный указатель Ключ базы данных: Литерал строковый 376 0 внешний 568 0 первичный 564 Локализация 924 ° составной 568 Ключевое слово: м 0 abstract 204 0 class 90 0 const 82,116,153 0 delegate 230 0 interface 221 0 lock 353 0 operator 235 0 out 104 0 override 173 0 private 128 0 public 128 0 readonly 153 0 ref 104,117 0 return 110 0 sealed 212 0 static 149 0 struct 395 0 this 146,353 0 tiy242 0 void 100 Кнопка 275 Код символа 375 Кодировка 924 Коллекция 773 0 классов 773 Кольцевой буфер 366 Командное окно 41 Комментарий 55 0 HTML 943 0 XML-документации 909 0 многострочный 55 0 однострочный 55 Компонент 265 0 Timer 342 Конверт SOAP 670 Конкатенация 65,385 Консорциум Unicode 925 Консорциум World Wide Web 26,522 Константа 82,152 0 именованная 116 0 символьная 82,375 0 строковая 376 Конструктор: Маркер конца файла 464 Массив 95,114 0 многомерный 120 0 передача в метод 116 0 прямоугольный 120 0 разреженный 120 Меню 297 Метка 274 Метод 57,126 0 Main 131 0 абстрактный 204 0 вызов 96 0 доступа 128 0 оператор вызова 99 0 определенный программистом 96 0 предикативный 128 0 рекурсивный 109 0 сигнатура 112 0 статический 61 0 формат определения 100 Механизм: 0 перевода текста в речь 450 0 распознавания речи 450 Микробраузер 833 Многодокументный интерфейс 265,331 Многозадачность 346 Многократное использование программного обеспечения 132 Многоугольник 427 Модель реляционной базы данных 563 Модификатор: 0 internal 171 0 protected 171 0 доступа 128 н Набор символов 463 0 Unicode 375 Надкласс 90 Название гарнитуры шрифта 419 Наследование 33,132,166,168,247 0 визуальное 338 0 единичное 168 0 множественное 168 0 класса 129,133 0 перегруженный 134 0 по умолчанию 134 Контроль сеансов 629 Конфликт имен 161 Критерий отбора записей в базе данных 570 Кэш данных 581 Номер порта 716 О Область действия 107 0 блока 107,132 0 класса 107,132 Обнаружение коллизий 439 л Обработка исключительных ситуаций 239 Обработчик: Лексикографическое сравнение 380 Линия 423 0 catch 242 0 событий 99
Предметный указатель 1053 Общая траектория 434 Объект 27,28,126 0 действие 28 0 свойство 28 0 присваивания 73 0 сдвига влево « 1028 0 сдвига вправо » 1028 0 условная 86 Объект потока: 0 стандартного ввода 464 0 стандартного вывода 464 0 стандартной ошибки 464 Объектная модель документа 528 Объектно-ориентированное программирование 126 Объявление: 0 условная ?: 70 0 физической передачи 465 Определение типов документов 543 Освобождение ресурсов 148 Отладка 896 Отладчик 897 Отношение: 0 XML523 0 типа документа 604 Ограничение доступа к данным 128 Ограничивающий прямоугольник 424 Однодокументный интерфейс приложения 331 Окно: 0 многие ко многим 569 0 один ко многим 568 Очередь 160,742,757 Ошибка: 0 компиляции 896 0 Autos 900 0 Breakpoints 902 0 Call Stack 903 0 Class View 165 0 Immediate 900 0 Locals 900 0 MDI 265 0 Object Browser 165 0 Properties 48 0 Solution Explorer 46 0 Task List 896 0 This 900 0 Threads 352 0 Watch 899 0 активное 266 0 диалоговое модальное 477 Оператор 57 0 break 80,84 0 checked 261 0 continue 84 0 do/while 83 0 for 74 0 foreach 123 0 if 63,69 0 if/else70 0 new 114,129,744 0 switch 80,203 0 throw 252 0 unchecked 261 0 while 70 0 вызова метода 99 0 пустой 66 Операция: 0 логическая 896 0 синтаксическая 896 0 синхронизации 240 п Пакет 725 Панель 279 0 инструментов 45 ° Debug 901 0 навигационная 651 Перегрузка: 0 метода 77,111 0 оператора 233 Передача данных: 0 по значению 104 0 по ссылке 104 Передача управления 68 Переключатель 284 Переменная: 0 глобальная 149 0 локальная 96 0 среды 962 0 статическая 149 0 условия 364 0 экземпляра 126,128 Поведение объекта 125 Подкласс 90 Подменю 298 Позиционирование: 0 абсолютное 610 0 относительное 610 Поиск DNS 600 Поле 463,564 0 декремента 73 0 дополнения ~1028 0 инкремента 73 0 логическая 86 0 логического вывода 465 0 побитовая 1028 0 побитового "И" (&) 1028 0 побитового "ИЛИ" (|) 1028 0 побитового исключающего "ИЛИ" (л) 1028 0 приведения типа 78 0 автоматического увеличения 564 Полигон 427 Полиморфизм 33,166,195,197 Порядок табуляции 800 Последовательное выполнение 68 Последовательность 760 Поток: 0 байтов 464 0 приоритет 349 0 состояние 347
1054 Предметный указатель Преобразование типов: Режим: 0 неявное 78,101 0 явное 78,201 Преобразования XSL 553 Приведение типов 101 Привязка 272 Приложение: 0 отладки 899 0 прерывания 899 Результирующее множество 564 Рекурсия 109 Речевой синтезатор 808 Решение 43 0 Windows 42 0 консольное 41 ° отладка 898 0 мгновенной выборки данных 491 0 многоуровневое 602 Пробел 401 Проводной протокол 669 Проводной формат 669 Проект 43 Проектные единицы 418 Прокси-класс 677 Промежуточный язык Microsoft 31 Пространство имен 56,103,161,266 0 по умолчанию 130 Пространство объявления 107 0 класса 107 0 локальной переменной 107 Решето Эратосфена 1037 с Свойство: 0 абстрактное 204 0 класса 137 Селективная кнопка 282 Сериализация XML 706 Символ 375,463 0 разделения 465 0 специальный 463 Символ-слово 401 Синхронизация файлов 30 Система координат 411 Система счисления: 0 восьмеричная 873 Процедура доступа: 0 get 133 0 set 133 Прямоугольник 423 Псевдокод 69 Пункт меню 298 0 двоичная 873 0 десятичная 873 0 шестнадцатеричная 873 Система управления базами данных (СУБД) 464, 563,602 Слияние данных в базах 575 Снятие упорядочивания 464 р Событие 99 0 клавиатуры 292 Размер гарнитуры шрифта 419 Разработка программных приложений на базе Web 599 Рамка изображения 288 Расширение файла: 0 мыши 290 Сокет 36,716 0 дейтаграммный 716 0 потоковый 716 Сокрытие информации 159 0 acw824 0 ascx 651 0 asmx667 0 aspx 599,663 0 cs 58, 127 0 disco 678 0 dll 164 0 dtd 546 0 exe 164 0 htm981 0 html 981 0 ocx 933 0 rpt 1042 0 sin 676 0 tlb936 0 vdisco678 0 xdr 547 0 xml 524 0 xsd 548 Ревизор 620 Регистрация, обратная 626 Сопоставление с эталоном 571 Спецификация: 0 универсального языка 30 0 формата 129 Список 307 0 вложенный 953 0 комбинированный 314 0 маркированный 951 0 нумерованный 955 0 раскрывающийся 314 0 связанный 742,744 Справка: 0 динамическая 49 0 контекстно-зависимая 50 Ссылка: 0 this 146 0 внешняя 853 0 внутренняя 853, 969, 1012 Стандартное устройство вывода 57 Стек 159,742,753 0 вызова метода 254 0 обращений к методам 903
Предметный указатель 1055 Стиль гарнитуры шрифта 419 Столкновения 784 Строка 376 Структура: 0 потомок 529 0 родитель 529 Указатель местоположения файла 486 Универсальное описание, обнаружение и интеграция 0 Color 411 0 Point 411 0 Rectangle 411 0 данных 742 ° динамическая 742 ° линейная 760 Стыковка 273 Сцепление строк 65,385 677 Универсальный идентификатор ресурса 527 Унифицированный указатель ресурса 527 Упорядочивание 464 Устройства iMode 833 Утечка: 0 памяти 247 0 ресурсов 241,247 Учебный пример: т 0 беспроводной портал 851 0 бронирование авиабилетов 693,866 Таблица 564 0 стилей 524 Тег HTML 941 ф 943 0 <а>946 0 <body>943 0 <br>950 0 <form> 962 0 <head> 943 0 <hr>951 0 <html>...<html> 941 0 <img> 947 0 <input>963 0 <li> 953 0 <meta>974 0 <ol>955 0 <p>944 0 <title> 943 0 <ul>951 Ter XML 523 0 конечный 523 0 начальный 523 Текстовое поле 274 Тип данных: 0 гостевая книга 644 0 дерево двоичного поиска: ° объектов 767 ° целочисленных значений 761 0 игра: "Блэк-джек" 683 ° "Крестики-нолики" 730 ° в кости 844 0 интерактивная переписка (чат) 718 0 моделирование процесса тасования и раздачи карт 397 0 начисление зарплаты 213 0 обработка: ° исключений 243 ° кредитов 494 ° транзакций 504 0 определение температуры и погоды 698 0 обучение математике 706 0 просмотр списка книг выбранного автора 650 0 создание и использование интерфейсов 221 0 управление порядком перехода по клавише <ТаЬ> 800 Ф 0 абстрактный 126,159 0 встроенный 65 0 пользовательский 706 0 простой 65 0 структура 395 Точка: Файл 462,463 0 маркер конца 464 0 обнаружения 677 0 открытие 464 0 последовательный 464 0 входа в программу 131 0 выбрасывания исключения 242,254 0 прерывания 897 0 закладки 800 Трассировка 661 0 приложений 662 0 страниц 661 0 прямого доступа 491 0 фонового кода 600,833 Факториал 110 Финализация 148 Флажок 282 Форма 44 0 Windows 265 Фрейм 807,975, 1017 У Функция установки узлов 556 Удаленная машина 667 Удаленный вызов процедуры 667 Узел 317 0 брат 529 0 корневой 317,529 X Хост 600 0 имя 600 Хэширование 784 ' i
1056 Предметный указатель Хэш-код 381 Хэш-таблица 381 Хэш-функция 785 ц Цвет 413 Целостность данных 568 Цифра 401 ч Частичное воспроизведение 439 Число, простое 1037 Число позиций 378 ш Шаблон элемента управления 860 э Экранная клавиатура 826 Элемент сеанса 640 Элемент управления: 0 AdRotator 616 0 Button 275 0 CheckBox 282 0 CheckedListBox 307 0 ComboBox 314 0 GroupBox279 0 Label 274 0 LinkLabel 304 0 ListBox 307 0 ListView 322 0 MainMenu 298 0 Panel 279 0 PictureBox 288 0 RadioButton 282 0 TreeView 317 0 TabControl 327 0 TextBox 274 0 Web 599 0 контейнер 524 0 контролирующий 620 0 мобильный 835 0 оконный 265 0 пользовательский 341 0 потомок 524 0 родитель 524 Эллипс 423 я Язык: 0 С#30 0 XHTML 27,981,999 0 XML 27,522 0 структурированных запросов 563,569 Якорь 946
(bhv c# Авторы: профессор Харви М. Дейтел и Пол Дж. Дейтел являются основателями корпорации Deitel & Associates, Inc., одной из ведущих в мире компаний по разработке надежного программного обеспечения и корпоративному обучению. Ими написана знаменитая серия учебников «How to Program...», которыми пользуются сотни тысяч студентов во всем мире для освоения С, C++, Java, С#, Visual Basic .NET, Perl, Python, XML и других языков программирования. Книга предназначена для программистов, имеющих опыт работы с языками высокого уровня, такими как C++, Visual Basic, Java. В ней применен подход Live-Code корпорации Deitel для обучения программированию при детальном исследовании языка C#. Изложены важ- нейшие концепции C# на примере полностью проте- стированных программ, с нюансами синтаксиса, под- робными построчными описаниями и выходными данными программ. Дана классическая трактовка объектно-ориентированного программирования и вся необходимая информация для создания приложений на базе Windows, Web, а также Web-служб XML. Книга построена по принципу «от простого к слож- ному»: от краткого введения в основные понятия языка C# к более сложным темам, таким как формы Windows, ADO.NET, ASP.NET, Web-службы ASP.NET, сетевое программирование и обработка документов XML. Представлены 230 программ, 402 полезных со- вета по технологии программирования. По каждой теме дан обзор ресурсов Интернета. Практические описания с примерами: • Объекты, наследование и полиморфизм • GUI, формы Windows • Обработка исключений • Мобильный инструментальный набор для выхода в Интернет • ASP.NET, Web-формы и элементы управления Web • Базы данных, SQL и ADO.NET • Web-службы ASP.NET • Обработка документов XML и XHTML • И многое другое... ISBN 5-94157-817-2 БХВ-Петербург 194354, Санкт-Петербург ул. Есенина, 5Б E-mail: mail@bhv.ru Internet: www.bhv.ru Ten./факс: (812) 591-6243 9 785941 5 7 8 1 7 7 I