::Главная ->Литература ->Руководство по программированию в Windows | |
Содержание
::Новости::F.A.Q. ::Форум ::Компоненты ::Исходники ::Литература ::Рассылка ::Ссылки
Клуб
::Клуб программистов::Члены клуба ::off-форум ::off-чат
Работа
::Есть программисты::Есть вакансии ::Программы на заказ ::Готовые программы
Другое
::О сайте::Голосование ::Модератору |
Оглавление Windows 3.0/pg/1#3 = 1 = Глава 1. Обзор среды Windows...................................6 1.1 Сравнение Windows и DOS....................................6 1.1.1 Интерфейс пользователя...................................7 1.1.2 Ввод из очереди..........................................7 1.1.3 Независимая от устройства графика........................8 1.1.4 Многозадачность..........................................9 1.2 Программная модель Windows.................................9 1.2.1 Окна....................................................10 1.2.2 Меню....................................................10 1.2.3 Панели диалога..........................................11 1.2.4 Цикл обработки сообщений................................11 1.3 Библиотеки Windows........................................12 1.4 Разработка прикладной программы для Windows...............12 1.5 Инструментальные средства разработки прикладных......13 1.5.1 Компилятор С............................................13 1.5.2 Компоновщик.............................................13 1.5.3 Редакторы ресурсов......................................14 1.5.4 Компилятор ресурсов.....................................15 1.5.5 Средства отладки и оптимизации..........................15 1.5.6 Сопровождение программ..................................16 1.6 Рекомендации для разработки прикладных программ...........17 1.7 Заключение................................................18 ГЛАВА 2. Прикладная программа Generic.........................20 2.1 Прикладная программа Generic..............................20 2.2 Прикладная программа в среде Windows......................20 2.3 Функция WinMain...........................................21 2.3.1 Типы данных и структуры Windows.........................22 2.3.2 Дескрипторы.............................................23 2.3.3 Управление экземплярами.................................23 2.3.4 Регистрация класса окна.................................24 2.3.5 Создание окна...........................................28 2.3.6 Отображение и корректировка окна........................30 2.3.7 Создание цикла обработки сообщений......................30 2.3.8 Передача управления.....................................32 2.3.9 Завершение прикладной программы.........................32 2.3.10 Функции инициализации..................................33 2.3.11 Командная строка прикладной программы..................35 2.4 Функция окна..............................................35 2.5 Создание панели диалога About.............................37 2.5.1 Создание шаблона панели диалога.........................38 2.5.2. Создание включаемого файла.............................39 2.5.3 Создание функции диалога................................40 2.5.4 Определение меню с элементом About......................41 2.5.5 Обработка сообщения WM_COMMAND..........................42 2.6 Создание файла определения модуля.........................43 2.7 Объединение компонент Generic.............................46 2.7.1 Создание исходного файла на языке С.....................47 2.7.2 Создание включаемого файла..............................53 2.7.3 Создание файла описания ресурсов........................53 2.7.4 Создание файла определения модуля.......................53 2.7.5 Создание файла make.....................................54 2.7.6 Запуск программы MAKE...................................56 2.8 Использование Generic как шаблона.........................56 . Windows 3.0/pg/1#3 = 2 = 2.9 Заключение................................................57 ЧАСТЬ 2. ПРОГРАММИРОВАНИЕ ПРИКЛАДНЫХ ПРОГРАММ WINDOWS........59 Глава 3. Вывод в окно.........................................60 3.1 Контекст отображения......................................60 3.1.1 Использование функции GetDC.............................61 3.1.2 Сообщение WM_PAINT......................................61 3.1.3 Перерисовка области пользователя........................62 3.1.4 Контекст отображения и контекст устройства..............63 3.1.5 Система координат.......................................63 3.2 Создание, выборка и удаление средств рисования............64 3.3 Рисование и вывод текста..................................65 3.4 Пример прикладной программы Output........................67 3.4.1 Добавление новых переменных.............................68 3.4.2 Модификация фрагмента WM_CREATE.........................68 3.4.3 Добавление фрагмента WM_PAINT...........................69 3.4.4 Модификация фрагмента WM_DESTROY........................72 3.4.5 Трансляция и компоновка.................................73 3.5 Заключение................................................73 Глава 4. Ввод с использованием мыши и клавиатуры..............75 4.1 Типы вводимой информации..................................75 4.1.1 Форматы сообщений.......................................76 4.1.2 Ввод с клавиатуры.......................................76 4.1.3 Ввод символа............................................77 4.1.4 Ввод с помощью мыши.....................................77 4.1.5 Ввод от таймера.........................................78 4.1.6 Ввод из строки прокрутки................................79 4.1.7 Ввод из меню............................................80 4.2 Пример прикладной программы Input.........................81 4.2.1 Как программа Input осуществляет вывод..................82 4.2.2 Добавление новых переменных.............................82 4.2.3 Установка типа класса окна..............................83 4.2.4 Модификация функции CreateWindow........................83 4.2.5 Определение прямоугольников для текста..................84 4.2.6 Добавление элемента WM_CREATE..........................84 4.2.7 Модификация фрагмента WM_DESTROY........................85 4.2.8 Добавление фрагментов WM_KEYUP и WM_KEYDOWN.............85 4.2.9 Добавление фрагмента WM_CHAR............................85 4.2.10 Добавление фрагмента WM_MOUSEMOVE......................85 4.2.11 Добавление фрагментов WM_LBUTTONUP и WM_LBUTTONDOWN....86 4.2.12 Добавление фрагмента WM_LBUTTONDBLCLK..................86 4.2.13 Добавление фрагмента WM_TIMER..........................86 4.2.14 Добавление фрагментов WM_HSCROLL и WM_VSCROLL..........86 4.2.15 Добавление фрагмента WM_PAINT..........................87 4.2.16 Трансляция и компоновка................................87 4.3 Заключение................................................88 Глава 5. Иконы................................................89 5.1 Что такое икона..........................................89 5.1.1 Использование встроенных икон..........................90 5.2. Использование собственных икон...........................90 5.2.1 Создание файла иконы...................................90 5.2.2 Определение ресурса иконы..............................91 5.2.3 Загрузка ресурса иконы.................................91 . Windows 3.0/pg/1#3 = 3 = 5.3 Иконы классов.............................................91 5.4 Отображение собственных икон.............................92 5.5 Использование икон в панели диалога.......................93 5.6 Пример прикладной программы Icon..........................94 5.6.1 Включение оператора ICON................................95 5.6.2 Включение управляющего оператора ICON...................95 5.6.3 Установка иконы класса..................................95 5.6.4 Добавление MYICON.ICO в MAKE-файл.......................95 5.6.5 Трансляция и компоновка................................95 5.7 Заключение...............................................96 Глава 6. Курсор, мышь и клавиатура............................97 6.1. Управление формой курсора................................97 6.1.1. Использование встроенных курсоров......................97 6.1.2 Использование собственных курсоров.....................98 6.2 Отображение курсора......................................99 6.2.1 Курсор класса...........................................99 6.2.2 Индицирование собственного курсора......................99 6.2.3 Пример: Использование песочных часов при длительных....100 6.3 Выбор пользователем инофрмации с помощью мыши...........101 6.3.1 Начало выборки графики................................102 6.3.2 Индицирование выборки.................................104 6.3.3 Окончание выборки.....................................106 6.4 Использование курсора с клавиатурой.....................107 6.4.1 Использование клавиатуры для перемещения курсора......107 6.4.2 Использование курсора при отсутствии мыши..............109 6.5 Пример прикладной программы: Cursor.....................110 6.5.1 Добавление оператора CURSOR...........................111 6.5.2. Добавление новых переменных...........................112 6.5.3 Установка курсора класса..............................112 6.5.4 Подготовка курсора в виде песочных часов..............113 6.5.5 Добавление длительной операции........................113 6.5.6 Добавление фрагментов WM_LBUTTONDOWN, WM_MOUSEMOVE и...114 6.5.7 Добавление фрагмента WM_KEYDOWN и WM_KEYUP............116 6.5.8 Добавьте файл BULLSEYE.CUR к MAKE-файлу...............118 6.5.9 Трансляция и компоновка...............................118 6.6 Заключение..............................................118 Глава 7. Меню...............................................120 7.1 Что такое меню..........................................120 7.2 Определение меню........................................121 7.2.1 Идентификаторы меню...................................122 7.3 Подключение меню к прикладной программе.................123 7.3.1 Определение меню в качестве меню класса................123 7.3.2 Установка меню окна...................................123 7.4 Обработка ввода от меню.................................124 7.5 Работа с меню из прикладной программы...................125 7.5.1 Доступные и недоступные элементы меню.................126 7.5.2 Контроль элементов меню...............................127 7.5.3 Добавление элементов к меню...........................128 7.5.4 Модификация существующего меню........................129 7.5.5 Удаление элемента меню................................130 7.5.6 Использование в качестве элементов меню растровых карт.131 7.5.7 Замена меню...........................................132 . Windows 3.0/pg/1#3 = 4 = 7.5.8 Создание нового меню.................................132 7.5.9 Инициализация меню....................................133 7.6 Специальные возможности меню............................134 7.6.1 Клавиши - ускорители..................................134 7.6.2 Использование каскадных меню..........................138 7.6.3 Использование плавающих накладываемых меню............139 7.6.4 Создание собственных контрольных отметок..............141 7.6.5 Использование меню, рисуемых владельцем...............143 7.7 Пример прикладной программы: EditMenu...................145 7.7.1 Добавление новых меню к файлу описания ресуросов......145 7.7.2 Добавление определений во включаемый файл.............146 7.7.3 Добавление к файлу описания ресурсов таблицы...147 7.7.4 Добавление новых переменных...........................147 7.7.5 Загрузка таблицы ускорителей..........................148 7.7.6 Модификация цикла обработки сообщений.................148 7.7.7 Модификация фрагмента WM_COMMAND......................148 7.7.8 Компиляция и компоновка...............................149 7.8 Заключение..............................................149 . Windows 3.0/pg/1#3 = 5 = ---------------------------------------------------------------- ПРОГРАММА-СПРАВОЧНИК ПО Microsoft Windows Версия 3.0 Руководство по програмированию в среде Microsoft Windows 1#3 Москва 1991 г. ---------------------------------------------------------------- . Windows 3.0/pg/1#3 = 6 = Глава 1. Обзор среды Windows. ---------------------------------------------------------------- Microsoft Windows 3.0 предоставляет много дополнительных возможностей, которых нет в стандартной DOS. По этой причине прикладные программы в среде Windows выглядят более сложными, чем стандартные DOS-программы. В данной главе описаны следующие разделы: - Сравнение прикладных программ Windows и прикладных прог- рамм DOS. - Возможности, предоставляемые средой Windows, и включение этих возможностей при разработке ваших прикладных программ. - Программная модель Windows. - Процесс разработки прикладных программ Windows. 1.1 Сравнение Windows и DOS. Microsoft Windows 3.0 предоставляет много дополнительных возможностей, которых нет в стандартной DOS. По этой причине прикладные программы в среде Windows выглядят более сложными, чем стандартные DOS-программы. Вы поймете это после рассмотрения дополнительных возможностей, которые предоставляет Windows. Эти возможности включают: - графический интерфейс, обеспечивающий возможность использования в прикладных программах окон, меню, панелей диалога и блоков управления; - ввод с использованием очередей; - независимая от устройств графика; - многозадачность; - обмен данными между прикладными программами. Большинство пользователей, программирующих на языке Си, используют стандартную библиотеку исполняющей системы Си для ввода, вывода, управления памятью и других действий. Эта библиотека подразумевает стандартную операционную среду, включающую алфавитно-цифровой терминал для ввода/вывода и единоличный доступ к системной памяти и устройствам ввода/вывода ПЭВМ. В среде Windows эти предположения уже недействительны. Прикладные программы, работающие в среде Windows, разделяют системные ресурсы, включая процессор, с другими прикладными программами и взаимодействуют с пользователем через графический дисплей, клавиатуру и мышь. . Windows 3.0/pg/1#3 = 7 = В следующих разделах описаны основные отличия между стандартными прикладными программами DOS и прикладными программами Windows. 1.1.1 Интерфейс пользователя. Одной из главных целей создания среды Windows является обеспечение одновременного визуального доступа к большинству (если не всем) прикладным программам. В многозадачном окружении важно дать всем прикладным программам некоторую часть экрана для взаимодействия с пользователем; это подразумевает, что пользователь может взаимодействовать со всеми прикладными программами. Некоторые системы предоставляют весь экран одной программе, в то время как другие программы работают в фоновом режиме. В Windows все прикладные программы имеют доступ к одному и тому же экрану одновременно. Прикладная программа разделяет экран с другими прикладными программами, используя "окно" для взаимодействия с пользователем. На самом деле окно - это несколько больше, чем простой прямоугольник, предоставленный системой прикладной программе.В действительности окно содержит такой набор визуальных объектов как меню, блоки управления и строки прокрутки, которые пользователь применяет для непосредственного управления прикладной программой. В стандартном окружении DOS система автоматически подготавливает системный дисплей для прикладной программы, обычно передавая прикладной программе дескриптор файла, который можно использовать для вывода информации на системный экран посредством системных вызовов или стандартных вызовов исполняющей системы С. В среде Windows перед тем, как осуществить ввод или вывод, необходимо создать окно. Но как только это сделано, Windows обеспечивает пользователя полной информацией о том, что он должен делать с окном, и автоматически выполняет многие запросы пользователя, такие, например, как перемещение или изменение размеров окна. Другим преимуществом Windows является возможность создавать и использовать любое число окон для индицирования той или иной информации. При этом за экраном следит сама среда, так что не может быть двух прикладных программ, которые пытаются получить одновременный доступ к одной и той же части системного дисплея. 1.1.2 Ввод из очереди. Одним из основных отличий между Windows и стандартным С является метод ввода от пользователя. В среде DOS программа читает с клавиатуры при помощи явного вызова, например, функции getchar. Эта функция обычно . Windows 3.0/pg/1#3 = 8 = ожидает, пока пользователь нажмет клавишу, прежде чем возвратить в программу код символа. В среде Windows прикладная программа не делает явных вызовов для чтения с клавиатуры. Вместо этого Windows получает всю вводимую информацию с клавиатуры, от мыши или таймера в свою системную очередь и автоматически перенаправляет ввод в конкретную прикладную программу, копируя его из системной очереди в очередь прикладной программы. Когда прикладная программа готова принять вводимую информацию, она просто читает из своей очереди следующее сообщение. В стандартной системе DOS ввод с клавиатуры обычно осуществляется в форме 8 битовых знаков. Стандартные функции ввода, getchar и fscanf, читают знаки с клавиатуры и возвращают ASCII-коды, соответствующие нажатым клавишам. Программа может перемешивать прерывания от устройств ввода (например, от мыши или таймера). В среде Windows для каждого созданного окна автоматически предусматривается ввод с клавиатуры или мыши. Среда обеспечивает ввод в унифицированном формате в виде так называемых "сообщений о вводе", содержащих гораздо большую информацию о вводе, чем DOS. Эти сообщения специфицируют системное время, положение мыши, состояние клавиатуры, код клавиши (если она нажата), нажатие на кнопку мыши, а также устройство, генерирующее сообщение. Например, имеются два сообщения клавиатуры: WM_KEYDOWN и WM_KEYUP. Эти сообщения соответствуют нажатию и отпусканию той или иной клавиши. Для каждого сообщения система обеспечивает независимый от устройства виртуальный код, который идентифицирует клавишу, зависимый от устройства скан-код, генерируемый клавиатурой, состояние таких клавиш, как Shift, Ctrl или NumLock. Сообщения клавиатуры, мыши и таймера имеют один и тот же формат и обрабатываются одним и тем же способом. 1.1.3 Независимая от устройства графика. В среде Windows имеется доступ к богатому набору независимых от устройств графических операций. Это означает, что прикладная программа может рисовать линии, прямоугольники, окружности и даже сложные кривые, используя для этого одни и те же вызовы и данные для их вывода на экран высокого разрешения или на матричный принтер. Windows требует наличия драйверов устройств для преобразования запросов на графический вывод в информацию, выводимую на принтер, плоттер, дисплей или другое устройство вывода. Драйвер устройства - это специальная библиотека исполняющей системы, которую прикладная программа может загрузить и присоединить к специальному устройству вывода или порту. "Контекст устройства" определяет драйвер устройства, устройство вывода и коммуникационный порт. Прикладная программа в этом случае может выполнить графические операции в . Windows 3.0/pg/1#3 = 9 = "контексте" данного устройства. 1.1.4 Многозадачность. Windows - многозадачная среда. Это означает, что одновременно могут работать более одной прикладной программы. В стандартной DOS многозадачность не поддерживается. Обычно DOS-программы "предполагают", что они имеют исключительное управление всеми ресурсами ПЭВМ, включая устройства ввода и вывода, память, системный дисплей и даже процессор. В среде Windows все эти ресурсы разделяются с другими прикладными программами, работающими в данный момент. По этой причине Windows тщательно следит за этими ресурсами и требует от прикладной программы использования специального программного интерфейса, гарантирующего Windows управление этими ресурсами. Например, в стандартной DOS программа имеет доступ ко всей памяти, не занятой системой или резидентными программами. Это означает, что программы вольны использовать всю доступную память для любых своих нужд и осуществлять доступ к ней любым способом. В Windows память - распределяемый ресурс. Поскольку в один и тот же момент времени могут работать несколько программ, необходимо разделять память, чтобы избежать ее исчерпания. Прикладные программы могут получить ту часть памяти системы, которая им необходима. В Windows имеется два источника распределения памяти: глобальная память для выделения больших блоков памяти и локальная память для выделения небольших блоков памяти. Для наиболее эффективного использования памяти программа управления памятью Windows часто перемещает или даже сбрасывает блоки памяти. Это означает, что вы не можете подразумевать, что объекты, которые вы определили, находятся там, куда вы их поместили. Если работает несколько прикладных программ, то перемещение или сбрасывание блоков памяти может происходить достаточно часто. Другим примером разделяемого ресурса может служить системный дисплей. В стандартном окружении DOS система обычно предоставляет вашей программе исключительное использование системного дисплея. Это означает, что вы можете использовать дисплей так, как вам нравится, изменять цвет символов и фона, изменять режим отображения с текстового на графический и наоборот. В Windows ваша программа разделяет доступ к системному дисплею вместе с другими прикладными программами, и поэтому вы не получаете управления дисплеем. 1.2 Программная модель Windows. Большинство прикладных программ Windows используют для взаимодействия с пользователем следующие элементы: - Окна. . Windows 3.0/pg/1#3 = 10 = - Меню. - Панели диалога. - Цикл обработки сообщений. В последующих подразделах эти элементы описываются более подробно. 1.2.1 Окна. Окно - это основной элемент для ввода и вывода информации любой прикладной программы Windows. Это единственный способ для прикладной программы получить доступ к системному дисплею. Окно - это совокупность строки заголовка, строки меню, строк прокрутки, окантовки и других элементов, заполняющих прямоугольник окна на системном дисплее. При создании окна необходимо указать те свойства, которые от него требуются. Затем среда Windows рисует окно на экране и управляет им. На рис. 1.1 показаны основные элементы окна. Хотя прикладная программа создает окно и технически исключительно "владеет" им, управление окном фактически осуществляют совместно прикладная программа и Windows. Windows управляет расположением и видом окна, его стандартными возможностями, такими, например, как окантовка, строки прокрутки и заголовки, и выполняет ряд задач, инициированных пользователем и непосредственно воздействующих на окно. Прикладная программа управляет всем остальным, за исключением окна. Она, в частности, отвечает за область пользователя (часть внутри окна), в которую прикладная программа может свободно заносить все, что необходимо. Windows отслеживает для каждого окна те изменения, которые могут влиять на него. Поэтому, каждое окно должно иметь соответствующую "функцию окна". Функция окна получает от программы управления окнами сообщения, на которые должна соответствующим образом реагировать. Сообщения управления окном или определяют действия, которые должна выполнить функция, или запрашивают от функции информацию. 1.2.2 Меню. Меню - это главное средство ввода от пользователя в прикладных программах, работающих в среде Windows. Меню - это перечень команд, который пользователь может просматривать и из которого может выбирать. При создании прикладной программы определяются вид меню и имена команд в меню. Windows индицирует и управляет меню, посылая сообщение функции окна, когда пользователь делает выбор. Сообщение - это сигнал для выполнения команды. . Windows 3.0/pg/1#3 = 11 = 1.2.3 Панели диалога. Панель диалога - это временное окно, которое можно создать для того, чтобы дать пользователю возможность получить больше информации о команде. Панель диалога содержит один или несколько блоков управления. Блок управления - это небольшое окно, которое имеет очень простые функции ввода и вывода. Например, редактируемый блок управления - это просто окно, дающее пользователю возможность вводить и редактировать текст. Блоки управления в панели диалога представляют пользователю способ определения имен файлов, выбора параметров и других направлений действия команды. 1.2.4 Цикл обработки сообщений. Поскольку прикладная программа получает входную информацию через очередь прикладной программы, основной чертой каждой прикладной программы, работающей в среде Windows, является цикл обработки сообщений. Цикл обработки сообщений получает сообщения о вводе из очереди прикладной программы и направляет их соответствующим окнам. Как показано на рис. 1.2, Windows собирает всю вводимую с клавиатуры информацию. Windows получает ввод от пользователя, когда он нажимает или отпускает клавишу. Затем она копирует эту информацию в соответствующую очередь прикладной программы. Цикл обработки сообщений находит сообщение, переводит его в сообщение типа символа ANSI, WM_CHAR и направляет его через Windows соответствующей функции окна. Функция окна может затем с помощью функции TextOut вывести этот символ в области пользователя в окне. Windows может получать и направлять сообщения ввода для нескольких прикладных программ одновременно. Как показано на рисунке 1.3, Windows получает всю вводимую информацию в форме сообщений в своей системной очереди. Затем он копирует эти сообщения в очередь прикладной программы. Цикл обработки сообщений каждой прикладной программы получает сообщения и направляет их через Windows соответствующей функции окна. В отличие от сообщений клавиатуры, которые прикладная программа получает из очереди, сообщения управления окнами Windows направляет напрямую функции окна. На рис. 1.4 показано, как Windows посылает сообщения, управляющие окнами, непосредственно функции окна. После того, как Windows выполнит запрос на разрушение окна, она посылает сообщение WM_DESTROY непосредственно функции окна в обход очереди прикладной программы. Функция окна должна затем сигнализировать основной функции, что окно разрушается, и прикладная программа должна быть завершена. Она делает это, копируя сообщение WM_QUIT в очередь прикладной программы с помощью функции PostQuitMessage. Когда цикл обработки сообщений находит сообщение WM_QUIT, . Windows 3.0/pg/1#3 = 12 = цикл завершается, и делается выход из основной функции. 1.3 Библиотеки Windows. Функции Windows такие, например, как функции исполняющей системы С, определяются в библиотеках. Другие библиотеки Windows (не включающие эти функции) являются специальными библиотеками динамической связи, которые система связывает с прикладной программой при ее загрузке. Библиотеки динамической связи являются важнейшими компонентами среды Windows, поскольку они минимизируют объем кода прикладных программ. Windows содержит три основные библиотеки: User библиотека пользователя. Обеспечивает управление всей средой Windows и окнами прикладной программы; Kernel библиотека ядра. Обеспечивает системный сервис, та- кой как многозадачность, управление памятью и управле- ние ресурсами; GDI библиотека GDI. Обеспечивает интерфейс графических устройств. 1.4 Разработка прикладной программы для Windows. Прикладная программа в среде Windows разрабатывается в следующей последовательности: - написать функцию WinMain и функции окон и поместить их в исходные файлы на языке С или языке ассемблера; - с помощью редакторов ресурсов (SDKPaint, Dialog Editor и Font Editor) создать курсоры, иконы, растровые карты и шрифты, которые необходимы в прикладной программе; - создать файл описания ресурсов, в котором определить все ресурсы прикладной программы. В файле описания ресурсов вы приводите имена и список созданных вами на предшествующих шагах ресурсов. Вы также определяете меню, панели диалога и другие ресурсы; - написать файл определения модуля (.DEF), который определяет атрибуты модулей прикладной программы, типа атрибутов сегментов, размер стека и размер динамической области памяти; - оттранслировать и скомпоновать все исходные программы на языке Си и ассемблера; - оттранслировать с помощью компилятора ресурсов файл описания ресурса и присоединить его к выполняемому файлу. . Windows 3.0/pg/1#3 = 13 = На рис. 1.5 представлены шаги, необходимые для создания прикладной программы в среде Windows. 1.5 Инструментальные средства разработки прикладных программ. При создании прикладной программы необходимо использовать много новых инструментальных средств, а также уже известные инструментальные средства с новыми свойствами. В последующих пунктах все они кратко описываются. 1.5.1 Компилятор С. Можно транслировать прикладные программы Windows, используя в командной строке те же самые параметры, которые применяются для стандартных С-программ. Однако Windows требует использования двух специальных параметров: -Gw и -Zp. Параметр -Gw добавляет к каждой функции требуемые Windows коды инициализации и завершения. Параметр -Zp упаковывает структуры так, что структуры, используемые в прикладной программе, имеют тот же размер, что и структуры Windows. Типичная команда компилятора cl выглядит так: cl -c -AS -Gw -Os -Zdp test.c Параметр -c заставляет компилятор производить только компиляцию, но не компоновку. Этот параметр необходим в случае, если вы компилируете файлы с исходным кодом отдельно. 1.5.2 Компоновщик. Компоновщик, поставляемый с Microsoft C Compiler, позволяет формировать выполняемые файлы в формате Windows. Дополнительной его чертой является необходимость иметь файл описания модуля. Этот файл (.DEF): - определяет имя прикладной программы; - помечает, что прикладная программа должна иметь формат Windows; - определяет атрибуты прикладной программы, такие как должен ли сегмент быть перемещаемым. Ниже приведен пример файла определения модуля: NAME Generic ; имя прикладной программы DESCRIPTION 'Sample Microsoft Windows Application' EXETYPE WINDOWS ; обязательно для Windows . Windows 3.0/pg/1#3 = 14 = STUB 'WINSTUB.EXE' ; печатает сообщение об ошибке при ; попытке запустить программу без ; Windows CODE PRELOAD MOVEABLE ; код может быть перемещаем ; DATA должен быть MULTIPLE, если может работать несколько ; экземпляров программы одновременно DATA MOVEABLE MULTIPLE HEAPSIZE 1024 STACKSIZE 5120 ; рекомендуемый минимум для Windows ; все функции, которые вызывает Windows, должны быть ; экспортируемыми EXPORTS MainWndProc @1 ; имя функции обработки сообщений AboutDlgFunc @2 ; имя функции обработки панели диа- ; лога About Для того, чтобы скомпоновать прикладную программу в среде Windows, необходимо указать имена объектных файлов, сформированных компилятором, имя импортируемой библиотеки Windows, имя файла определения модуля и другие параметры и файлы. Ниже приводится пример типичной команды link: link /NOD Generic,,, slibw libw, Generic.def Более подробная информация по link и файлу определения модуля приводится в "Tools". 1.5.3 Редакторы ресурсов. Редакторы ресурсов используются для создания ресурсов типа курсоров, икон или растровых карт. После этого вы можете заносить эти ресурсы в файл описания ресурсов. Эти программы входят в комплект инструментальных средств Windows SDK. Это редакторы: - SDKPaint (SDKPAINT) - для создания икон, курсоров и растровых карт; - Dialog Editor (DIALOG) - для создания описаний панелей диалога; - Font Editor (FONTEDIT) - редактор шрифтов для создания шрифтов. Поскольку данные редакторы являются прикладными программами Windows, то вы должны запускать их только из среды Windows. Подробная информация о редакторах ресурсов приведена в . Windows 3.0/pg/1#3 = 15 = "Tools". 1.5.4 Компилятор ресурсов. Большинство прикладных программ в среде Windows использует ряд ресурсов, таких, например, как иконы, курсоры, меню и панели диалога. Эти ресурсы необходимо описать в специальном файле, называемом "файл описания ресурсов". Этот файл имеет расширение .RC. Затем оттранслировать этот файл с помощью компилятора ресурсов (RC) и подсоединить его к выполняемому файлу прикладной программы. Во время выполнения прикладной программы она может загружать и использовать ресурсы из выполняемого файла. Ниже приводится пример файла описания ресурсов, идентифицирующего два ресурса - курсор и икону: bullseye CURSOR bullseye.cur Generic ICON Generic.ico Первый оператор определяет ресурс курсора с именем Bullseye, объявляя его курсором (CURSOR), и определяет файл, в котором хранится сам ресурс (Bullseye.cur). Второй оператор делает то же для иконы. Для трансляции файла описания ресурсов и подсоединения его к выполняемому файлу используйте команду rc. Ниже приводится типичная команда rc: rc example.rc Компилятор ресурсов полностью описан в "Tools". Операторы файла определения ресурсов описаны во втором томе справочного руководства. 1.5.5 Средства отладки и оптимизации. SDK Windows включают в себя несколько средств, которые помогут вам отлаживать прикладные программы и оптимизировать их работу: - CodeView для Windows (CVW) позволяет вам отлаживать прикладные программы Windows при работе Windows в стандартном режиме и в расширенном режиме процессора 386. CVW позволяет устанавливать точки останова, просматривать текст программ на уровне исходного текста и выводить символьную информацию во время выполнения прикладной программы. - Символьный отладчик (symdeb) позволяет отлаживать прикладные программы при работе в реальном режиме. - Шпион (SPY) позволяет вам отслеживать сообщения, которые . Windows 3.0/pg/1#3 = 16 = Windows посылает вашей прикладной программе. Этим средством удобно пользоваться при отладке. - Профилер (PROFILER) позволяет вам определить относительное время, в течение которого работают различные сегменты кода вашей программы. Это поможет вам улучшить характеристики вашей программы. - Анализатор подкачки (SWAP) позволяет вам определить поведение вашей программы при выполнении подкачки памяти. - Heap Walker (HEAPWALK) позволяет вам определить содержимое локальной и глобальной динамической области памяти. Подробно данные средства описаны в "Tools". 1.5.6 Сопровождение программ. Программа make - это средство сопровождения программ, которое корректирует программы, сохраняя даты создания исходных файлов. MAKE включена в стандартную поставку с Microsoft С версии 5.1 (NMAKE - это та же программа, только с Microsoft С версии 6.0). Обе программы работают с Windows. Какую вы будете использовать, зависит от имеющейся у вас версии С. Хотя MAKE и NMAKE поставляются с компилятором С, а не с SDK, она особенно важна из-за большого количества файлов, требуемых для создания прикладных программ в Windows. Программа make работает с файлом make, который содержит перечень команд и файлов, необходимых для создания прикладных программ. Команды транслируют и компонуют различные файлы. Make выполняет эти команды только в том случае, когда изменились файлы, указанные в этих командах. Это экономит время при незначительных изменениях в программе. Ниже приводится пример типичного файла make: #эта строка позволяет также использовать NMAKE all:Generic.exe #модифицировать ресурсы при необходимости Generic.res: Generic.rc Generic.h rc -r Generic.rc #модифицировать при необходимости объектные файлы Generic.obj: Generic.c Generic.h cl -c -AS -DLINT_ARGS -Gsw -Oat -W2 -Zped Generic.c #модифицировать при необходимости .exe файл и добавить #ресурсы. Generic.exe: Generic.obj Generic.def . Windows 3.0/pg/1#3 = 17 = link /NOD Generic,,, SLIBEW LIBW, Generic.def MAPSYM Generic RC Generic.res #если обновлен .res файл, а выполняемый файл остался #прежним. Generic.exe: Generic.res RC Generic.res Обычно файлы make имеют то же самое имя, что и создаваемые прикладные программы, хотя допускаются и другие имена. Типичная команда make создает файл Generic: make Generic Подробное описание программы MAKE вы найдете в документации по Microsoft C Optimizing Compiler. 1.6 Рекомендации для разработки прикладных программ. Существует много приемов программирования, которые работают в стандартном С или ассемблере, но не работают в среде Windows. В главе 14 "Язык С и ассемблер" содержатся детальная информация по использованию этих языков для написания программ под Windows. При разработке программ необходимо помнить следующие основные правила: - не стремитесь к единоличному использованию процессора - это разделяемый ресурс. Хотя Windows и многозадачная система, она не может взять управление у прикладной программы до тех пор, пока прикладная программа сама не вернет его. Корректные программы позволяют другим программам получить доступ к процессору. - не пытайтесь получить непосредственный доступ к памяти или аппаратуре, такой как клавиатура, мышь, таймер, дисплей, последовательный или параллельный порт. Windows требует абсолютного управления этими ресурсами для того, чтобы иметь одинаковый непрерывный доступ ко всем работающим прикладным программам. - каждая программа должна иметь функцию WinMain. Эта функция является точкой входа или начальной точкой прикладной программы. Она содержит операторы и вызовы функций Windows, которые создают окна и считывают и передают сообщения прикладной программы. Определение функции имеет следующую форму: int PASCAL WinMain(hInst,hPrevInst,lpCmdLine,nCmdShow) HANDLE hInst; . Windows 3.0/pg/1#3 = 18 = HANDLE hPrevInst; LPSTR lpCmdShow; int nCmdShow; { . . . } Функция WinMain должна быть объявлена с ключевым словом PASCAL. Хотя Windows вызывает эту функцию напрямую, WinMain не должна объявляться как FAR, поскольку она вызывается из подсоединенного в период компоновки стартового кода. - при использовании функций Windows желательно проверять возвращаемые значения, поскольку при игнорировании возвращаемых значений могут возникнуть неопределенные состояния, когда функции завершаются по ошибке. - не используйте такие динамические функции ввода/вывода исполняющей системы С как getchar, putchar, scanf и printf. - не используйте функции ввода/вывода файлов исполняющей системы С для доступа к последовательным и параллельным портам. Вместо этого используйте функции коммуникаций, которые подробно описаны в Справочном руководстве. - можно использовать функции ввода/вывода исполняющей системы С для доступа к дисковым файлам. В частности, используйте функцию OpenFile среды Windows и функции низкого уровня исполняющей системы С для ввода и вывода. Хотя функции ввода/вывода исполняющей системы С и могут быть использованы, вы не получаете преимуществ, которые предоставляет функция OpenFile. - можно использовать функции исполняющей системы С для управления памятью malloc, calloc, realloc и free, но нужно быть уверенным, что Windows преобразует эти функции в свои собственные функции локальной динамической области памяти LocalAlloc, LocalReAlloc и LocalFree. Поскольку функции локальной динамической области памяти не всегда работают точно так, как функции исполняющей системы С для управления памятью, то можно получить непредсказуемый результат. 1.7 Заключение. В данной главе приводится обзор среды Windows и производится сравнение со стандартным окружением С-программ. Дополнительную информацию по различным разделам, относящимся к . Windows 3.0/pg/1#3 = 19 = Windows, в можете получить: Раздел Руководство --------------------------------------------------------------- Цикл обработки Руководство программиста, глава 2 "Приклад- сообщений ная программа Generic" Простая программа Руководство программиста, глава 2 "Приклад- Windows ная программа Generic" Меню Руководство программиста, глава 7 "Меню" Панель диалога Руководство программиста, глава 9 "Панели диалога" Использование стан- Руководство программиста, глава 14 "Язык С дартных функций С и и язык ассемблера" языка ассемблера Функции и сообщения Справочное руководство, том 1. Windows Средства разработки "Tools" . Windows 3.0/pg/1#3 = 20 = ГЛАВА 2. Прикладная программа Generic. ---------------------------------------------------------------- В данной главе поясняется, как создать простую прикладную программу, названную Generic. Эта прикладная программа демонстрирует принципы создания прикладных программ, приведенные в главе 1, "Обзор среды Windows". В этой главе описаны следующие темы: - важные части прикладных программ Windows; - инициализация прикладных программ Windows; - цикл обработки сообщений; - завершение прикладной программы; - основные шаги, необходимые при создании прикладной программы. Generic также будет использоваться как основа других рассматриваемых прикладных программ в части 2. (Исходные тексты программы Generic и других примеров программ поставляются вместе с SDK на диске "Sample Source Code".) 2.1 Прикладная программа Generic. Generic - это стандартная прикладная программа в среде Windows, т.е. она отвечает рекомендациям по пользовательскому интерфейсу, данным в System Application Architecture, Common User Access: Advansed Interface Design Guide. Generic имеет основное окно, окантовку, меню прикладной программы и панели для увеличения и уменьшения размера окна. Меню Help включает команду About, которая при выборе пользователем индицирует панель диалога About, описывающую Generic. Полностью Generic с панелью диалога About выглядит так, как показано на рис. 2.1. Рисунок 2.1. Generic: шаблон для создания прикладных программ для Windows. 1. Меню Help. 2. Панель диалога About. Generic важен не потому, что он может делать, но потому, что он обеспечивает: шаблон для написания прикладных программ в среде Windows. Создание этого примера поможет представить как объединяются прикладные программы и как они работают. 2.2 Прикладная программа в среде Windows. Прикладная программа Windows - это любая программа, которая специально написана для работы в операционной среде Windows и использующая интерфейс прикладной программы (API). . Windows 3.0/pg/1#3 = 21 = Прикладная программа Windows имеет следующие основные компоненты: - основную функцию WinMain; - функцию окна. Функция WinMain - это точка входа в программу; эта функция аналогична основной функции, используемой в стандартной среде С. Эта функция всегда называется WinMain. Функция окна - это для вас новое. Функция окна - это функция многократного вызова, т.е. функция вашей программы, которую вызывает Windows. Ваша программа никогда не вызывает эту функцию напрямую. Вместо этого она ожидает, пока ее не вызовет Windows для выполнения каких-либо действий или получения информации. 2.3 Функция WinMain. Без нее прикладная программа работать не может. Подобно основной функции в стандартной системе С WinMain является точкой входа в прикладную программу. Каждая прикладная программа должна иметь функцию WinMain. В большинстве случаев эта функция делает следующее: - вызывает функции инициализации, которые регистрируют классы окон, создают окна и выполняют другие инициализации; - запускает цикл обработки сообщений из очереди прикладной программы; - завершает программу при получении сообщения WM_QUIT. Функция WinMain имеет следующий вид: int PASCAL WinMain(hInstance, hPrevInstance, lpCmdLine, nCmdShow) HANDLE hInstance; /* текущий экземпляр */ HANDLE hPrevInstance; /* предыдущий экземпляр */ LPSTR lpCmdLine; /* командная строка */ int nCmdShow; /* тип представления окна (откры- тое или в виде иконы) */ { } Функция WinMain использует соглашения по вызову языка Паскаль. При запуске программы Windows передает функции WinMain четыре параметра: . Windows 3.0/pg/1#3 = 22 = Параметр Значение, передаваемое программе --------------------------------------------------------------- hInstance Дескриптор экземпляра программы. hPrevInstance Дескриптор предыдущего экземпляра программы, если она уже была запущена. Если это первый экземпляр, то Windows передает NULL. lpCmdLine Длинный указатель на оканчивающуюся нулем командную строку. nCmdShow Целое, передаваемое функции определяет в каком виде окно должно быть индицировано: как открытое окно или как икона. Это значение программа передает функции ShowWindow, когда возникает необходимость в отображении окна. Более подробная информация находится в пункте 2.3.2 "Дескрипторы". Дополнительную информацию о параметре lpCmdLine вы найдете в разделе 2.3.11 "Параметры командной строки программы". 2.3.1 Типы данных и структуры Windows. Функция WinMain использует несколько нестандартных типов данных для определения своих параметров. Например, тип данных HANDLE используется для определения параметров hInstance и hPrevInstance, а тип данных LPSTR используется для определения параметра lpCmdLine. Вообще говоря, Windows использует значительно больше типов данных, чем встречаются в обычных С-программах. Хотя типы данных Windows часто эквивалентны обычным типам данных языка С, они более мнемоничны и помогают лучше понять назначение данной переменной или параметра. Типы данных Windows определяются во включаемом файле windows.h. Включаемый файл - это обычный исходный файл на языке С, который содержит определения для всех специальных констант, переменных, структур данных и функций. Для использования этих определений необходимо подключить файл windows.h к исходному файлу путем размещения указанной ниже строки в начале исходного файла: #include "windows.h" /* необходима для всех программ */ Ниже приводится перечень нескольких наиболее используемых типов данных среды Windows: Тип Значение --------------------------------------------------------------- WORD Определяет 16-битовое беззнаковое целое; LONG Определяет 32-битовое целое со знаком; . Windows 3.0/pg/1#3 = 23 = HANDLE Идентифицирует 16-битовое беззнаковое целое, используемое как дескриптор; HWND Идентифицирует 16-битовое беззнаковое целое, используемое как дескриптор окна; LPSTR Определяет 32-битовый указатель на перемен- ную типа char; FARPROC Определяет 32-битовый указатель на функцию. --------------------------------------------------------------- Ниже приводится перечень некоторых наиболее часто используемых структур: Структура Описание --------------------------------------------------------------- MSG Определяет поля полученного сообщения; WNDCLASS Определяет класс окна; PAINTSTRUCT Определяет структуру изображения, используемую для рисования в окне; RECT Определяет прямоугольник. --------------------------------------------------------------- Полный список и описание всех структур и типов данных Windows приведен во втором томе "Справочного руководства". 2.3.2 Дескрипторы. Функция WinMain имеет два параметра: hPrevInstance и hInstance, которые называются дескрипторами. Дескриптор - это уникальное целое, которое Windows использует для идентификации объекта, создаваемого или используемого прикладной программой. Windows использует большое число дескрипторов, идентифицирующих такие объекты как экземпляры прикладной программы, окна, меню, блоки управления, распределяемую память, устройства вывода, файлы, перья и кисти GDI и многие другие. Большинство дескрипторов являются значениями индексов внутренних таблиц. Windows использует индексы дескрипторов для доступа к информации, содержащейся в таблице. Обычно прикладные программы имеют доступ только к дескиптору, а не к самим данным. Когда необходимо проверить или изменить данные, указывается дескриптор, и Windows делает необходимые действия. Т.о. Windows обеспечивает защиту данных при работе в многозадачном режиме. . Windows 3.0/pg/1#3 = 24 = 2.3.3 Управление экземплярами. Windows позволяет работать не только с несколькими прикладными программами одновременно, но и с несколькими копиями или "экземплярями" одной и той же прикладной программы. Для того, чтобы отличить одну копию от другой, Windows при вызове функции WinMain формирует уникальный дескриптор экземпляра. Экземпляр - это отдельно выполняющаяся копия программы, а дескриптор экземпляра - это целое, которое однозначно определяет экземпляр. В некоторых многозадачных системах при работе нескольких копий одной и той же программы система каждый раз загружает в память новую копию кода и данных этой программы и обрабатывает ее. Windows же при запуске нового экземпляра программы загружает только данные, т.е. эта система использует тот же самый код для всех экземпляров программы. Этот способ значительно экономит память для других программ. Однако необходимо, чтобы во время работы программы ее кодовый сегмент оставался неизменным. Это означает, что нельзя хранить данные в кодовом сегменте или изменять его во время выполнения. Для большинства программ первый экземпляр имеет особую роль. Поскольку программа создает много таких ресурсов, которые доступны для всех программ (например, классы окна), только первый экземпляр программы создает эти ресурсы. Все последущие экземпляры используют эти ресурсы, не создавая их. Для того, чтобы определить какой из экземпляров является первым, Windows устанавливает, параметр hPrevInstance функции WinMain в NULL, если нет предыдущих экземпляров. Ниже дается пример, как проверить, имеется ли предыдущий экземпляр: int PASCAL WinMain(hInstance, hPrevInstance, lpCmdLine, nCmdShow) HANDLE hInstance; HANDLE hPrevInstance; LPSTR lpCmdLine; int nCmdShow; { if (!hPrevInstance) . . . } Можно помешать пользователю запустить более одного экземпляра программы, проверив параметр hPrevInstance, когда программа запускается и сразу возвращает управление Windows, если значение этого параметра - не NULL. Приведенный ниже пример показывает, как это сделать: if (hPrevInstance) return (NULL); . Windows 3.0/pg/1#3 = 25 = 2.3.4 Регистрация класса окна. Перед созданием окна необходимо определить его класс. Класс окна - это шаблон, определяющий атрибуты окна, такие, например, как форма курсора и имя меню окна. Класс окна определяет также функцию окна, которая будет обрабатывать сообщения для всех окон этого класса. Хотя Windows обеспечивает некоторые встроенные классы окон, большинство прикладных программ определяют свои собственные классы для управления всеми аспектами различных способов работы с окнами. Прежде, чем создавать окно, принадлежащее этому классу, необходимо зарегистрировать класс окна. Регистрация класса окна - это заполнение структуры WNDCLASS информацией о классе и передача ее в качестве параметра функции RegisterClass. Заполнение структуры WNDCLASS. Структура WNDCLASS передает Windows информацию об имени, атрибутах, ресурсах и функции окна данного класса. Структура WNDCLASS содержит следующий поля. Поле Описание ---------------------------------------------------------- lpszClassName Указывает на имя класса окна. Имя класса окна должно быть уникальным; т.е. различные программы должны иметь различные имена классов. hInstance Определяет экземпляр программы, который регистрирует класс. lpfnWndProc Указывает на функцию окна, используемую для обработки окна. style Определяет тип класса, такой как авто- матическая перерисовка окна при перемещении или изменении размеров. hbrBackgroung Определяет кисть, используемую для ри- сования фона окна. hCursor Определяет курсор, используемый в окне. hIcon Определяет икону, используемую для пре- дставления окна. lpszMenuName Указывает на имя ресурса меню. cbClsExtra Определяет число дополнительных байт, отведенных под эту структуру. clWndExtra Определяет число дополнительных байт, отведенных под все структуры, созданные для данного класса. ---------------------------------------------------------- . Windows 3.0/pg/1#3 = 26 = Дополнительную информацию об этих полях вы найдете во втором томе "Справочного руководства". Некоторым полям, таким как lpszClass, hInstance и lpfnWndProc, обязательно должны быть присвоены значения. Другие могут быть установлены в NULL для использования атрибутов по умолчанию для окон, принадлежащих этому классу. Ниже приводится пример, как зарегистрировать класс окна: BOOL InitApplication(hInstance) HANDLE hInstance; { 1) WNDCLASS WC; /* заполнение структуры класса окна, параметрами описывающими основное окно */ 2) WC.style = NULL; /* тип окна */ 3) WC.lpfnWndProc = MainWndProc; /* функция окна */ 4) WC.cbClsExtra = NULL; /* нет дополнительных данных */ WC.clWhdExtra = NULL; 5) WC.hInstance = hInstance; /* экземпляр, владелец класса */ 6) WC.hIcon = LoadIcon(NULL, IDI_APPLICATION); 7) WC.hCursor = LoadCursor(NULL, IDC_ARROW); 8) WC.hbrBackground = GetStockObject(WHITE_BRUSH); 9) WC.lpszMenuName = "Generic"; /* имя меню в .RC-файле */ 10) WC.lpszClassName = "GenericWndClass"; /* имя класса */ /* регистрация класса */ return(RegisterClass(&WC)); } 1) В данном примере в начале объявляется структура WNDCLASS c именем WC. 2) Поле style устанавливается в NULL. 3) Поле lpfnWndProc содержит указатель на функцию окна с именем MainWndProc. Это означает, что функция MainWndProc будет получать сообщения, которые Windows будет посылать окну, и эта функция будет обрабатывать эти сообщения. Для того, чтобы присвоить адрес функции GenericWndProc полю lpfnWndProc, необходимо объявить эту функцию где-либо перед оператором присваивания. Большинство программ используют прототипы фукций для их объявления для того, чтобы использовать преимущества автоматического контроля и . Windows 3.0/pg/1#3 = 27 = приведения типов при трансляции. Ниже приводится пример правильного прототипа функции GenericWndProc: long FAR PASCAL MainWndProc(HWND, unsigned, WORD, LONG); Отметим, что MainWndProc должна быть описана как экспортируемая в файле определения модуля. 4) Поля cbClsExtra и cbWndExtra установлены в NULL, поскольку не требуется дополнительная память для каждого класса и для каждого окна. (Вы можете занести сюда ненулевое значение, и затем использовать эту дополнительную память для каждого окна. Смотрите главу 16 "Еще об управлении памятью", в которой описано использование этого дополнительного пространства.) 5) Поле hInstance содержит экземпляр, т.е. дескриптор, посы- лаемый Windows прикладной программе при запуске. 6) Поле hIcon получает дескриптор встроенной иконы. Функция LoadIcon возвращает дескриптор или встроенной, или определяемой пользователем иконы. В данном случае аргументы NULL и IDI_APPLICATION определяют встроенную икону. (В большинстве программ используются собственные иконы. В главе 5 "Иконы" описано, как создавать и использовать собственные иконы.) 7) Поле hCursor получает дескриптор встроенного курсора. Функция LoadCursor возвращает дескрипторы встроенных или определенных прикладной программой курсоров. Здесь аргументы NULL и IDC_ARROW определяют встроенный курсор в виде стрелки. (Некоторые программы используют свои собственные курсоры. Глава 6 "Курсор, мышь и клавиатура" описывает, как можно создать и использовать свой собственный курсор.) 8) Поле hbrBackground определяет цвет кисти, которую Windows будет использовать при рисовании фона окна. В данном случае программа использует функцию GetStockObject для получения дескриптора стандартной белой кисти. 9) Поле lpszMenuName определяет имя меню для окон данного класса, "GenericMenu". Это меню будет в дальнейшем использоваться во всех окнах данного класса. Если окно не должно иметь меню, данному полю присваивают значение NULL. 10) Поле lpszClassName определяет строку "GenericWndClass" как имя данного класса окон. Регистрация класса окна. После присваивания значений полям структуры WNDCLASS зарегистрируйте класс, используя функцию RegisterClass. Если . Windows 3.0/pg/1#3 = 28 = регистрация прошла успешно, функция возвращает TRUE. В противном случае она возвращает FALSE. Следует проверить возвращаемое значение, поскольку если класс не зарегистрирован, окно создавать нельзя. Хотя функция RegisterClass требует 32-битовый указатель на структуру WNDCLASS, в предыдущем примере знак операции взятия адреса (&) генерирует только 16-битовый адрес. Это пример неявного приведения типов, выполняемого С-компилятором. Включаемый файл Windows содержит прототипы всех его функций. Эти прототипы определяют правильные типы для каждого параметра функции, и С-компилятор делает приведение к этим типам автоматически. 2.3.5 Создание окна. Можно создать окно с помощью функции CreateWindow. Эта функция побуждает Windows создать окно, которое имеет указанный тип и принадлежит к определенному классу. Функция CreateWindow имеет несколько параметров: - имя класса окна; - заголовок окна; - тип окна; - расположение окна; - дескриптор родительского окна; - дескриптор меню; - дескриптор экземпляра; - 32 бита дополнительных данных. В приведенном ниже примере создается окно, принадлежащее классу "GenericWndClass": hWnd = CreateWindow( (1) "GenericWndClass", /* класс окна */ (2) "Generic Sample Application", /* имя окна */ (3) WS_OVERLAPPEDWINDOW, /* тип окна */ (4) CW_USEDEFAULT, /* координата x */ CW_USEDEFAULT, /* координата y */ CW_USEDEFAULT, /* ширина */ CW_USEDEFAULT, /* высота */ (5) NULL, /* дескриптор родите- льского окна */ (6) NULL, /* ID меню или дочер- него окна */ (7) hInstance, /* экземпляр */ . Windows 3.0/pg/1#3 = 29 = (8) NULL); /* добавочная информа- ция */ В данном примере создается перекрывающееся окно, имеющее тип WS_OVERLAPPEDWINDOW и принадлежащее классу окна, созданному в предыдущем примере. В данном случае: 1) Первый параметр функции CreateWindow определяет имя класса окна, который Windows должна использовать при создании окна. В этом примере имя класса "GenericWndClass". 2) Второй параметр определяет заголовок: "Generic Sample Application". 3) Тип WS_OVERLAPPEDWINDOW определяет окно как обычное пере- крываемое окно. 4) Следующие четыре параметра определяют положение и размеры окна. Поскольку значение CW_USEDEFAULT определяется для параметров ширины и высоты окна и его координат, окно будет иметь расположение, ширину и высоту по умолчанию. Координаты окна и его размеры по умолчанию зависят от системы и от того, какое количество программ работает в среде. (Отметим, что Windows не индицирует окно до тех пор, пока не будет вызвана функция ShowWindow.) 5) При создании окна можно специфицировать его родителя (используемого с блоками управления и дочерними окнами). Перекрывающееся окно не должно иметь родителя, так что этот параметр должен быть установлен в NULL. 6) Если при создании окна вы укажете меню, то это меню заме- нит меню класса (если оно есть). Поскольку это окно должно использовать меню класса, то данный параметр равен NULL. 7) Необходимо указать экземпляр программы, который создает окно. Windows использует этот экземпляр для того, чтобы функция окна, поддерживающая данное окно, использовала данные именно этого экземпляря, а не какие-либо другие. 8) Последний параметр - для дополнительных данных, которые будут использоваться функцией окна при создании окна. Поскольку окно не использует дополнительных данных, параметр установлен в NULL. Когда CreateWindow функция создает окно, (т.е. заканчивается успешно), она возвращает дескриптор нового окна. Этот дескриптор может быть использован для выпонения операций над окнами, таких, например, как их индицирование или корректировка их области пользователя. Если функция CreateWindow не может создать окно, она . Windows 3.0/pg/1#3 = 30 = возвращает NULL. Каждый раз при создании окна необходимо контролировать дескриптор на NULL и реагировать соответствующим образом. Например, если нельзя создать основное окно ПП, находясь в функции WinMain, необходимо завершить программу, т.е. возвратить управление Windows. 2.3.6 Отображение и корректировка окна. Хотя функция CreateWindow и создает окно, она автоматически не индицирует его. Прикладная программа должна предусмотреть индикацию окна с помощью функции ShowWindow и корректировку области пользователя окна с помощью функции UpdateWindow. Функция ShowWindow побуждает Windows индицировать новое окно с дескриптором hWnd. Для основного окна программы функция WinMain должна вызвать функцию ShowWindow вскоре после создания окна и передать ей параметр nCmdShow. Этот параметр определяет, как индицируется окно: как открытое окно или как икона. Затем функция WinMain должна вызвать UpdateWindow. В приведенном ниже примере показано, как отобразить и скорректировать окно: ShowWindow (hWnd, nCmdShow); /* отображение окна */ UpdateWindow (hWnd); /* посылка сообщения WM_PRINT */ Примечание: Обычно, параметр nCmdShow может быть установлен в любое значение с помощью констант, начинающихся на SW_ (определены в WINDOWS.H). Имеется единственное исключение при индицировании основного окна с помощью функции ShowWindow: ей передается параметр nCmdShow, который WinMain получает от Windows. (Полный список этих констант вы найдете в первом томе "Справочного руководства"). 2.3.7 Создание цикла обработки сообщений. Как только функция WinMain создала и индицировала окно, она может выполнять свою основную задачу - читать сообщения из очереди прикладной программы и направлять их соответствующему окну. Функция WinMain делает это с помощью цикла обработки сообщений. Цикл обработки сообщений - это программный цикл, обычно создаваемый с использованием оператора while, при работе в котором функция WinMain находит сообщения и направляет их соответствующим функциям. Windows не посылает вводимую информацию непосредственно прикладной программе. Вместо этого он помещает информацию, вводимую с "мыши" и клавиатуры, в очередь прикладной программы (вместе с сообщениями, посылаемыми Windows и другими программами). Программа должна читать очередь, находить сообщения и передавать их так, чтобы соответствующая функция окна могла эти сообщения обработать. . Windows 3.0/pg/1#3 = 31 = Наиболее простой цикл обработки сообщений состоит из функций GetMessage и DispatchMessage. Этот цикл имеет следующий вид: MSG msg; . . . while (GetMessage(&msg, NULL, NULL, NULL)) { DispatchMessage(&msg); } В этом примере функция GetMessage получает сообщение из очереди прикладных программ и копирует его в структуру msg. Аргументы NULL указывают, что должны быть обработаны все сообщения. Функция DispatchMessage побуждает Windows послать каждое сообщение соответствующей функции окна. Каждое сообщение, которое получает функция окна (за исключением сообщения WM_QUIT), принадлежит одному из окон, созданных прикладной программой. Поскольку программа не должна вызывать непосредственно функцию окна, необходима функция DispatchMessage для передачи сообщений соответствующей функции. В зависимости от назначения программы можно иметь и более сложный цикл обработки сообщений. В частности, если необходимо обработать вводимые с клавиатуры символы, нужно преобразовывать каждое полученное сообщение с помощью функции TranslateMessage. Теперь цикл обработки сообщений будет выглядеть так: while (GetMessage(&msg, NULL, NULL, NULL)) { TranslateMessage(&msg); DispatchMessage(&msg); } Функция TranslateMessage отыскивает пары сообщений WM_KEYDOWN и WM_KEYUP и генерирует соответствующее сообщение WM_CHAR для того окна, которое содержит значение этого символа в коде ANSI для заданной клавиши. Цикл обработки сообщений может также содержать функции для обработки ускорителей меню и мягких клавиш внутри панели диалога. И это также зависит от назначения программы. Windows помещает в очередь прикладной программы сообщения о вводе, когда пользователь перемещает в окне курсор мыши, нажимает или отпускает кнопку мыши, находясь в окне, нажимает или освобождает клавишу на клавиатуре, если окно захватило ввод. Система управления окнами вначале собирает всю вводимую с клавиатуры и мыши информацию в системную очередь, а затем копирует соответсвующие сообщения в очередь прикладной программы. Обработка цикла сообщений продолжается до тех пор, пока . Windows 3.0/pg/1#3 = 32 = GetMessage не возвратит NULL, что происходит в том случае, если найдено сообщение WM_QUIT, сигнализирующее о завершении программы. Оно обычно посылается (помещается в очередь прикладной программы) функцией основного окна прикладной программы. 2.3.8 Передача управления. Windows не является приоритетной многозадачной средой. Это означает, что она не может забрать управление у программы. Прикладная программа сама должна вернуть управление, прежде чем Windows сможет передать управление другой программе. Для того, чтобы все программы имели равный доступ к процессору, функция GetMessage автоматически передает управление, если в очереди прикладной программы отсутствуют сообщения. Это означает, что если программе нечего делать, Windows может передать управление другой прикладной программе. Поскольку все программы имеют цикл обработки сообщений, эта неявная передача управления гарантирует разделение управления. В общем, следует положиться на функцию GetMessage в смысле разделения управления. Хотя имеется и явная функция Yield, следует избегать ее использования. Поскольку могут существовать моменты, когда программа должна держать управление в течение длительного времени (например, во время записи большого буфера на дисковый файл), следует попытаться минимизировать эту работу и обеспечить для пользователя визуальный контроль над длительными операциями. 2.3.9 Завершение прикладной программы. Программа завершает работу, когда функция WinMain возвращает управление Windows. Управление можно возвратить в любой момент перед входом в цикл обработки сообщений. Обычно программа контролирует каждый шаг, подготавливающий к циклу обработки сообщений, для того, чтобы быть уверенным, что каждый класс окна зарегистрирован и каждое окно создано. Если произошла ошибка, программа может отобразить сообщение об ошибке перед завершением работы. Однако, как только функция WinMain войдет в цикл обработки сообщений, единственным способом завершить цикл является посылка ею (с помощью функции PostQuitMessage) сообщения WM_QUIT в очередь прикладной программы. Когда функция GetMessage находит сообщение WM_QUIT, она возвращает NULL, что завершает цикл обработки сообщений. По соглашению, сообщение WM_QUIT посылает только функция WinMain и только тогда, когда основное окно разрушено (т.е. когда функция окна получила сообщение WM_DESTROY). Хотя WinMain определяет тип возвращаемого значения, Windows обычно его не использует. Однако, возвращаемое значение . Windows 3.0/pg/1#3 = 33 = может быть полезным во время отладки программ. Вообще говоря, можно использовать те же соглашения о коде возврата, что и для обычных С-программ: нуль для успешного завершения выполнения, ненулевое значение - для ошибки. Функция PostQuitMessage позволяет функции окна указать возвращаемое значение. Это значение затем копируется в поле wParam сообщения WM_QUIT. Для возврата этого значения после завершения цикла обработки сообщений используйте следующий оператор: return (msg.wParam); /* возвращает значение из функции PostQuitMessage */ Хотя стандартные С-программы обычно закрывают и освобождают ресурсы непосредственно перед завершением работы, программы в среде Windows должны быть готовы к закрытию по мере разрушения каждого окна. Если этого не сделать, то можно потерять некоторые данные. Например, если сама Windows завершает работу, она разрушает каждое окно, но не возвращает управление циклу обработки сообщений прикладной программы. Это означает, что цикл не получит сообщение WM_QUIT, и операторы после цикла не выполнятся. В действительности, Windows посылает каждой программе сообщение перед завершением работы, так что прикладная программа имеет возможность выполнить все необходимые действия (в главе 10 "Ввод и вывод в файл" поясняется работа сообщения WM_QUERYENDSESSION). 2.3.10 Функции инициализации. Большинство программ используют две локальные функции инициализации: - Основная функция инициализации выполняет те задачи, вы- полнение которых необходимо только один раз, для всех экземпляров программы (например регистрация классов окон). - Функция инициализации экземпляра программы выполняет за- дачи, необходимые для инициализации каждого экземпляра программы. Использование функций инициализации позволяет вам сохранить функцию WinMain простой и удобочитаемой; они также позволяют вам организовать инициализацию задач так, чтобы они могли быть помещены в отдельный кодовый сегмент и сброшены после использования. Программа Generic не сбрасывает функции инициализации. (В главе 15, "Управление памятью", описана простая программа "Memory", которая сбрасывает функции инициализации.) BOOL InitApplication(hInstance) HANDLE hInstance; /* текущий экземпляр */ { WNDCLASS WC; . Windows 3.0/pg/1#3 = 34 = /* заполнение структуры класса окна параметрами, описывающими основное окно */ WC.style = NULL; /* тип окна */ WC.lpfnWndProc = MainWndProc; /* функция окна */ WC.cbClsExtra = 0; /* нет дополнительных данных */ WC.clWhdExtra = 0; WC.hInstance = hInstance; /* экземпляр, владелец класса */ WC.hIcon = LoadIcon(NULL, IDI_APPLICATION); WC.hCursor = LoadCursor(NULL, IDC_ARROW); WC.hbrBackground = GetStockObject(WHITE_BRUSH); WC.lpszMenuName = "Generic"; /* имя меню в .RC-файле */ WC.lpszClassName = "GenericWndClass"; /* имя класса */ /* регистрация класса */ return(RegisterClass(&WC)); } BOOL InitInstance(hInstance,nCmdShow) HANDLE hInstance; /* текущий экземпляр */ int nCmdShow; /* Параметр функции ShowWindow */ { HWND hWnd; /* Дескриптор основного окна */ /* Сохранить номер экземпляра в статической переменной, которая используется в последующих вызовах Windows */ hInst = hInstance; /* Создать основное окно для данного экземпляра программы */ hWnd = CreateWindow( "GenericWndClass", /* класс окна */ "Generic Sample Application", /* имя окна */ WS_OVERLAPPEDWINDOW, /* тип окна */ CW_USEDEFAULT, /* координата x */ CW_USEDEFAULT, /* координата y */ CW_USEDEFAULT, /* ширина */ CW_USEDEFAULT, /* высота */ NULL, /* дескриптор родите- льского окна */ NULL, /* ID меню или дочер- него окна */ hInstance, /* экземпляр */ NULL); /* добавочная информа- ция */ /* если окно не создано, возвращается ошибка */ . Windows 3.0/pg/1#3 = 35 = if(!hWnd) return (FALSE); /* Сделать окно видимым. Перерисовать область пользователя и вернуть код успешной операции */ ShowWindow(hWnd,nCmdShow); /* Отобразить окно */ UpdateWindow(hWnd); /* Перерисовать область пользователя */ return (TRUE); } 2.3.11 Командная строка прикладной программы. Можно проверить командную строку, использующуюся для запуска прикладной программы, с помощью параметра lpCmdLine. Этот параметр указывает на начало массива символов, который содержит команды точно в том виде, в каком они были напечатаны пользователем. В отличие от С-программ командная строка автоматически не разбивается на отдельные поля. Если необходимо извлечь из командной строки имя файла или параметры, надо написать соответствующие операторы. Вы можете использовать вместо этого параметра переменные __argc и __argv. Дополнительную информацию вы найдете в главе 14, "Язык С и язык ассемблера". 2.4 Функция окна. Каждое окно должно иметь функцию окна. Функция окна обеспечивает реакцию на вводимую информацию и сообщения системы управления окнами, получаемые от Windows. Функция окна может быть простой, обрабатывающей только одно или два сообщения; она может быть и сложной, обрабатывающей большое число типов сообщений для ряда окон прикладной программы. Функция окна имеет следующий вид: long FAR PASCAL MainWndProc(hWnd, message, wParam, lParam) HWND hWnd; /* дескриптор окна */ unsigned message; /* тип сообщения */ WORD wParam; /* дополнительная информация */ LONG lParam; /* дополнительная информация */ { . . . switch (message) { . . . default: /* если не обрабатывается */ return (DefWindowProc(hWnd, message, wParam, . Windows 3.0/pg/1#3 = 36 = lParam)); } return(NULL); } Функция окна использует соглашение по вызову языка PASCAL. Поскольку Windows вызывает эту функцию непосредственно и всегда использует эти соглашения, требуется использовать ключевое слово PASCAL. В определении функции окна имеется ключевое слово FAR, поскольку Windows использует при вызове 32-битовый адрес. Кроме того, необходимо поименовать функцию окна в операторе EXPORTS файла определения модуля. Смотрите раздел 2.6 "Создание файла определения модуля". Функция окна получает сообщения от Windows. Это могут быть сообщения о вводе, переданные функцией WinMain, или сообщения системы управления окнами, поступающие непосредственно от Windows. Функция окна должна проверить каждое сообщение и либо выполнить некоторые специальные действия, основываясь на сообщениях, либо передать сообщение обратно Windows для обработки по умолчанию с помощью функции DefWindowProc. Параметр message определяет тип сообщения. Этот параметр используется в операторе switch для выбора нужного фрагмента и соответствующей ему обработки. Параметры lParam и wParam содержат дополнительную информацию о сообщении. Функция окна обычно использует эти параметры для выполнения запрашиваемых действий. Если функция окна не обрабатывает сообщение, она должна передать его функции DefWindowProc. Это дает уверенность в том, что будут выполнены любые действия, влияющие на окно, программу или на Windows. Большинство функций окна обрабатывает сообщение WM_DESTROY. Windows посылает это сообщение функции окна сразу же после разрушения окна. Сообщение предоставляет функции окна возможность закончить обработку и, если эта функция основного окна программы, послать сообщение WM_QUIT в очередь прикладной программы. В приведенном ниже примере показано, как функция основного окна должна обработать это сообщение: case WM_DESTROY: PostQuitMessage(0); break; Функция PostQuitMessage помещает сообщение WM_QUIT в очередь прикладной программы. Когда функция GetMessage получает это сообщение, она завершает работу цикла обработки сообщений и прикладной программы. Функция окна получает сообщения из двух источников: сообщения о вводе из цикла обработки сообщений и сообщения системы управления окнами из Windows. Сообщения о вводе имеют место при вводе от "мыши", с клавиатуры или таймера. Типичным . Windows 3.0/pg/1#3 = 37 = сообщением о вводе являются: WM_KEYDOWN, WM_KEYUP, WM_MOUSEMOVE и WM_TIMER, которые явно соответствуют вводу с устройств ввода. Windows посылает сообщения системы управления окнами непосредственно функции окна в обход очереди прикладной программы или цикла обработки сообщений. Эти сообщения обычно являются запросами на то, чтобы функция окна выполнила некоторые действия, такие, например, как рисование в области пользователя или выдача информации об окне. Сообщения могут также информировать функцию окна об изменениях, которые Windows делает с окном. Типичными сообщениями системы управления окнами являются: WM_CREATE, WM_DESTROY и WM_PAINT. Функция окна должна возвратить длинное значение. Это возвращаемое значение зависит от получаемых сообщений. В первом томе Справочного руководства описаны возвращаемые значения, когда они имеют смысл. (Для большинства сообщений возвращаемое значение необязательно.) Если функция окна не обрабатывает сообщение, ее возвращаемое значение представляет собой возвращаемое значение функции DefWindowProc. 2.5 Создание панели диалога About. Документ System Application Architecture, Common User Access: Advanced Interface Design Guide, рекомендуется в каждую прикладную программу включать панель диалога About. Панель диалога - это временное окно, которое индицирует информацию и запрашивает ввод от пользователя. Панель диалога About индицирует имя прикладной программы и информацию об авторском праве. Пользователь индицирует панель диалога About, выбирая из меню команду About. ( Соглашения по поводу панелей дилога About вы найдете в документе System Application Architecture, Common User Access: Advanced Interface Design Guide.) Создание и индицирование панели диалога осуществляется с помощью функции DialogBox. Эта функция для создания панели диалога использует шаблон панели диалога, адрес экземпляра процедуры и дескриптор родительского окна. Функция создает панель диалога, с помощью которой вы можете выводить и вводить данные. Для индицирования и использования панели диалога необходимо выполнить следующие шаги: 1) создать шаблон панели диалога и добавить его к файлу описания ресурсов; 2) создать функцию диалога и добавить ее к исходному С-файлу; 3) экспортировать функцию диалога в файле определения модуля; . Windows 3.0/pg/1#3 = 38 = 4) добавить меню в файл описания ресурсов; 5) обработать сообщение WM_COMMAND в прикладной программе. После выполнения указанных шагов пользователь может вывести панель диалога About при выборе команды About из меню. В следующих разделах эти шаги разбираются более подробно. 2.5.1 Создание шаблона панели диалога. Шаблон панели диалога - это текстовое описание типа, содержимого, формы и размера панели диалога. Можно создавать шаблон вручную или с помощью Windows 3.0 Dialog Editor. В данном примере шаблон создается вручную. Использование Dialog Editor описано в "Tools". Шаблон панели диалога создается в файле описания ресурсов. Файл описания ресурсов содержит определения ресурсов, которые будут использоваться прикладной программой, таких как иконы, курсоры и шаблоны панели диалога. Для создания шаблона панели диалога About необходимо использовать оператор DIALOG и заполнить его операторами блоков управления, как показано ниже: (1) AboutBox DIALOG 22, 17, 144, 75 (2) STYLE DS_MODALFRAME | WS_CAPTION | WS_SYSMENU CAPTION "About Generic" (3) BEGIN (4) CTEXT "Microsoft Windows" -1, 0, 5, 144, 8 CTEXT "Generic Application" -1, 0, 14, 144, 8 CTEXT "Version 3.0" -1, 0, 34, 144, 8 (5) DEFPUSHBUTTON "ВК" IDOK, 53, 59, 32, 14, WS_GROUP END 1) Оператор DIALOG начинает шаблон панели диалога. Имя AboutBox идентифицирует шаблон, когда для создания панели диалога используется функция DialogBox. Верхний левый угол панели размещается в точке с координатами (22,17) области пользователя родительского окна. Панель имеет ширину 144 единицы и высоту 75 единиц. Единица ширины панели диалога равна одной четверти ширины символа системного шрифта. Единица высоты панели диалога равна одной восьмой высоты символа системного шрифта. Функция GetDialogBaseUnits возвращает размер единицы панели диалога в пикселях. 2) Оператор STYLE определяет тип панели диалога. В частности, в данном примере тип - накладываемое окно с окантовкой в виде рамки и системным меню. Этот тип обычно используется в модальных панелях диалога. 3) Операторы BEGIN и END помечают начало и конец определений блоков управления. Панель диалога содержит текст и альтернативную клавишу по умолчанию. Альтернативная . Windows 3.0/pg/1#3 = 39 = клавиша дает возможность пользователю послать информацию функции диалога для завершения работы панели диалога. Операторы, строки и целые, содержащиеся между операторами BEGIN и END, описывают содержимое панели диалога. Поскольку вы обычно создаете панели диалога с помощью Dialog Editor то в данном руководстве не приводится информация о числах и операторах, используемых в описании. Смотрите полное описание Dialog Editor в "Tools". 4) Оператор CTEXT создает прямоугольник с текстом в кавычках, расположенным в центре прямоугольника. Этот оператор встречается несколько раз с различным текстом, который появляется в панели диалога. 5) Оператор DEFPUSHBUTTON создает альтернативную клавишу, ко- торая дает возможность пользователю дать ответ по умолчанию. В этом случае при выборе мягкой клавиши "OK" панель диалога исчезает. Константы DS_MODALFRAME, WS_CAPTION, WS_SYSMENU и IDOK, используемые в шаблоне панели диалога, определены во включаемом файле среды Windows. Следует включить этот файл в файл описания ресурсов, использовав директиву #include в начале файла. Операторы данного файла были созданы с помощью текстового редактора и основывались на панели диалога, используемой в другой программе. Вы можете многие ресурсы копировать из других программ и модифицировать с помощью текстового редактора. Вы можете также создавать панели диалога с помощью Dialog Editor. (Файлы, созданные Dialog Editor, содержат операторы, которые несколько отличаются от приведенных здесь. Такие файлы обычно редактируются только с помощью Dialog Editor. Более подробное описание использования Dialog Editor для создания панелей диалога приводится в "Tools".) 2.5.2. Создание включаемого файла Часто бывает полезно создать включаемый файл, в котором определяются константы и прототипы функций для вашей программы. Большинство прикладных программ содержат по крайней мере два исходных файла, разделяющих общие константы: исходный С-файл и файл описания ресурсов. Поскольку компилятор ресурсов rc выполняет ту же самую предобработку, что и С-компилятор, полезно и удобно поместить определение констант в единственный включаемый файл и затем включить его как в исходный С-файл, так и в файл описания ресурсов. Например, для программы Generic можно поместить прототипы функций WinMain, MainWndProc, About и InitApplication и InitInstnce, а также определение ID меню для команды About во включаемый файл Generic.h. Файл будет выглядеть так: . Windows 3.0/pg/1#3 = 40 = #define IDM_ABOUT 100 int PASCAL WinMain(HANDLE, HANDLE, LPSTR, int); BOOL InitApplication(HANDLE); BOOL InitInstance(HANDLE,int); long FAR PASCAL MainWndProc(HWND, unsigned, WORD, LONG); BOOL FAR PASCAL About(HWND, unsigned, WORD, LONG); Поскольку данный включаемый файл ссылается на типы данных среды Windows, то его необходимо вставить после того, как подсоединен включаемый файл WINDOWS.H. Другими словами, начало файла ресурсов должно выглядеть так: #include "windows.h" /* требуется для всех программ */ #include "generic.h" /* нужен для данной программы */ 2.5.3 Создание функции диалога. "Панель диалога" - это специальное окно, функция обработки которого встроена в Windows. Каждая панель диалога должна иметь функцию диалога. Встроенная функция обработки панелей диалога вызывает функцию диалога для обработки сообщений, которые могут быть обработаны только прикладной программой. Функция, которая обрабатывает вводимую в панель диалога About информацию называется About. Функция About подобно другим функциям диалога использует те же самые параметры, что и функция окна, но обрабатывет только сообщения, которые специфичны для панели диалога. (Функция диалога возвращает TRUE, если обработала сообщение, и FALSE в противном случае.) Функция диалога как и обычная функция окна использует соглашение по вызову языка PASCAL и объявляется как FAR. Вы должны также указать эту функцию в операторе EXPORTS файла определения модуля. Как и функцию окна вы не должны явно вызывать в прикладной программе функцию диалога. В отличие от функции окна функция About обычно обрабатывает только сообщения о вводе от пользователя (такие, например, как WM_COMMAND) и не должна посылать необработанные сообщения функции DefWindowProc. Функция About программы Generic выглядит так: BOOL FAR PASCAL About(hDlg, message, wParam, lParam) HWND hDlg; /* дескриптор окна панели диалога */ unsigned message; /* тип сообщения */ WORD wParam; /* информация, зависящая от типа */ LONG lParam; /* сообщения */ { switch (message) { case WM_INITDIALOG: /* сообщение: инициализация панели диалога */ return (TRUE); . Windows 3.0/pg/1#3 = 41 = case WM_COMMAND: /* сообщение: получение команды */ if (wParam == IDOK|| wParam == IDCUNCEL) { /* выбрана па- нель "ОК"? или "Close" */ EndDialog(hDlg, NULL); /* Завершение работы с па- нелью диало- га */ return (TRUE); } break; } return (FALSE); /* сообщение не обработано */ } Функция About обрабатывает два сообщения: WM_COMMAND и WM_INITDIALOG. Сообщение WM_INITDIALOG посылается Windows функции диалога для того, чтобы функция подготовилась к индицированию панели диалога. В этом случае WM_INITDIALOG возвращает TRUE, так что ввод захватывает первый блок управления в панели диалога, в котором установлен бит WS_TABSTOP (это будет альтернативная клавиша по умолчанию). Если WM_INITDIALOG возвратит FALSE, то ни один блок управления не захватит ввод. В отличие от этого сообщения WM_COMMAND являются результатом ввода от пользователя. About отвечает на выбор клавиши "OK" или "Close" в системном меню вызовом функции EndDialog, которая удаляет панель диалога, и Windows продолжает выполнение прикладной программы. Функция EndDialog используется для завершения работы панели диалога. 2.5.4 Определение меню с элементом About. Теперь, когда имеется панель диалога About, необходимо дать пользователю возможность указать, когда ее индицировать. В большинстве прикладных программ команда About добавляется как последняя команда в меню Help. Если прикладная программа не имеет меню Help, то команда добавляется в первое меню, чаще всего это меню File. В Generic есть только команда About, которая добавляется как элемент меню Help. Чаще всего меню создают, определяя его ресурс в файле описания ресурсов. Поместите следующие операторы в файл описания ресурсов: GenericMenu MENU BEGIN POPUP "&Help" BEGIN MENUITEM "About Generic...",IDM_ABOUT . Windows 3.0/pg/1#3 = 42 = END END Эти операторы описывают меню с именем "GenericMenu" и с единственной командой Help. Эта команда отображает выпадающее меню с единственным элементом "About Generic...". Отметим знак амперсанда "&" в строке "&Help". Этот символ непосредственно предшествует мнемонике команды. Мнемоника - это уникальный символ или цифра, с помощью которой пользователь может взаимодействовать с меню или командами. Это часть прямого доступа к Windows. Если пользователь нажимает клавишу мнемоники вместе с клавишей ALT, Windows выбирает меню или команду. В случае "&Help" Windows заменяет знак амперсанда на подчеркивание символа, которому он предшествует, в данном случае "H", при выводе меню. Пользователь может увидеть команду About при выборе меню Help. Если пользователь выбирает команду About, Windows посылает функции окна сообщение WM_COMMAND, содержащее идентификатор команды About, в данном случае IDM_ABOUT. 2.5.5 Обработка сообщения WM_COMMAND. После добавления команды к меню Generic вам необходима возможность ответить на выбор пользователем этой команды. Для этого необходимо обработать сообщение WM_COMMAND. Windows посылает это сообщение функции окна, когда пользователь выбирает команду из меню прикладной программы. Windows помещает ID команды меню в параметр wParam, так что можно проконтролировать, какая команда выбрана. Можно проверить этот параметр, используя оператор switch. Если параметр равен IDM_ABOUT (ID команды About), то индицируется панель диалога. В случае любого другого значения необходимо передать сообщение функции DefWindowProc. Если этого не сделать, то все другие команды меню сделаются недоступными. Фрагмент WM_COMMAND должен выглядеть так FARPROC lpProcAbout; . . . case WM_COMMAND: /* сообщение: команда системного меню */ if (wParam == IDM_ABOUT) { (1) lpProcAbout = MakeProcInstance(About,hInst); (2) DialogBox(hInst, /* текущий экземп- ляр */ "AboutBox", /* используемый ре- сурс */ hWnd, /* дескриптор роди- тельского окна */ . Windows 3.0/pg/1#3 = 43 = lpProcAbout); /* адрес экземпляра функции About()*/ (3) FreeProcInstance(lpProcAbout); break; } else /* обработка сообщений Windows */ return (DefWindowProc(hWnd, message, wParam, lParam)); (1) Для индицирования панели диалога необходим адрес экземпля- ра функции диалога. Адрес экземпляра процедуры создается с помощью функции MakeProcInstance. Эта функция связывает сегмент данных текущего экземпляра прикладной программы с указателем функции. Это дает гарантию того, что когда Windows вызовет функцию диалога, она будет использовать данные текущего экземпляря, а не некоторого другого экземпляря программы. Функция MakeProcInstance возвращает адрес экземпляра процедуры. Это значение должно быть присвоено переменной указателя, имеющей тип FARPROC. (2) Функция DialogBox создает панель диалога. Для нее необхо- димы дескриптор текущего экземпляра прикладной программы и имя шаблона панели диалога. Она использует эту информацию для загрузки шаблона панели диалога. Функция DialogBox также использует дескриптор родительского окна (окна, которому принадлежит панель диалога) и адрес экземпляра процедуры. Эта функция не возвращает управление до тех пор, пока пользователь не закроет панель диалога. Обычно панель диалога содержит по крайней мере блок управления типа "альтернативная клавиша" для того, чтобы пользователь мог закрыть панель. (3) Когда функция DialogBox возвращает управление, адрес эк- земпляря функции диалога становится больше не нужным, и функция FreeProcInstance освобождает его. При этом содержимое переменной указателя становится неопределенным, и при попытке ее повторного использования выдается ошибка. 2.6 Создание файла определения модуля. Каждая прикладная программа должна иметь файл определения модуля. Этот файл определяет имя, сегменты, требования к памяти и экспортируемые функции прикладной программы. Для такой простой прикладной программы как Generic необходимо иметь в нем, по крайней мере, операторы NAME, STACKSIZE, HEAPSIZE, EXETYPE и EXPORTS. Однако большинство прикладных программ включают полное определение модуля, как это показано ниже: ;файл определения модуля для Generic -- используется . Windows 3.0/pg/1#3 = 44 = ;link.exe (1) NAME Generic ;имя модуля прикладной программы (2) DESCRIPTION 'Пример прикладной программы' (3) EXETYPE WINDOWS ;требуется для всех программ ;Windows (4) STUB 'WINSTUB.EXE' ;генерация сообщения об ошибке, ;если программа запущена без ;Windows (5) CODE MOVEABLE DISCARDABLE ;кодовый сегмент перемещаем в ;памяти и удаляем ;сегмент DATA должен быть типа MULTIPLE, если программа ;вызывается более одного раза (6) DATA MOVEABLE MULTIPLE (7) HEAPSIZE 1024 (8) STACKSIZE 5120 ;рекомендованный минимум для Windows ;все функции, которые вызываются любой подпрограммой ;Windows, должны быть экспортируемыми (9) EXPORTS MainWndProc @1 ;имя функции, обрабатывающей окно AboutDlgProc @2 ;имя функции, обрабатывающей ;панель диалога Точка с запятой - это ограничитель комментария в файле определения модуля. 1) Оператор NAME определяет имя прикладной программы, исполь- зуемое Windows для идентификации программы. Оператор NAME является обязательным. 2) Оператор DESCRIPTION является необязательным оператором, который помещает сообщение "Пример прикладной программы" в выполняемый файл прикладной программы. Этот оператор обычно используется для занесения в файл или версии или информации об авторских правах. 3) Оператор EXETYPE идентифицирует выполняемый файл как вы- полняемый файл в формате Windows или в формате OS/2. Прикладные программы для Windows должны иметь оператор EXETYPE WINDOWS, поскольку по умолчанию компоновщик создает файлы в формате OS/2. 4) Оператор STUB специфицирует другой необязательный файл, . Windows 3.0/pg/1#3 = 45 = определяющий выполняемый модуль специального типа, который должен быть помещен в начало файла. Этот выполняемый модуль индицирует предупреждающее сообщение и завершает программу, если пользователь пытается выполнить ее вне среды Windows. Вместе с SDK поставляется файл WINSTUB.EXE. Этот модуль выводит предупреждающее сообщение и завершает выполнение программы при запуске программы вне среды Windows. Вы можете указать собственный модуль STUB. 5) Оператор CODE определяет требования к памяти со стороны кодового сегмента прикладной программы. Он состоит из выполняемого кода, который генерируется при трансляции файла Generic.c. Generic - это программа в модели памяти SMALL только с одним кодовым сегментом, который определен как MOVEABLE DISCARDABLE. Если программа не работает и Windows необходимо дополнительное место в памяти, система может переместить кодовый сегмент для того, чтобы дать место другим сегментам, или при необходимости вообще удалить его из памяти. При необходимости удаленный сегмент загружается Windows автоматически. 6) Оператор DATA определяет требования к памяти со стороны сегмента данных прикладной программы. Сегмент данных содержит место для всех статических переменных, объявленных в файле Generic.c. Он также содержит место для стека программы и локальной динамической области памяти. Сегмент данных также определен как MOVEABLE. Ключевое слово MULTIPLE служит для указания Windows на создание нового сегмента данных для прикладной программы при запуске каждого нового экземпляра. Ключевое слово MULTIPLE является обязательным, если имеется возможность запуска нескольких экземпляров прикладной программы. 7) Оператор HEAPSIZE определяет размер в байтах локальной ди- намической области памяти прикладной программы. Generic использует ее для отведения временной структуры, используемой для регистрации класса окна, занимающей 1024 байта памяти. Прикладные программы, которые часто используют локальную динамическую область памяти, должны специфицировать больший объем памяти. 8) Оператор STACKSIZE определяет размер в байтах стека прик- ладной программы. Стек используется для временного хранения аргументов функции. Любая программа, которая вызывает свои собственные локальные функции, должна иметь стек. Generic использует 5120 байт для стека - рекомендованный средой Windows минимум. 9) Оператор EXPORTS определяет имена и порядковые номера функций, которые должны быть экспортированы прикладной программой. Generic экспортирует свою функцию окна MainWndProc, которая имеет порядковый номер 1 (это идентификатор; он может быть любым целым, но обычно такие . Windows 3.0/pg/1#3 = 46 = значения присваиваются экспортируемым функциям последовательно в порядке перечисления). Windows требует, чтобы все функции за исключением WinMain, которые должны быть вызваны средой Windows, были бы экспортированы таким способом. На эти функции ссылаются как на функции многократного вызова. К функциям многократного вызова относятся следующие: - Все функции окон. - Все функции диалога. - Специальные функции многократного вызова типа функций перечисления, которые требуются для функций Windows API. - Любые другие функции, которые могут быть вызваны из вне вашей программы. Подробная информация по функциям многократного вызова содержится в главе 14 "Язык С и язык ассемблера". Подробная информация по операторам файла определения модуля содержится во втором томе Справочного руководства. 2.7 Объединение компонент Generic. Теперь вы готовы к тому, чтобы объединить отдельные части нашего примера Generic. (Вы можете скопировать файлы с исходными текстами Generic с диска "SDK Sample Source Code Disk".) Для объединения компонент прикладной программы Generic необходимо сделать следующее: 1) создать исходный файл на языке С (.С). 2) создать включаемый файл (.Н). 3) создать файл описания ресурсов (.RC). 4) создать файл определения модуля (.DEF). 5) создать файл make; 6) запустить make для трансляции и компоновки программы. В следующих разделах эти шаги описаны более подробно. Примечание: Вместо того, чтобы набирать вручную данные примеры, вы возможно найдете более удобным просто скопировать исходные файлы с диска, поставляемого с SDK и откомпилировать их. . Windows 3.0/pg/1#3 = 47 = 2.7.1 Создание исходного файла на языке С. Исходный файл на языке С должен содержать функцию WinMain, функцию окна MainWndProc, функцию диалога About и функции инициализации InitApplication и InitInstance. Назовем файл Generic.c. Общий вид этого файла: /************************************************************* PROGRAM: GENERIC НАЗНАЧЕНИЕ: Шаблон для создания прикладных программ для Windows. ФУНКЦИИ: WinMain() - вызывает функции инициализации, запуска- ет цикл обработки сообщений. InitApplication() - инициализирует данные окна и ре- гистрирует окно. InitInstance() - сохраняет дескриптор экземпляра и создает основное окно. MainWndProc() - обрабатывает сообщения. About() - обрабатывает сообщения, для панели диалога About. КОММЕНТАРИИ: Windows может иметь несколько копий вашей прикладной программы, работающих одновременно. Переменная hInst содержит номер экземпляра для того, чтобы обработка сообщений производилась для соответствующего экземпляра программы. ***************************************************************/ #include "windows.h" /* требуется для всех программ Windows */ #include "Generic.h" /* специфично для этой программы */ HANDLE hInst; /* текущий экземпляр */ /************************************************************* ФУНКЦИЯ: WinMain(HANDLE,HANDLE,LPSTR,int) НАЗНАЧЕНИЕ: вызов функций инициализации, запуск цикла об- работки сообщений. КОММЕНТАРИЙ: Windows рассматривает эту функцию как исходную точку входа в программу. Эта функция вызывает функции инициализации прикладной программы. Если не были запущены другие экземпляры этой программы, то она . Windows 3.0/pg/1#3 = 48 = также вызывает функцию инициализации экземпляра. Затем, она запускает цикл обработки сообщений, который является высокоуровневой структурой обработки для остальной части программы. Цикл обработки сообщений завершается при получении сообщения WM_QUIT, и функция завершает выполнение программы, возвращая в качестве кода возврата число, передаваемое функцией PostQuitMessage. Если функция завершается до входа в цикл обработки сообщений, то по соглашению она возвращает NULL. ***************************************************************/ int PASCAL WinMain(hInstance, hPrevInstance, lpCmdLine, nCmdShow) HANDLE hInstance; /* текущий экземпляр */ HANDLE hPrevInstance; /* предыдущий экземпляр */ LPSTR lpCmdLine; /* командная строка */ int nCmdShow; /* тип представления окна (откры- тое или в виде иконы) */ { MSG msg; /* сообщение */ if (!hPrevInstance) /* инициализирована ли программа? */ if (!InitApplication(hInstance)) /* инициализация */ return (FALSE); /* выход, если инициализация не произведена */ /* инициализация экземпляра */ if(!InitInstance(hInstance,nCmdShow)) return(FALSE); /* цикл обработки сообщений */ while (GetMessage(&msg, /* структура сообщения */ NULL, /* дескриптор окна, полу- чившего сообщение */ NULL, /* проверяемое сообщение, имеющее наименьший код*/ NULL)) /* проверяемое сообщение, имеющее наибольший код*/ { TranslateMessage(&msg); /* преобразовать коды виртуальных клавиш */ DispatchMessage(&msg); /* передать сообщение окну */ } return (msg.wParam); /* возвращаемое значение функции PostQuitMessage */ } . Windows 3.0/pg/1#3 = 49 = /************************************************************* ФУНКЦИЯ: InitApplication(HANDLE) НАЗНАЧЕНИЕ: Инициализирует данные окна и регистрирует класс окна. КОММЕНТАРИИ: Эта функция вызывается во время инициализации только в том случае, если не был уже запущен другой экземпляр этой программы. Функция выполняет инициализацию тех данных, инициализация которых может быть проведена только один раз. В данном случае мы регистрируем класс окна, заполняя структуру WNDCLASS и вызывая функцию RegisterClass(). Поскольку все следующие экземпляры будут использовать тот же самый класс окна, то им уже не нужно проводить его регистрацию после того, как он был зарегистрирован в первом экземпляре. ***************************************************************/ BOOL InitApplication(hInstance) HANDLE hInstance; /* текущий экземпляр */ { WNDCLASS WC; /* заполнение структуры класса окна параметрами описы- вающими основное окно */ WC.style = NULL; /* тип окна */ WC.lpfnWndProc = MainWndProc; /* функция окна */ WC.cbClsExtra = 0; /* нет дополнительных данных */ WC.clWhdExtra = 0; WC.hInstance = hInstance; /* экземпляр, владелец класса */ WC.hIcon = LoadIcon(NULL, IDI_APPLICATION); WC.hCursor = LoadCursor(NULL, IDC_ARROW); WC.hbrBackground = GetStockObject(WHITE_BRUSH); WC.lpszMenuName = "Generic"; /* имя меню в .RC-файле */ WC.lpszClassName = "GenericWndClass"; /* имя класса */ /* регистрация класса */ return(RegisterClass(&WC)); } /************************************************************* ФУНКЦИЯ: InitInstance(HANDLE,int) НАЗНАЧЕНИЕ: Сохраняет дескриптор экземпляра и создает . Windows 3.0/pg/1#3 = 50 = основное окно. КОММЕНТАРИИ: Эта функция вызывается во время инициализации для каждого экземпляра программы. Эта функция выполняет те инициализации, которые не могут быть разделены между различными экземплярами. В данном случае мы сохраняем дескриптор экземпляра в статической переменной, создаем и индицируем окно. ***************************************************************/ BOOL InitInstance(hInstance,nCmdShow) HANDLE hInstance; /* текущий экземпляр */ int nCmdShow; /* Параметр функции ShowWindow */ { HWND hWnd; /* Дескриптор основного окна */ /* Сохранить номер экземпляра в статической переменной, которая используется в последующих вызовах Windows */ hInst = hInstance; /* Создать основное окно для данного экземпляра программы */ hWnd = CreateWindow( "GenericWndClass", /* класс окна */ "Generic Sample Application", /* имя окна */ WS_OVERLAPPEDWINDOW, /* тип окна */ CW_USEDEFAULT, /* координата x */ CW_USEDEFAULT, /* координата y */ CW_USEDEFAULT, /* ширина */ CW_USEDEFAULT, /* высота */ NULL, /* дескриптор родите- льского окна */ NULL, /* ID меню или дочер- него окна */ hInstance, /* экземпляр */ NULL); /* добавочная информа- ция */ /* если окно не создано, возвращается ошибка */ if(!hWnd) return (FALSE); /* Сделать окно видимым. Перерисовать область пользователя, и вернуть код успешной операции */ ShowWindow(hWnd,nCmdShow); /* Отобразить окно */ UpdateWindow(hWnd); /* Перерисовать область . Windows 3.0/pg/1#3 = 51 = пользователя */ return (TRUE); } /************************************************************* ФУНКЦИЯ: MainWndProc(HWND,unsigned,WORD,LONG) НАЗНАЧЕНИЕ: Обработка сообщений. КОММЕНТАРИИ: Для обработки сообщения IDM_ABOUT вызывается MakeProcInstance для получения адреса экземпляра процедуры функции About. Затем вызывается функция DialogBox, которая создает панель диалога в соотвествии с шаблоном из файла Generic.rc и передает управление функции About. После возвращения управления она освобождает адрес экземпляра процедуры. ***************************************************************/ long FAR PASCAL MainWndProc(hWnd, message, wParam, lParam) HWND hWnd; /* дескриптор окна */ unsigned message; /* тип сообщения */ WORD wParam; /* дополнительная информация */ LONG lParam; /* дополнительная информация */ { FARPROC lpProcAbout; switch (message) { case WM_COMMAND: /* сообщение: команда системного меню */ if (wParam == IDM_ABOUT) { lpProcAbout = MakeProcInstance(About,hInst); DialogBox(hInst, /* текущий экземп- ляр */ "AboutBox", /* используемый ре- сурс */ hWnd, /* дескриптор роди- тельского окна */ lpProcAbout); /* адрес экземпляра функции About()*/ FreeProcInstance(lpProcAbout); break; } else /* обработка сообщений Windows */ return (DefWindowProc(hWnd, message, wParam, lParam)); . Windows 3.0/pg/1#3 = 52 = case WM_DESTROY: /* окно разрушено */ PostQuitMessage(0); break; default: /* если не обрабатывается */ return (DefWindowProc(hWnd, message, wParam, lParam)); } return(NULL); } /************************************************************* ФУНКЦИЯ: About(HWND,unsigned,WORD,LONG) НАЗНАЧЕНИЕ: Обработка сообщений панели диалога "About". КОММЕНТАРИИ: При инициализации нет необходимости в выполнении каких-либо действий, но мы должны вернуть Windows TRUE. Ожидает, пока пользователь не нажмет клавишу "OK", и затем закрывает панель диалога. ***************************************************************/ BOOL FAR PASCAL About(hDlg, message, wParam, lParam) HWND hDlg; /* дескриптор окна панели диалога */ unsigned message; /* тип сообщения */ WORD wParam; /* информация, зависящая от типа */ LONG lParam; /* сообщения */ { switch (message) { case WM_INITDIALOG: /* сообщение: инициализация панели диалога */ return (TRUE); case WM_COMMAND: /* сообщение: получение команды */ if (wParam == IDOK|| wParam == IDCUNCEL) { /* выбрана па- нель "ОК"? или "Close" */ EndDialog(hDlg, NULL); /* Завершение работы с па- нелью диало- га */ return (TRUE); } break; } return (FALSE); /* сообщение не обработано */ } . Windows 3.0/pg/1#3 = 53 = 2.7.2 Создание включаемого файла. Во включаемом файле содержатся определения и объявления, требуемые для файла с исходным текстом на С, который включается в файл с исходным текстом с помощью директивы #include. Файл с именем Generic.h будет выглядеть следующим образом: #define IDM_ABOUT 100 int PASCAL WinMain(HANDLE, HANDLE, LPSTR, int); BOOL InitApplication(HANDLE); BOOL InitInstance(HANDLE,int); long FAR PASCAL MainWndProc(HWND, unsigned, WORD, LONG); BOOL FAR PASCAL About(HWND, unsigned, WORD, LONG); 2.7.3 Создание файла описания ресурсов. Файл описания ресурсов должен содержать шаблон панели диалога About и шаблон меню Help. Имя этого файла - Generic.rc. Общий вид: #include "windows.h" #include "generic.h" GenericMenu MENU BEGIN POPUP "&Help" BEGIN MENUITEM "About Generic...",IDM_ABOUT END END AboutBox DIALOG 22, 17, 144, 75 STYLE DS_MODALFRAME | WS_CAPTION | WS_SYSMENU CAPTION "About Generic" BEGIN CTEXT "Microsoft Windows" -1, 0, 5, 144, 8 CTEXT "Generic Application" -1, 0, 14, 144, 8 CTEXT "Version 3.0" -1, 0, 34, 144, 8 DEFPUSHBUTTON "ВК" IDOK, 53, 59, 32, 14, WS_GROUP END 2.7.4 Создание файла определения модуля. Файл определения модуля должен содержать определение модуля программы Generic. Дадим файлу имя Generic.def. Общий его вид будет следующим: . Windows 3.0/pg/1#3 = 54 = ;файл определения модуля для Generic -- используется ;link.exe NAME Generic ;имя модуля прикладной программы DESCRIPTION 'Пример прикладной программы' EXETYPE WINDOWS ;требуется для всех программ ;Windows STUB 'WINSTUB.EXE' ;генерация сообщения об ошибке, ;если программа запущена без ;Windows CODE MOVEABLE DISCARDABLE ;кодовый сегмент перемещаем в ;памяти и удаляем ;сегмент DATA должен быть типа MULTIPLE, если программа ;вызывается более одного раза DATA MOVEABLE MULTIPLE HEAPSIZE 1024 STACKSIZE 5120 ;рекомендованный минимум для Windows ;все функции, которые вызываются любой подпрограммой ;Windows, должны быть экспортируемыми EXPORTS MainWndProc @1 ;имя функции, обрабатывающей окно AboutDlgProc @2 ;имя функции, обрабатывающей ;панель диалога 2.7.5 Создание файла make. Для трансляции и компоновки программы Generic файл make должен быть создан следующим образом: - использовать компилятор С с именем cl для трансляции файла Generic.c. - использовать компоновщик link для компоновки объектного файла Generic.obj с библиотекой Windows и файлом описания модуля Generic.def. - использовать компилятор ресурсов rc для создания двоич- ного файла ресурсов и подсоединить его к выполняемому файлу прикладной программы. Ниже приводятся соответствующие операторы для трансляции и компоновки файлов Generic: # Стандартный Make-файл Windows. Служебная программа MAKE.EXE . Windows 3.0/pg/1#3 = 55 = # сравнивает дату создания файлов с левой стороны от двоеточия # с датой создания файлов справа от двоеточия. Если файлы # справа новее, чем слева, MAKE запускает командные строки, ко- # торые следуют за этой строкой и сдвинуты по крайней мере на # один пробел или символ табуляции. Могут быть использованы лю- # бые допустимые командные строки MS-DOS. # Корректировка ресурса в случае необходимости (1) generic.res: generic.rc generic.h rc -r generic.rc # Корректировка объектного файла в случае необходимости (2) generic.obj: generic.c generic.h cl -c -Gsw -Oas -Zpe generic.c # Корректировка выполняемого файла в случае необходимости, # и присоединение к нему ресурсов (3) generic.exe: generic.obj generic.def link4 /NOD generic, , , slibew libw, generic.def rc generic.res # Если файл .res - новый, а файл .exe не изменялся, # скорректировать ресурсы # Заметим, что файл .rc может корректироваться без повторной # трансляции или компоновки (4) generic.exe: generic.res rc generic.res 1) Первые две строки служат make для создания оттранслирован- ного файла ресурсов generic.res, если были скорректированы или файл описания ресурсов generic.rc или включаемый файл generic.h. Параметр -r команды rc служит для создания фай- ла ресурсов без подсоединения его к выполняемому файлу. 2) Следующие две строки служат make для создания файла generic.obj, если файлы generic.c или generic.h имеют бо- лее позднюю версию (по сравнению с generic.obj). Команда cl имеет несколько параметров в командной строке, которые служат для подготовки прикладной программы к выполнению в среде Windows. Минимально требуемые параметры - это -c, -Gw, -Zp. В этом случае подразумевается, что программа Generic - это программа в малой модели памяти. (Все программы в данном руководстве используют малую модель памяти). 3) Затем программа make создает файл generic.exe, если любой из файлов generic.obj, generic.def имеют более позднюю версию (по сравнению с generic.exe). Малые программы типа . Windows 3.0/pg/1#3 = 56 = Generic должны быть скомпонованы с библиотекой Windows slibw.lib и с библиотекой Windows, содержащей функции исполняющей системы С. Объектный файл generic.obj и файл определения модуля generic.def используются как аргументы в командной строке link. 4) Последняя команда rc автоматически подсоединяет скомпили- рованные ресурсы файла generic.res к выполняемому файлу generic.exe. 2.7.6 Запуск программы MAKE. После создания make-файла вы можете откомпилировать и скомпоновать свою программу с помощью утилиты MAKE. Ниже показано, как это сделать для программы Generic: MAKE GENERIC 2.8 Использование Generic как шаблона. Программа Generic и не имеет никаких "лишних" функций и может служить отправной точкой для создания ваших собственных программ. Она использует стандарты данные в :"System Application Architecture, Common User Access: Advansed Interface Design Guide", и содержит все файлы, которые может содержать прикладная программа: .def, .h, .rc, .c и make. Включена также стандартная панель диалога About, а также команда меню Help - About Generic. Можно использовать Generic как шаблон для построения собственных программ. Для этого необходимо скопировать и переименовать исходные тексты этой программы, а затем изменить соответствующие имена функций и вставить собственную часть программы. Все примеры программ из данного руководства были созданы на основе Generic. Ниже описывается, как использовать Generic в качестве шаблона: 1. Выберите свое имя файла программы. 2. Скопируйте указанные ниже имена файлов, изменив их име- на в соответствии с именем программы: generic.c, generic.h, generic.def, generic.rc и generic. 3. С помощью текстового редактора измените все вхождения слова generic в исходном Си-файле прикладной программы (в первую очередь generic.c) на имя прикладной программы. Имеются в виду изменения: - имени класса GenericWndClass; - имени меню класса: GenericMenu; . Windows 3.0/pg/1#3 = 57 = - заголовка окна "Пример прикладной программы Generic"; - имени включаемого файла generic.h; 4. Используйте текстовый редактор для изменения каждого вхождения слова Generic в файл определения модуля программы на имя вашей программы. Имеются в виду изменения: - имени программы Generic; 5. Используйте текстовый редактор для изменения каждого вхождения слова Generic в файл описания ресурсов программы на имя вашей программы. Имеются в виду изменения: - имени включаемого файла generic.h; - заголовка программы "Generic Application"; - имени меню GenericMenu. 6. Используйте текстовый редактор для изменения каждого вхождения слова Generic в файл make программы на имя вашей программы. Имеются в виду изменения: - имени исходного файла на языке С generic.c; - имени объектного файла generic.obj; - имени выполняемого файла generic.exe; - имени файла определения модуля generic.def. После добавления новых функций, ресурсов и включаемых файлов в программу, необходимо быть уверенным в том, что имена файлов прикладной программы уникальны. 2.9 Заключение. В данной главе описаны необходимые элементы прикладных программ Windows и процесс создания простой программы Generic, содержащей все элементы. Вы можете использовать программу Generic в качестве шаблона для создания своих прикладных программ. Прикладные программы для среды Windows должны содержать функцию WinMain и функции окон. Функция WinMain производит инициализацию, обработку сообщений и завершение программы. Функция окна отвечает на ввод и на сообщения системы управления окнами, посылаемыми Windows. . Windows 3.0/pg/1#3 = 58 = Дополнительную информацию относительно простой прикладной программы Windows вы найдете в: Раздел Руководство --------------------------------------------------------------- Программная модель Руководство программиста, глава 1, "Обзор Windows среды Windows" Цикл обработки Руководство программиста, глава 2, "Приклад- сообщений ная программа Generic" Меню Руководство программиста, глава 7, "Меню" Панель диалога Руководство программиста, глава 9, "Панели диалога" Использование стан- Руководство программиста, глава 14, "Язык С дартных функций С и и язык ассемблера" языка ассемблера Функции и сообщения Справочное руководство, том 1. Windows Сообщение WM_COMMAND Справочное руководство, том 1, глава 6, "Список сообщений" Типы данных и струк- Справочное руководство, том 2, глава 7, туры "Типы и структуры данных" Средства разработки "Tools" . Windows 3.0/pg/1#3 = 59 = ЧАСТЬ 2. ПРОГРАММИРОВАНИЕ ПРИКЛАДНЫХ ПРОГРАММ WINDOWS. ---------------------------------------------------------------- Так же как и остальные прикладные программы, прикладные программы в среде Windows производят ввод от пользователя и выполняют вывод на экран и принтер. Однако, в отличие от стандартных программ, программы в среде Windows могут взаимодействовать друг с другом внутри мультизадачного графического окружения. По этой причине они не могут осуществлять непосредственный ввод с клавиатуры и выводить непосредственно на устройства вывода. Вместо этого они должны иметь между программой и системными ресурсами среду Windows. Это неудобство уменьшается благодаря встроенной в Windows мощной поддержке интерфейса пользователя и интерфейса с системным оборудованием. Например, пользователь обычно производит ввод в прикладных программах Windows через меню, и набирая информацию или выбирая отдельные элементы в панелях диалога. В Windows вы не должны реализовывать подробно то, как программа должна отображать эти меню и панели диалога и отвечать на ввод от пользователя. Вместо этого вы производите описание на высоком уровне их содержимого и определяете сообщения, которые должна получать ваша программа при взаимодействии пользователя с конкретным элементом. Windows производит всю низкоуровневую работу по отображению меню и панелей диалога и отслеживает взаимодействие с пользователем. В части 1 содержится обзор среды Windows и базовой структуры прикладной программы, и представлены некоторые основные элементы прикладных программ, типа окон, меню и панелей диалога. Часть 2 содержит более детальное описание всех основных аспектов прикладных программ Windows. В следующих главах вы узнаете, как создавать и работать с окнами, иконами, курсорами, меню и другими средствами, которые делают прикладные программы Windows прозрачными и легкими для использования. Каждая глава части 2 посвящена определенному разделу программирования в среде Windows и содержит пример программы, иллюстрирующий использование концепций, приведенных в данной главе. . Windows 3.0/pg/1#3 = 60 = Глава 3. Вывод в окно. ---------------------------------------------------------------- В Windows весь вывод в окно осуществляется с помощью интерфейса графического устройства (GDI). В данном разделе поясняется: - описывается процесс рисования в среде Windows. - назначение контекста отображения и сообщения WM_PAINT. - как использовать функции GDI для рисования в области пользователя окна. - как нарисовать линии и произвольные фигуры, записать текст и создать перо или кисть. Кроме этого в данной главе описывается, как создать простую прикладную программу Output, которая иллюстрирует использование некоторых из этих концепций. 3.1 Контекст отображения. Контекст отображения определяет устройство вывода, текущие средства рисования, цвета и другую информацию, используемую GDI для генерации вывода. Дескриптор контекста отображения необходим всем функциям вывода GDI. Никакой вывод невозможен без контекста отображения. Для рисования внутри окна необходим только дескриптор окна. Этот дескриптор используется для получения дескриптора контекста отображения области пользователя окна. Способ, используемый для получения дескриптора, зависит от того, где планируется выполнить операции вывода. Хотя для этих операций можно использовать любую точку прикладной программы (включая функцию WinMain), большинство программ размещают их в функции окна. Хотя функция окна может рисовать внутри области пользователя в ответ на почти любое сообщение, наиболее подходящим моментом для этого является ответ на сообщение WM_PAINT. Windows посылает это сообщение функции окна, когда корректировки окна могут изменить содержимое области пользователя. Поскольку только прикладная программа знает, что находится в области пользователя, Windows посылает сообщение функции окна, чтобы она могла восстановить область пользователя. Для генерации сообщения WM_PAINT необходимо использовать функцию BeginPaint. Если предполагается рисовать в области пользователя в любой момент времени (не обязательно в ответ на сообщение WM_PAINT), нужно использовать функцию GetDC для получения дескриптора контекста отображения. . Windows 3.0/pg/1#3 = 61 = Необходимо помнить, что контекст передается прикладной программе только временно. Контекст отображения - это разделяемый ресурс, и если одна прикладная программа имеет контекст отображения, то другая программа не может получить его. Это означает, что пользователь должен как можно скорее освободить контекст отображения после того, как использовал его для рисования в окне. Если контекст отображения был получен с помощью функции GetDC, то он должен быть освобожден с помощью функции ReleaseDC. Аналогично, если использовалась функция BeginPaint, то нужна функция EndPaint. 3.1.1 Использование функции GetDC. Обычно функция GetDC используется для того, чтобы обеспечить оперативную обратную связь на некоторые действия пользователя, например, на рисование линии при перемещении пользователем курсора мыши в окне. Функция возвращает дескриптор контекста отображения, который может быть использован в функциях вывода GDI. В приведенном ниже примере показано, как использовать функцию GetDC для получения дескриптора контекста отображения и записать строку "Привет, Windows!" в область пользователя: hDC=GetDC(hWnd); TextOut(hDC, 10, 10, "Привет, Windows!", 14); ReleaseDC(hWnd, hDC); В этом примере функция GetDC возвращает контекст отображения окна, идентифицированного параметром hWnd, а функция TextOut записывает строку, начиная с позиции (10, 10) в области пользователя окна. Функция ReleaseDC освобождает контекст отображения. Все, что нарисовано в области пользователя, будет стерто, как только Windows пошлет сообщение WM_PAINT функции окна. Причина этого заключается в том, что Windows посылает сообщение WM_ERASEBKGND функции окна как часть обработки сообщения WM_PAINT. Если сообщение WM_ERASEBKGND передается функции DefWindowProc, то она заполняет область пользователя кистью фона класса, полностью стирая всю ранее выведенную информацию. 3.1.2 Сообщение WM_PAINT. Windows посылает сообщение WM_PAINT, когда в результате действий пользователя окно изменяется. Например, Windows посылает сообщение WM_PAINT, когда пользователь закрывает окно, перекрывающее часть другого окна. Поскольку окно разделяет экран с другими окнами, то все, что пользователь делает в одном окне, может воздействовать на содержимое и внешний вид другого окна. Однако, никакие действия по изменению окна не допускаются, пока программа не получит сообщение WM_PAINT. . Windows 3.0/pg/1#3 = 62 = Windows посылает сообщение WM_PAINT, делая его последним сообщением в очереди прикладной программы. Это означает, что любая вводимая информация обрабатывается до обработки сообщения WM_PAINT. Функция GetMessage обрабатывает сообщение WM_PAINT, если в очереди нет других сообщений. Это делается для того, чтобы дать программе возможность выполнить все операции, которые могут воздействовать на вид окна. Вообще говоря, операции вывода следует выполнять как можно реже для того, чтобы избежать мерцания и других отвлекающих эффектов. Windows это гарантирует, удерживая сообщение WM_PAINT до тех пор, пока оно не будет последним в очереди. В приведенном ниже примере показано, как обработать сообщение WM_PAINT: PAINTSTRUCT ps; . . . case WM_PAINT: hDC=BeginPaint(hWnd, &ps); /* операции вывода */ EndPaint(hWnd, &ps); break; Необходимо использовать функции BeginPaint и EndPaint. Функция BeginPaint заполняет структуру PAINTSTRUCT (ps) информацией о запросе на рисование, такой как, например, размер части области пользователя, которую необходимо перерисовать, и возвращает дескриптор контекста отображения. Этот дескриптор может использоваться в любых функциях вывода GDI. Функция EndPaint оканчивает обработку запроса на рисование и освобождает контекст отображения. Вместо функций BeginPaint и EndPaint нельзя использовать функции GetDC и ReleaseDC. Функции BeginPaint и EndPaint выполняют специальные задачи, такие как проверка области пользователя и посылка сообщения WM_ERASEBKGND, которые дают уверенность в том, что запрос на рисование обработан правильно. Если вместо BeginPaint использовать GetDC, запрос на рисование никогда не будет удовлетворен, и функция окна будет продолжать получать тот же самый запрос на рисование. 3.1.3 Перерисовка области пользователя. Windows не является единственным источником сообщений WM_PAINT. Можно также генерировать эти сообщения, используя две функции: InvalidateRect и InvalidateRgn. Эти функции помечают часть или всю область пользователя как нуждающуюся в перерисовке. Например, приведенная ниже функция помечает всю область пользователя: IvalidateRect(hWnd, NULL, TRUE); . Windows 3.0/pg/1#3 = 63 = В данном примере помечается вся область пользователя окна, идентифицированного параметром hWnd. Аргумент NULL, используемый вместо структуры, определяющей прямоугольник, специфицирует всю область пользователя. Аргумент TRUE приводит к очистке фона. Когда область пользователя помечается для перерисовки, Windows посылает сообщение WM_PAINT. Если помечаются другие части области пользователя для перерисовки, новое сообщение WM_PAINT не посылается. Вместо этого помеченные области объединяются и обрабатываются одним сообщением WM_PAINT. Если намерение перерисовать область пользователя изменилось, можно снять пометку с помощью функций ValidateRect и ValidateRgn. Эти функции удаляют предыдущие пометки и могут удалить сообщение WM_PAINT, если помеченных областей больше не остается. Если нет желания ждать, пока сообщение WM_PAINT будет взято из очереди прикладной программы, можно форсировать обработку этого сообщения, используя функцию UpdateWindow. Если существует помеченная часть области пользователя, функция UpdateWindow берет сообщение WM_PAINT для данного окна из очереди и посылает его непосредственно функции окна. 3.1.4 Контекст отображения и контекст устройства. Контекст отображения - это фактически тип контекста устройства, который специально подготовлен для вывода в область пользователя окна. Контекст устройства определяет устройство, средства рисования и информацию для всего устройства, такого как дисплей или принтер; контекст отображения определяет то же самое, но только для области пользователя окна. При подготовке контекста отображения Windows настраивает начальное положение устройства на верхний левый угол области пользователя (а не дисплея). Он также устанавливает прямоугольную область выреза, так что вывод в контексте отображения "вырезается" областью пользователя. Это означает, что вывод вне пределов области пользователя на экран не попадает. 3.1.5 Система координат. Система координат по умолчанию для контекста отображения очень проста. Верхний левый угол области пользователя является началом координат (точкой (0, 0)). Каждый пиксель справа от нее представляет единицу в положительном направлении оси х; вниз - единицу в положительном направлении оси у. Можно модифицировать эту систему координат, изменив режим отображения и начало координат экрана. Режим отображения определяет единицы системы координат. Режим по умолчанию - MM_TEXT или один пиксель на единицу. Можно также . Windows 3.0/pg/1#3 = 64 = специфицировать режимы отображения, которые используют в качестве единиц измерения дюймы или миллиметры. Функция SetMapMode изменяет режим отображения для устройства. Начало системы координат может быть перемещено в любую точку с помощью функции SetViewportOrg. Для простоты примеры, приведенные в данном разделе и руководстве, используют систему координат по умолчанию. 3.2 Создание, выборка и удаление средств рисования. GDI дает возможность использовать в окне ряд средств для рисования. GDI предоставляет перья для рисования линий, кисти для заливки внутренних областей и шрифты для записи текста. Чтобы использовать эти средства, надо их создать с помощью функций CreatePen и CreateSolidBrush и затем выбрать их в контексте отображения, использовав функцию SelectObject. После применения средств рисования их можно удалить с помощью функции DeleteObject. Можно создать перо для рисования линий и окантовок, используя функцию CreatePen. Функция возвращает дескриптор пера, имеющего указанный тип, ширину и цвет. (Не забудьте проверить возвращаемое функцией CreatePen значение, чтобы убедиться, что вы получили допустимый дескриптор.) В приведенном ниже примере создается пунктирное черное перо шириной в один пиксель: HPEN hDashPen; . . . hDashPen=CreatePen(PS_DASH, 1, RGB(0, 0, 0)); if(hDashPen) /* проверка допустимости дескриптора */ . . . Служебная команда RGB создает 32-битовое значение, указывающее красное, зеленое и синее цветовые значения. Три аргумента специфицируют интенсивность цветов - соответственно, красного, зеленого и синего. В данном примере все цвета имеют нулевую интенсивность, так что указан черный цвет. Можно создать сплошную кисть для рисования и заливки, используя функцию CreateSolidBrush. Эта функция возвращает дескриптор кисти специфицированного сплошного цвета. (Не забудьте проверить возвращаемое функцией CreateSolidBrush значение, чтобы убедиться, что вы получили допустимый дескриптор.) В приведенном ниже примере показано, как создать красную . Windows 3.0/pg/1#3 = 65 = кисть: HBRUSH hRedBrush . . . hRedBrush=CreateSolidBrush(RGB(255, 0, 0)); if(hRedBrush) /* проверка допустимости дескриптора */ . . . Как только средство рисования создано, его можно выбрать в контексте отображения с помощью функции SelectObject. В приведенном ниже примере выбирается красная кисть для рисования: HBRUSH hOldBrush; . . . hOldBrush=SelectObject(hDC, hRedBrush); В данном примере функция SelectObject возвращает дескриптор предыдущей кисти. В общем случае необходимо сохранить дескриптор предыдущего средства рисования для того, чтобы в дальнейшем его можно было восстановить. Не следует создавать или выбирать средство рисования перед использованием контекста отображения. Windows обеспечивает средства рисования по умолчанию своим собственным контекстом отображения; к ним относятся: черное перо, белая кисть и системный шрифт. Можно удалить объекты рисования, если они больше не нужны, используя функцию DeleteObject. В приведенном ниже примере удаляется кисть, идентифицированная дескриптором hRedBrush: DeleteObject(hRedBrush); Не следует удалять выбранное средство рисования. Но если это необходимо, то можно использовать функцию SelectObject для восстановления предыдущего средства рисования и потом удалить ненужные средства, как показано ниже: SelectObject(hDC, hOldBrush); DeleteObject(hRedBrush); Поскольку работа со шрифтами - довольно специфический процесс, полное описание процесса создания и выборки шрифта приводится в главе 18, "Шрифты". . Windows 3.0/pg/1#3 = 66 = 3.3 Рисование и вывод текста. GDI обеспечивает широкий спектр операций вывода от рисования линий до записи текста. В самом деле, можно использовать функции LineTo, Rectangle, Ellipse, Arc, Pie, TextOut и DrawText для рисования линий, прямоугольников, окружностей, дуг, секторов круговых диаграмм и текста. Во всех случаях функции используют выбранное перо и кисть для рисования окантовок и заливки внутренних областей, а также выбранные шрифты для записи текста. Линии рисуются с использованием функции LineTo. Обычно функции MoveTo и LineTo используются совместно для рисования линий. В приведенном ниже примере рисуется линия от точки (10, 90) до точки (360, 90): MoveTo(hDC, 10, 90); LineTo(hDC, 360, 90); Прямоугольник рисуется с помощью функции Rectangle. Эта функция использует выбранное перо для рисования окантовки и выбранную кисть для заливки внутренней области. В приведенном ниже примере рисуется прямоугольник, координаты верхнего левого и нижнего правого углов которого, соответственно, - точки (10, 30) и (60, 80): Rectangle (hDC, 10, 30, 60, 80); Эллипс или окружность рисуются с помощью функции Ellipse. Функция использует выбранное перо для рисования окантовки и выбранную кисть для заливки внутренней области. В приведенном ниже примере рисуется эллипс, который ограничен прямоугольником, определенным точками (160, 30) и (210, 80): Ellipse (hDC, 160, 30, 210, 80); Дуги рисуются с помощью функции Arc. Дуга определяется окружностью и точками начала и конца дуги. В приведенном ниже примере показано, как нарисовать дугу, начиная с точки (10, 90) и кончая точкой (360, 90). Дуга рисуется от точки (10,90) до точки (360,90): Arc (hDC, 10, 90, 360, 120, 10, 90, 360, 90); Сектор круговой диаграммы рисуется с использованием функции Pie. Этот сектор состоит из дуги и двух радиусов, идущих от центра окружности к концам дуги. Функция Pie использует выбранное перо для рисования окантовки и выбранную кисть для заливки внутренней области. В приведенном ниже примере рисуется сектор круговой диаграммы, ограниченный прямоугольником, который специфицирован точками (310, 30) и (360, 80) и который начинается и заканчивается, соответственно, в точках (360, 30) и (360, 80): . Windows 3.0/pg/1#3 = 67 = Pie (hDC, 310, 30, 360, 80, 360, 30, 360, 80); Текст индицируется при помощи функции TextOut. Функция индицирует строку, начиная со специфицированной точки. В приведенном ниже примере индицируется строка "Пример строки", начиная с точки (1, 1): TextOut(hDC, 1, 1, "Пример строки", 13); Можно также индицировать текст, используя функцию DrawText. Эта функция аналогична TextOut за исключением того, что она позволяет записывать текст в несколько строк. В приведенном ниже примере строка "Эта длинная строка иллюстрирует работу функции DrawText" индицируется в нескольких строках специфицированного прямоугольника: RECT rcTextBox; PSTR lpText="Эта длинная строка иллюстрирует работу функции DrawText"; . . . SetRect(&rcTextBox, 1, 10, 160, 40); DrawText(hDC, lpText, strlen(pText), &rcTextBox, DT_LEFT); В этом примере строка, на которую указывает параметр lpText, индицируется в одну или несколько выравненных влево строк в прямоугольнике, определенном точками (1, 10) и (160, 40). Хотя вы можете создавать и отображать в окне растровые карты, в данной главе этот процесс не описывается. Смотрите главу 11, "Растровые карты". 3.4 Пример прикладной программы Output. Эта прикладная программа показывает, как использовать сообщение WM_PAINT для рисования внутри области пользователя, а также как создать и использовать средства рисования. Прикладная программа Output - это просто расширение программы Generic, описанной в предыдущем разделе. Для создания прикладной программы Output скопируйте и переименуйте исходные файлы программы Generic, а затем сделайте следующие изменения: 1) добавьте новые переменные; 2) модифицируйте фрагмент WM_CREATE; 3) добавьте фрагмент WM_PAINT; 4) модифицируйте фрагмент WM_DESTROY; . Windows 3.0/pg/1#3 = 68 = 5) оттранслируйте и скомпонуйте программу. Вы можете взять исходные тексты этой программы с диска, поставляемого с SDK: "SDK Sample Source Code Disk". Для этого примера необходим цветной дисплей. Если его нет, GDI будет моделировать некоторые из выводимых цветов квантованием. Квантование - это метод моделирования цвета из двух доступных цветов. На цветных мониторах, которые не могут отображать оранжевый цвет, Windows моделирует его, комбинируя красные и желтые пиксели. На черно-белых мониторах Windows моделирует цвета, используя черные и белые цвета и различные степени серого. Вместо того, чтобы вводить тексты, приведенные в следующих разделах, вам возможно будет удобнее просто переписать исходные тексты из SDK. 3.4.1 Добавление новых переменных. Для данного примера необходимы несколько новых глобальных переменных. Добавьте приведенные ниже переменные в начало исходного С-файла: HPEN hDashPen; /* дескриптор пера "---" */ HPEN hDotPen; /* дескриптор пера "..." */ HBRUSH hOldBrush; /* дескриптор старой кисти */ HBRUSH hRedBrush; /* дескриптор красной кисти */ HBRUSH hGreenBrush; /* дескриптор зеленой кисти */ HBRUSH hBlueBrush; /* дескриптор синей кисти */ Также необходимы новые локальные переменные в функции окна. Объявите в начале функции MainWndProc: HDC hDC; /* переменный контекст отображения */ PAINTSTRACT ps; /* структура рисования */ RECT rcTextBox; /* приямоугольная рамка для текста */ HPEN hOldPen; /* дескриптор старого пера */ 3.4.2 Модификация фрагмента WM_CREATE. Прежде, чем что-либо рисовать, необходимо создать средства рисования, которые будут использоваться в области пользователя программы Output. Поскольку необходимо создать их только на один раз, наиболее подходящее место для этого - обработка сообщения WM_CREATE. Модифицируйте фрагмент WM_CREATE так, чтобы он имел следующий вид: case WM_CREATE: /* создать кисти */ hRedBrush = CreateSolidBrush(RGB(255, 0, 0) . Windows 3.0/pg/1#3 = 69 = hGreenBrush = CreateSolidBrush(RGB( 0,255, 0) hBlueBrush = CreateSolidBrush(RGB( 0, 0,255) /* создание пера "---" */ hDashPen = CreatePen(PS_DASH, /* тип */ 1, /* толщина */ RGB(0, 0, 0)); /* цвет */ /* сорздание пера "..." */ hDotPen = CreatePen(PS_DOTT, /* тип */ 1, /* толщина */ RGB(0, 0, 0)); /* цвет */ Функции CreateSolidBrush создают сплошные кисти, которые должны быть использованы для заливки прямоугольника, эллипса и окружности в программе Output в ответ на получение сообщения WM_PAINT. Функции CreatePen создают пунктирную и штрих-пунктирную линии, используемые для рисования окантовки. 3.4.3 Добавление фрагмента WM_PAINT. Сообщение WM_PAINT информирует вашу программу о том, что необходима перерисовка всей или части области пользователя. Для обработки этого сообщения добавьте следующий фрагмент к функции окна: case WM_PAINT: { TEXTMETRIC textmetric; int nDrawX; int nDrawY; char szText[300]; /* Получить контекст отображения для начала рисования */ hDC = BeginPaint(hWnd, &ps); /* Получить размер текущего шрифта. Эта информация ис- */ /* пользуется для определения промежутков между строка- */ /* ми на экране. */ GetTextMetrics(hDC,&textmetric); /* Определить позицию вывода в 1/4 дюйма от верхней гра- */ /* ницы окна и от левого края верхнего левого угла об- */ /* ласти пользователя окна */ nDrawX = GetDeviceCaps (hDC, LOGPIXELSX) / 4; /* 1/4 inch */ nDrawY = GetDeviceCaps (hDC, LOGPIXELSY) / 4; /* 1/4 inch */ /* Вывести символы на экран. После вывода каждой строки */ . Windows 3.0/pg/1#3 = 70 = /* текста увеличивается вертикальная позиция точки вы- */ /* вода. Расстояние (в пикселях) между верхней точкой */ /* каждой строки равно стандартной высоте символа шрифта */ /* (tmHeight) плюс стандартное расстояние между стро- */ /* ками (tmExternaleading). */ strcpy (szText, "These characters are being painted using "); TextOut (hDC, nDrawX, nDrawY, szText, strlen (szText)); nDrawY += textmetric.tmExternalLeading + textmetric.tmHeight; strcpy (szText, "the TextOut() function, which is fast and "); TextOut (hDC, nDrawX, nDrawY, szText, strlen (szText)); nDrawY += textmetric.tmExternalLeading + textmetric.tmHeight; strcpy (szText, "allows programmer control of placement and ") ; TextOut (hDC, nDrawX, nDrawY, szText, strlen (szText)); nDrawY += textmetric.tmExternalLeading + textmetric.tmHeight; strcpy (szText, "formatting details. However, TextOut() "); TextOut (hDC, nDrawX, nDrawY, szText, strlen (szText)); nDrawY += textmetric.tmExternalLeading + textmetric.tmHeight; strcpy (szText, "does not provide any automatic formatting."); TextOut (hDC, nDrawX, nDrawY, szText, strlen (szText)); nDrawY += textmetric.tmExternalLeading + textmetric.tmHeight; /* Вывести текст в прямоугольник размером 5 на 1 дюйм. */ /* В начале определим прямоугольник */ nDrawY += GetDeviceCaps (hDC, LOGPIXELSY) / 4; /* 1/4 inch */ SetRect ( &rcTextBox , nDrawX , nDrawY , nDrawX + (5 * GetDeviceCaps (hDC, LOGPIXELSX)) /* 5" */ , nDrawY + (1 * GetDeviceCaps (hDC, LOGPIXELSY)) /* 1" */ ); /* Вывести текст в границах этого прямоугольника */ strcpy (szText, "This text is being displayed with a single " "call to DrawText(). DrawText() isn't as fast " "as TextOut(), and it is somewhat more " "constrained, but it provides numerous optional " "formatting features, such as the centering and " "line breaking used in this example."); DrawText ( hDC , szText , strlen (szText) , &rcTextBox , DT_CENTER | DT_EXTERNALLEADING | DT_NOCLIP | DT_NOPREFIX | DT_WORDBREAK . Windows 3.0/pg/1#3 = 71 = ); /* Вывести следующие объекты сразу же под прямоуголь- */ /* ником с текстом. */ nDrawY = rcTextBox.bottom; /* Координаты объектов должны лежать ниже и правее, */ /* чем текущие координаты (nDrawx,nDrawy). */ /* Нарисовать красный прямоугольник */ hOldBrush = SelectObject(hDC, hRedBrush); Rectangle ( hDC , nDrawX , nDrawY , nDrawX + 50 , nDrawY + 30 ); /* Нарисовать зеленый эллипс */ SelectObject(hDC, hGreenBrush); Ellipse ( hDC , nDrawX + 150 , nDrawY , nDrawX + 150 + 50 , nDrawY + 30 ); /* Нарисовать синий сектор круговой диаграммы */ SelectObject(hDC, hBlueBrush); Pie ( hDC , nDrawX + 300 , nDrawY , nDrawX + 300 + 50 , nDrawY + 50 , nDrawX + 300 + 50 , nDrawY , nDrawX + 300 + 50 , nDrawY + 50 ); nDrawY += 50; /* Восстановить старую кисть */ SelectObject(hDC, hOldBrush); . Windows 3.0/pg/1#3 = 72 = /* Выбрать "- -" перо, сохранить старое значение */ nDrawY += GetDeviceCaps (hDC, LOGPIXELSY) / 4; /* 1/4 inch */ hOldPen = SelectObject(hDC, hDashPen); /* Перейти в указанную точку */ MoveTo(hDC, nDrawX, nDrawY); /* Нарисовать линию */ LineTo(hDC, nDrawX + 350, nDrawY); /* Выбрать "..." перо, сохранить старое значение */ SelectObject(hDC, hDotPen); /* Нарисовать дугу, соединяющую концы линии */ Arc ( hDC , nDrawX , nDrawY - 20 , nDrawX + 350 , nDrawY + 20 , nDrawX , nDrawY , nDrawX + 350 , nDrawY ); /* Восстановить старое перо */ SelectObject(hDC, hOldPen); /* Сообщить Windows о завершении рисования */ EndPaint(hWnd, &ps); } break; 3.4.4 Модификация фрагмента WM_DESTROY. Перед завершением программы Output необходимо удалить средства рисования, созданные для окна Output, для того, чтобы освободить память, занимаемую этими средствами. Это можно сделать с помощью функции DeleteObject, которая удаляет различные перья и кисти в фрагменте WM_DESTROY. Модифицируйте этот фрагмент так, чтобы он имел следующий вид: case WM_DESTROY: DeleteObject(hRedBrush); . Windows 3.0/pg/1#3 = 73 = DeleteObject(hGreenBrush); DeleteObject(hBlueBrush); DeleteObject(hDashPen); DeleteObject(hDotPen); PostQuitMessage(0); break; Для каждого удаляемого объекта необходим один вызов функции DeleteObject. 3.4.5 Трансляция и компоновка. Для перетрансляции и перекомпоновки программы Output не требуется вносить какие-либо изменения в файл make. После трансляции и компоновки запустите Windows и прикладную программу. Прикладная программа будет выглядеть так, как показано на рис. 3.1 Рисунок 3.1 Окно программы Output. Для экспериментирования с различными функциями GDI можно использовать фрагмент WM_PAINT данной программы. Информация по другим функциям GDI приведена в первом томе Справочного Руководства. 3.5 Заключение. В данной главе описывается как интерфейс графических устройств (GDI) Windows обрабатывает вывод в окно. Для вывода GDI используют "контекст устройства". Контекст устройства - это структура данных, поддерживаемая GDI, которая содержит данные об используемом вами устройстве вывода. GDI позволяет вам использовать различные средства вывода в окно. Дополнительную информацию, относительно вывода вы найдете в: Раздел Руководство --------------------------------------------------------------- Работа с растровыми Руководство программиста, глава 11, "Раст- картами ровые карты" "Tools": Глава 4, "Создание образов: SDKPaint" Работа со шрифтами Руководство программиста, глава 18, "Шриф- ты" "Tools": Глава 6, "Создание шрифтов: Font Editor" . Windows 3.0/pg/1#3 = 74 = Функции окна и соб- Справочное руководство, том 1, глава 1, ственный контекст "Функции интерфейса с устройством управ- устройства ления окнами" Функции вывода Справочное руководство, том 1, глава 2, "Функции интерфейса графических устройств", глава 4, "Список функций". Сообщение WM_PAINT Справочное руководство, том 1, глава 6, WM_CREATE, "Список сообщений" WM_DESTROY Типы данных и струк- Справочное руководство, том 2, глава 7, туры "Типы и структуры данных" . Windows 3.0/pg/1#3 = 75 = Глава 4. Ввод с использованием мыши и клавиатуры. ---------------------------------------------------------------- Большинство прикладных программ нуждаются в вводе инофрмации пользователем. Обычно ввод осуществляется с использованием мыши или клавиатуры. Прикладные программы в среде Windows получают ввод с клавиатуры или от мыши в форме сообщений. В данной главе описываются следующие разделы: - Сообщения ввода, посылаемые Windows вашей прикладной программе. - Реакция на сообщения ввода, переданные вашей программе Windows. В данной главе также описано, как создать простую программу, Input, которая реагирует на различные сообщения ввода. 4.1 Типы вводимой информации. При любом нажатии клавиши, перемещении мыши или нажатии клавиши мыши, Windows посылает соответствующей прикладной программе сообщения ввода. Кроме этого, Windows посылает сообщения ввода в ответ на ввод от таймера. Поддерживаются следующие типы сообщений ввода: Сообщение Описание ---------------------------------------------------------- Клавиатура Ввод с клавиатуры. Символ Ввод с клавиатуры, оттранслированный в коды символов. Мышь Ввод информации с помощью мыши. Меню Ввод информации с помощью меню окна и мыши. Строка прокрутки Ввод с помощью строки прокрутки окна и мыши. Таймер Ввод от системного таймера. ---------------------------------------------------------- Сообщения ввода с клавиатуры, мыши и таймера соответствуют прямому вводу с аппаратуры. Windows передает эти сообщения прикладной программе через очередь прикладной программы. Сообщения о вводе символа, от меню или строки прокрутки создаются в ответ на действия с клавиатурой или "мышью" вне . Windows 3.0/pg/1#3 = 76 = границ области пользователя окна или как результат трансляции сообщений клавиатуры. Обычно Windows посылает их непосредственно функции окна. 4.1.1 Форматы сообщений. Сообщения ввода имеют два формата в зависимости от того, как прикладная программа получает эти сообщения: - Те сообщения, которые Windows помещает в очередь, имеют структуру MSG. Эта структура имеет поля, идентифицирующие сообщение и содержащие информацию о нем. Программа находит эту структуру в цикле обработки сообщений прикладной программы и направляет ее соответствующей функции окна. - Второй формат сообщения ввода, передаваемый функции окна, содержит 4 аргумента, соответствующие параметрам hWnd, wParam, lParam и самому сообщению. Единственное отличие заключается в том, что структура MSG включает поле для указания положения мыши и системного времени при генерации сообщения. Windows не посылает эту инофрмацию функции окна. 4.1.2 Ввод с клавиатуры. Большую часть информации пользователь вводит с клавиатуры. Когда пользователь нажимает или отпускает клавишу, Windows посылает в программу сообщение ввода с клавиатуры. Ниже приводится перечень сообщений ввода с клавиатуры и события, их вызывающие: Сообщение Событие ---------------------------------------------------------- WM_KEYDOWN Пользователь нажал клавишу; WM_KEYUP Пользователь отпустил клавишу; WM_SYSKEYDOWN Пользователь нажал системную клавишу; WM_SYSKEYUP Пользователь отпустил системную клавишу. ---------------------------------------------------------- Параметр wParam определяет виртуальный код данной клавиши. Виртуальный код клавиши - это независимое от устройства значение клавиши. Windows использует виртуальные коды клавиш для того, чтобы не зависить от используемой персональной ЭВМ. Параметр lParam содержит фактический код клавиши и дополнительную информацию о клавиатуре такую, как состояние клавиши Shift, и была ли нажата текущая клавиша. . Windows 3.0/pg/1#3 = 77 = Для системных клавиш Windows генерирует соответствующие сообщения WM_SYSKEYUP и WM_SYSKEYDOWN. Это специальные клавиши такие, например, как Alt и F10, которые принадлежат интерфейсу пользователя и не могут использоваться прикладной программой другим способом. Прикладная программа получает сообщения клавиатуры только тогда, когда она захватила ввод. Прикладная программа захватывает ввод, когда она становится активной, т.е. когда пользователь выбрал окно программы. Можно также использовать функцию SetFocus для явного указания на захват данным окном ввода и функцию GetFocus для определения того, какое окно захватило ввод. 4.1.3 Ввод символа. Прикладная программа, которая читает символы, вводимые с клавиатуры, должна использовать функцию TranslateMessage в своем цикле обработки сообщений. Эта функция преобразует сообщение ввода с клавиатуры в соответствующее сообщение ввода символа в коде ANSI - WM_CHAR или WM_SYSCHAR. Эти сообщения содержат ANSI-коды символа для данной клавиши в параметре wParam. Параметр lParam аналогичен таковому в сообщении ввода с клавиатуры. 4.1.4 Ввод с помощью мыши. При вводе с помощью "мыши" Windows посылает прикладной программе сообщение от "мыши", когда пользователь перемещает курсор "мыши" (указатель) в/через окно или нажимает/освобождает кнопку "мыши", находясь в окне. В табл. 3 приводится перечень сообщений от "мыши" и события, их вызывающие. Сообщение Описание ---------------------------------------------------------- WM_MOUSEMOVE Пользователь перемещает курсор мыши в/через окно. WM_LBUTTONDOWN Пользователь нажимает левую кнопку. WM_LBUTTONUP Пользователь отпускает левую кнопку. WM_LBUTTONDBLCLK Пользователь нажимает, отпускает и вновь нажимает левую кнопку (в рамках отпущенного системой времени). WM_MBUTTONDOWN Пользователь нажимает среднюю кнопку. WM_MBUTTONUP Пользователь отпускает среднюю кнопку. WM_MBUTTONDBLCLK Пользователь нажимает, отпускает и вновь нажимает среднюю кнопку(в рамках . Windows 3.0/pg/1#3 = 78 = отпущенного системой времени). WM_RBUTTONDOWN Пользователь нажимает правую кнопку. WM_RBUTTONUP Пользователь отпускает правую кнопку. WM_RBUTTONDBLCLK Пользователь нажимает, отпускает и вновь нажимает правую кнопку (в рам- ках отпущенного системой времени). ---------------------------------------------------------- Параметр wParam каждой кнопки включает маску бит, определяющую текущее состояние клавиатуры и кнопок мыши такое, например, как нажаты ли кнопки мыши, клавиша Shift и клавиша Ctrl. Параметр lParam содержит координаты x и y курсора мыши. Windows посылает окну сообщение мыши только в том случае, если курсор мыши находится в окне, или ввод от мыши захвачен с помощью функции SetCapture. Эта функция предписывает Windows послать все сообщения ввода мыши (вне зависимости от того, где находится курсор мыши) в указанное окно. Прикладная программа обычно использует эту функцию для того, чтобы взять на себя управление при выполнении некоторых критических операций, таких как выборка с помощью мыши некоторого объекта в области пользователя. Захват ввода от мыши предотвращает получение управления другой прикладной программой до завершения операции. Поскольку мышь является разделяемым ресурсом, важно освободить его сразу по завершении операции. Освобождение мыши делается функцией ReleaseCapture. Для определения окна, захватившего мышь, используется функция GetCapture. Windows посылает функции окна сообщение о двойном нажатии на кнопку мыши только в том случае, когда соответствующий класс окна имеет тип CS_DBLCLKS. Этот тип необходимо установить при регистрации класса окна. Сообщение о двойном нажатии на клавишу всегда является третьим по счету в серии из четырех сообщений. Первые два сообщения - о первом нажатии и освобождении кнопки. Последнее сообщение - это второе освобождение кнопки. Напомним, что сообщение о двойном нажатии фиксируется только тогда, когда первое и второе нажатия произошли в рамках установленного системой временного интервала. Можно узнать этот интервал, использовав функцию GetDoubleClickTime. Временной интервал можно установить с помощью функции SetDoubleClickTime, однако надо помнить, что временной интервал устанавливается для всех прикладных программ. 4.1.5 Ввод от таймера. Windows посылает сообщение ввода от таймера прикладной программе каждый раз, когда установленное для него время исчерпано. Для получения такого сообщения необходимо установить значение таймера с помощью функции SetTimer. . Windows 3.0/pg/1#3 = 79 = Информация от таймера поступает двумя способами: - в виде сообщения WM_TIMER через очередь прикладной программы. - от функции многократного вызова, которая определяется при вызове функции SetTimer. Ниже показано, как установить ввод от таймера, используя сообщение WM_TIMER, на пятисекундный интервал: idTimer = SetTimer(hWnd, 1, 5000, (FARPROC) NULL); В примере интервал таймера устанавливается равным 5000 миллисекунд. Это означает, что таймер будет генерировать сообщение каждые 5 сек. Второй аргумент, если он не равен 0, используется вашей программой для идентификации таймера. Последний аргумент равный NULL означает, что функция многократного вызова для обработки ввода от таймера отсутствует, так что Windows посылает сообщение ввода таймера через очередь прикладной программы. Функция SetTimer возвращает уникальное целое, которое идентифицирует таймер. Этот же самый ID таймера можно использовать в функции KillTimer для его отключения. 4.1.6 Ввод из строки прокрутки. Windows посылает функции окна сообщение ввода из строки прокрутки (WM_HSCROLL или WM_VSCROLL), когда пользователь нажимает на кнопку мыши, курсор которой находится в строке прокрутки. Прикладная программа использует сообщения строки прокрутки для реализации непосредственной прокрутки в окне, когда данные не вмещаются в область пользователя. Прикладные программы, которые выводят в окно текст или другие данные, которые полностью не помещаются в окне, обычно используют различные формы прокрутки. Строки прокрутки - это наиболее простой путь получения от пользователя команд на прокрутку изображения. Для того, чтобы получить указанную возможность, необходимо сформировать строки прокрутки в окне. Для этого при создании окна необходимо определить его тип как WS_HSCROLL или WS_VSCROLL. Это заставит функцию CreateWindow создать горизонтальную и вертикальную строку прокрутки в окне. Ниже приводится соответствующий пример: hWnd = CreatWindow("Input", /* класс окна */ "Пример программы Input", /* имя окна */ WS_OVERLAPPEDWINDOW | WS_HSCROLL | WS_VSCROLL, CW_USERDEFAULT, /* позиция по оси x */ CW_USERDEFAULT, /* позиция по оси y */ . Windows 3.0/pg/1#3 = 80 = CW_USERDEFAULT, /* ширина */ CW_USERDEFAULT, /* высота */ NULL, /* ID меню или дочернего окна */ NULL, /* дескриптор родительского окна */ hInstance, /* экземпляр */ NULL); /* дополнительная информа- ция */ Windows индицирует строки прокрутки при отображении окна. Система автоматически управляет строками прокрутки и посылает сообщения строк прокрутки функции окна при перемещении бегунка на строке прокрутки. При посылке сообщения строки прокрутки Windows помещает в wParam значение, определяющее тип прокрутки. Например, если пользователь нажимает кнопку, находясь на стрелке вверх вертикальной строки прокрутки, ПОЛИФЕЙС устанавливает параметр wParam в значение SB_LINEUP. В зависимости от собылия, Windows заносит в wParam следующие значения: Тип прокрутки Описание ---------------------------------------------------------- SB_LINEUP Пользователь нажимает кнопку, находясь на левой или верхней стрелке. SB_LINEDOWN Пользователь нажимает кнопку, находясь на нижней или правой стрелке. SB_PAGEUP Пользователь нажимает кнопку, находясь между панелью прокрутки и верхней или левой стрелкой. SB_PAGEDOWN Пользователь нажимает кнопку, находясь между панелью прокрутки и нижней или правой стрелкой. SB_THUMBPOSITION Пользователь освобождает кнопку мыши, когда курсор мыши находится в панели прокрутки, обычно после перемещения панели. SB_THUMBTRACK Пользователь перемещает панель прокрут- ки с помощью мыши. ---------------------------------------------------------- 4.1.7 Ввод из меню. При выборе пользователем элемента меню Windows посылает окну сообщения ввода из меню. Имеются два типа таких сообщений: . Windows 3.0/pg/1#3 = 81 = - WM_COMMAND, которое указывает, что пользователь выбрал команду из меню окна прикладной программы. - WM_SYSCOMMAND, которое указывает, что пользователь выбрал команду из системного меню. Поскольку ввод из меню часто является основным источником ввода для прикладных программ, и его обработка может быть достаточно сложна, она описана подробно в главе 7, "Меню", данного руководства. 4.2 Пример прикладной программы Input. Эта прикладная программа иллюстрирует, как обработать сообщения ввода с клавиатуры, мыши, таймера и строк прокрутки. Прикладная программа Input индицирует текущее или последнее состояние каждого из указанных устройств. Для создания прикладной программы Input скопируйте и переименуйте исходные файлы прикладной программы Generic, а затем сделайте следующие модификации: 1) добавьте новые переменные; 2) установите тип класса окна; 3) модифицируйте функцию CreateWindow; 4) определите прямоугольник для текста; 5) добавьте фрагмент WM_CREATE; 6) измените фрагмент WM_DESTROY; 7) добавьте фрагменты WM_KEYUP и WM_KEYDOWN; 8) добавьте фрагмент WM_CHAR; 9) добавьте фрагмент WM_MOUSEMOVE; 10) добавьте фрагменты WM_LBUTTONUP и WM_RBUTTONUP; 11) добавьте фрагмент WM_LBUTTONDBLCLK; 12) добавьте фрагмент WM_TIMER; 13) добавьте фрагменты WM_HSCROLL и WM_VSCROLL; 14) добавьте фрагмент WM_PAINT; 15) оттранслируйте и скомпонуйте. Хотя Windows и не требует устройства манипулирования, для . Windows 3.0/pg/1#3 = 82 = данного примера необходима мышь или другое подобное устройство. В противном случае программа не получит сообщений ввода от мыши. Примечание: Вместо того, чтобы вводить тексты, приведенные в следующих разделах, возможно, вам будет удобнее просто переписать исходные тексты из SDK. 4.2.1 Как программа Input осуществляет вывод. Программа Input в ответ на сообщения ввода выводит текст, который определяет тип сообщения ввода. Для вывода и форматирования текста она использует простые функции. Для создания отформатированной строки используйте функцию wsprintf, которая является версией функции исполняющей системы С sprintf для Windows. Функция Windows wsprintf копирует отформатированную строку в буфер. Вы затем можете передать адрес буфера функции TextOut. В программах, использующих малую модель памяти, как все программы из данного руководства, необходимо использовать функцию wsprintf с осторожностью: передаваемый ей буфер должен быть определен в сегменте данных или стека программы. Ниже приведен пример, показывающий, как создать отформатированную строку: char MouseText[48]; . . . wsprintf(MouseText, "WM_MOUSEMOVE: %x, %d, %d", wParam, LOWORD (lParam), HIWORD(lParam)); В данном примере отформатированная строка копируется в массив MouseText. Массив объявлен как локальная переменная, и таким образом он может быть передан функции wsprintf. 4.2.2 Добавление новых переменных. Необходимо иметь несколько новых глобальных переменных. Их нужно объявить в начале исходного С-файла: char MouseText[48]; /* состояние мыши */ char ButtonText[48]; /* состояние кнопки мыши */ char KeyboardText[48]; /* состояние клавиатуры */ char CharacterText[48]; /* последний символ */ char ScroolText[48]; /* состояние прокрутки */ char TimerText[48]; /* состояние таймера */ RECT rectMouse; RECT rectButton; RECT rectKeyboard; RECT rectCharacter; RECT rectScroll; . Windows 3.0/pg/1#3 = 83 = RECT rectTimer; int IdTimer; /* ID таймера */ int nTimerCount = 0; /* текущий счетчик таймера */ Cимвольные массивы содержат строки, которые описывают текущее состояние клавиатуры, мыши и таймера. Прямоугольники определяют положение строк на экране; они используют технику, которая описана в разделе 4.2.15, "Добавление фрагмента WM_PAINT". Необходимы также некоторые локальные переменные для функции окна. В начале функции окна надо объявить следующие переменные: HDC hDC; /* переменная контекста отображения */ PAINTSTRUCT ps; /* структура отображения */ char HorzOrVertText[12]; char ScrollTypeText[20]; RECT rect; Необходимо также добавить следующие переменные к функции InitInstance: HDC hDC; TEXTMETRIC textmetric; RECT rect; int nLineHeight; 4.2.3 Установка типа класса окна. Необходимо установить тип класса окна CS_DBLCLKS для возможности обработки двойного нажатия на кнопку. Найдем этот оператор в функции инициализации: wc.style=NULL; Изменим его следующим образом: wc.style=CS_DBLCLKS; Это дает возможность обрабатывать двойное нажатие на кнопку при работе с окнами, прнадлежащими данному классу. 4.2.4 Модификация функции CreateWindow. Необходимо модифицировать вызов функции CreateWindow для того, чтобы создать окно с горизонтальными и вертикальными строками прокрутки. Измените вызов функции CreateWindow в WinMain так, чтобы он выглядел следующим образом: hWnd = CreateWindow("InputWClass", "Пример программы Input", WS_OVERLAPPEDWINDOW | WS_HSCROLL | WS_VSCROLL, . Windows 3.0/pg/1#3 = 84 = CW_USERDEFAULT, CW_USERDEFAULT, CW_USERDEFAULT, CW_USERDEFAULT, NULL, NULL, hInstance, NULL); 4.2.5 Определение прямоугольников для текста. В функцию InitInstance необходимо добавить следующие операторы для определения прямоугольников в области пользователя, в которых отображаются различные сообщения: hDC = GetDC(hWnd); GetTextMetrics(hDC, &textmetric); ReleaseDC(hWnd, hDC); nLineHeight = textmetric.tmExternalLeading + textmetric.tmHeight; rect.left = GetDeviceCaps(hDC, LOGPIXELSX) / 4; /* 1/4 inch */ rect.right = GetDeviceCaps(hDC, HORZRES); rect.top = GetDeviceCaps(hDC, LOGPIXELSY) / 4; /* 1/4 inch */ rect.bottom = rect.top + nLineHeight; rectMouse = rect; rect.top += nLineHeight; rect.bottom += nLineHeight; rectButton = rect; rect.top += nLineHeight; rect.bottom += nLineHeight; rectKeyboard = rect; rect.top += nLineHeight; rect.bottom += nLineHeight; rectCharacter = rect; rect.top += nLineHeight; rect.bottom += nLineHeight; rectScroll = rect; rect.top += nLineHeight; rect.bottom += nLineHeight; rectTimer = rect; 4.2.6 Добавление элемента WM_CREATE. Необходимо установить таймер с помощью функции SetTimer. Это можно сделать в фрагменте WM_CREATE. Добавьте следующий опереатор: case WM_CREATE: . Windows 3.0/pg/1#3 = 85 = /* Установить таймер на интервал, равный 5 сек. */ idTimer = SetTimer(hWnd, NULL, 5000, (FARPROC) NULL); break; 4.2.7 Модификация фрагмента WM_DESTROY. Кроме того, необходимо остановить таймер перед завершением выполнения программы. Это можно сделать в фрагменте WM_DESTROY. Добавьте следующий оператор: KillTimer(hWnd, idTimer); 4.2.8 Добавление фрагментов WM_KEYUP и WM_KEYDOWN. Необходимо добавить фрагменты WM_KEYUP и WM_KEYDOWN для обработки нажатий на клавиши. Добавьте следующие операторы к функции окна: case WM_KEYDOWN: wsprintf(KeyboardText, "WM_KEYDOWN: %x, %x, %x", wParam, LOWORD(lParam), HIWORD(lParam)); InvalidateRect(hWnd, &rectKeyboard, TRUE); break; case WM_KEYUP: wsprintf(KeyboardText, "WM_KEYUP: %x, %x, %x", wParam, LOWORD(lParam), HIWORD(lParam)); InvalidateRect(hWnd, &rectKeyboard, TRUE); break; 4.2.9 Добавление фрагмента WM_CHAR. Необходимо добавить фрагмент WM_CHAR для обработки вводимых символов в коде ANSI. Добавьте следующие операторы к функции окна: case WM_CHAR: wsprintf(CharacterText, "WM_CHAR: %c, %x, %x", wParam, LOWORD(lParam), HIWORD(lParam)); InvalidateRect(hWnd, &rectCharacter, TRUE); break; 4.2.10 Добавление фрагмента WM_MOUSEMOVE. Необходимо добавить фрагмент WM_MOUSEMOVE для обработки сообщений о перемещении мыши. Добавьте следующие операторы к функции окна: case WM_MOUSEMOVE: wsprintf(MouseText, "WM_MOUSEMOVE: %x, %d, %d", wParam, LOWORD(lParam), HIWORD(lParam)); InvalidateRect(hWnd, &rectMouse, TRUE); break; . Windows 3.0/pg/1#3 = 86 = 4.2.11 Добавление фрагментов WM_LBUTTONUP и WM_LBUTTONDOWN. Необходимо добавить фрагменты WM_LBUTTONUP и WM_LBUTTONDOWN для обработки сообщений об изменении состояния кнопок мыши. Добавьте следующие операторы к функции окна: case WM_LBUTTONDOWN: wsprintf(ButtonText, "WM_LBUTTONDOWN: %x, %d, %d", wParam, LOWORD(lParam), HIWORD(lParam)); InvalidateRect(hWnd, &rectButton, TRUE); break; case WM_LBUTTONUP: wsprintf(ButtonText, "WM_LBUTTONUP: %x, %d, %d", wParam, LOWORD(lParam), HIWORD(lParam)); InvalidateRect(hWnd, &rectButton, TRUE); break; 4.2.12 Добавление фрагмента WM_LBUTTONDBLCLK. Необходимо добавить фрагмент WM_LBUTTONUPDBLCLK для обработки сообщений об изменении состояния кнопок мыши. Добавьте следующие операторы к функции окна: case WM_LBUTTONDBLCLK: wsprintf(ButtonText, "WM_LBUTTONDBLCLK: %x, %d, %d", wParam, LOWORD(lParam), HIWORD(lParam)); InvalidateRect(hWnd, &rectButton, TRUE); break; 4.2.13 Добавление фрагмента WM_TIMER. Необходимо добавить фрагмент WM_TIMER для обработки сообщений таймера. Добавьте следующие операторы к функции окна: case WM_TIMER: wsprintf(TimerText, "WM_TIMER: %d seconds", nTimerCount += 5); InvalidateRect(hWnd, &rectTimer, TRUE); break; 4.2.14 Добавление фрагментов WM_HSCROLL и WM_VSCROLL. Необходимо добавить фрагменты WM_HSCROLL и WM_VSCROLL для обработки сообщений строк прокрутки. Добавьте следующие операторы к функции окна: case WM_HSCROLL: case WM_VSCROLL: strcpy(HorzOrVertText, (message == WM_HSCROLL) ? "WM_HSCROLL" : "WM_VSCROLL"); strcpy(ScrollTypeText, . Windows 3.0/pg/1#3 = 87 = (wParam == SB_LINEUP) ? "SB_LINEUP" : (wParam == SB_LINEDOWN) ? "SB_LINEDOWN" : (wParam == SB_PAGEUP) ? "SB_PAGEUP" : (wParam == SB_PAGEDOWN) ? "SB_PAGEDOWN" : (wParam == SB_THUMBPOSITION) ? "SB_THUMBPOSITION" : (wParam == SB_THUMBTRACK) ? "SB_THUMBTRACK" : (wParam == SB_ENDSCROLL) ? "SB_ENDSCROLL":"unknown"); wsprintf(ScrollText, "%s: %s, %x, %x", (LPSTR)HorzOrVertText, (LPSTR)ScrollTypeText, LOWORD(lParam), HIWORD(lParam)); InvalidateRect(hWnd, &rectScroll, TRUE); break; 4.2.15 Добавление фрагмента WM_PAINT. Необходимо индицировать текущие состояния мыши, клавиатуры и таймера. Наиболее удобный способ это сделать - использовать сообщение WM_PAINT для индицирования этих состояний. Добавьте следующие операторы к функции окна: case WM_PAINT: hDC = BeginPaint (hWnd, &ps); if (IntersectRect(&rect, &rectMouse, &ps.rcPaint)) TextOut(hDC, rectMouse.left, rectMouse.top, MouseText, strlen(MouseText)); if (IntersectRect(&rect, &rectButton, &ps.rcPaint)) TextOut(hDC, rectButton.left, rectButton.top, ButtonText, strlen(ButtonText)); if (IntersectRect(&rect, &rectKeyboard, &ps.rcPaint)) TextOut(hDC, rectKeyboard.left, rectKeyboard.top, KeyboardText, strlen(KeyboardText)); if (IntersectRect(&rect, &rectCharacter, &ps.rcPaint)) TextOut(hDC, rectCharacter.left, rectCharacter.top, CharacterText, strlen(CharacterText)); if (IntersectRect(&rect, &rectTimer, &ps.rcPaint)) TextOut(hDC, rectTimer.left, rectTimer.top, TimerText, strlen(TimerText)); if (IntersectRect(&rect, &rectScroll, &ps.rcPaint)) TextOut(hDC, rectScroll.left, rectScroll.top, ScrollText, strlen(ScrollText)); EndPaint(hWnd, &ps); break; 4.2.16 Трансляция и компоновка. Можно транслировать и компоновать прикладную программу Input без изменения файла make. По окончании компиляции запустите Windows и прикладную программу Input. Для проверки работы программы нажмите клавиши на клавиатуре, нажмите кнопку . Windows 3.0/pg/1#3 = 88 = на мыши и переместите мышь. Прикладная программа должна выглядеть так, как показано на рис. 4.1. Рисунок 4.1. Окно программы Input. 1. Программа Input выводит текст при получении сообщений от мыши, клавиатуры или таймера. 4.3 Заключение. В этой главе описано, как прикладные программы в среде Windows получают ввод от пользователя. Весь ввод направляется вначале Windows, которая преобразует его в сообщения ввода и направляет соответствующей прикладной программе. Программа может получить эти сообщения или напрямую, через аргументы функции окна, или через очередь прикладной программы. В данной главе также описаны различные типы сообщений ввода, а также то, как необходимо реагировать на различные типы сообщений. Дополнительную информацию, относительно ввода вы найдете в: Раздел Руководство --------------------------------------------------------------- Программная модель Руководство программиста, глава 1, "Обзор Windows среды Windows" Использование курсо- Руководство программиста, глава 6, "Курсор, ра для ввода от мыши мышь и клавиатура" и клавиатуры Меню и ввод от меню Руководство программиста, глава 7, "Меню" Строки прокрутки Руководство программиста, глава 8, "Блоки управления" Функции ввода Справочное руководство, том 1, глава 1, "Функции интерфейса управления окнами", глава 4, "Список функций". Сообщения ввода Справочное руководство, том 1, глава 5, "Обзор сообщений", глава 6, "Список сооб- щений". . Windows 3.0/pg/1#3 = 89 = Глава 5. Иконы. ---------------------------------------------------------------- Стандартные программы Windows используют для собственного представления при минимизации окна иконы. В данной главе описываются следующие темы: - Что такое икона. - Создание и использование ваших собственных предопределенных икон. - Определение иконы класса окон прикладной программы. - Изменение иконы вашей программы во время выполнения (на лету). - Вывод иконы в панели диалога. В данной главе описано также, как создать простую программу Icon, которая иллюстрирует использование многих из перечисленных тем. 5.1 Что такое икона. Для пользователя икона - это небольшой графический образ, который можно использовать для графического представления окна при его минимизации. Например, программа PaintBrush использует икону, которая выглядит как палитра художника и представляет окно этой программы. Иконы также используются в сообщениях и панелях диалога. Для прикладной программы икона - это тип ресурсов. Перед компиляцией ресурсов каждая икона содержится в собственном файле в виде набора растровых карт. Эти растровые карты при выводе могут быть одинаковыми, однако предназначены для различных типов устройств отображения. Когда прикладная программа собирается использовать икону, она просто требует этот ресурс по имени. При загрузке иконы Windows определяет, какой из этих образов больше подходит для конкретного дисплея. Поскольку Windows выбирает их автоматически, то прикладной программе нет необходимости проверять тип дисплея и определять какой из образов для него подходит. Рисунок 5.1 иллюстрирует процесс запроса пользователем ресурса иконы. Рисунок 5.1 Использование икон. 1. Прикладной программе требуется ресурс с именем "MyIcon". 2. Windows ищет ресурс с именем "MyIcon" и определяет, что он содержит четыре образа для четырех различных типов дисплеев. 3. Windows выводит икону, которая наиболее соответствует дисплею. . Windows 3.0/pg/1#3 = 90 = 4. Диcплей EGA. 5. Дисплей VGA. 6. Монохромный дисплей. 7. Заказной дисплей. 5.1.1 Использование встроенных икон. Windows обеспечивает несколько встроенных икон. Встроенные иконы могут использоваться в прикладных программах. Windows использует большинство из них в панелях сообщения для представления примечаний, предупреждений и ошибок. Для использования встроенной иконы необходимо получить ее дескриптор, используя функцию LoadIcon. Первый аргумент функции должен быть NULL, а второй должен идентифицировать нужную икону. Например, если необходио использовать икону в виде восклицательного знака используйте следующую функцию: hHandIcon= LoadIcon(NULL, IDI_EXCLAMATION); После загрузки встроенной иконы ваша программа может использовать ее. Например, ваша программа может определить ее в качестве иконы класса для определенного класса окон. Или может включить икону в панель сообщений. Подробное описание вы найдете в разделах 5.3, "Определение иконы класса", и 5.4, "Вывод ваших собственных икон". 5.2. Использование собственных икон. При использовании икон необходимо выполнить три шага: 1. Создать файл иконы с помощью инструментального средства SDKPaint. 2. Определить ресурс иконы с помощью оператора ICON в файле описания ресурсов прикладной программы. 3. При необходимости загрузить ресурс иконы с помощью функции LoadIcon. После загрузки иконы ее можено использовать. Ее можно определить, например, в качестве иконы класса. Следующие разделы описывают этот процесс более детально. 5.2.1 Создание файла иконы. Файл иконы содержит одно или несколько изображений. Для рисования и сохранения в файле иконы вы можете использовать SDKPаint. Следуйте указаниям, данным в описании SDKPaint по созданию и сохранению в файле икон. Для файлов икон рекомендуется использовать расширение .ICO. . Windows 3.0/pg/1#3 = 91 = 5.2.2 Определение ресурса иконы. После того, как вы создали икону, вы должны описать икону в файле описания ресурсов вашей прикладной программы (.RC). Для описания ресурса иконы добавьте в файл описания ресурсов оператор ICON. Оператор ICON определяет имя иконы и файл, в котором она хранится. Например, ниже приведен оператор ICON, который добавляет к ресурсам вашей прикладной программы икону с именем "MyIcon": MyIcon ICON "MYICON.ICO" Имя файла "MYICON.ICO" определяет файл, в котором хранится икона с именем MyIcon. При компиляции файла ресурсов икона будет скопирована из файла в файл ресурсов. 5.2.3 Загрузка ресурса иконы. После создания иконы и определения ее в файле определения ресурсов ваша программа может загрузить его. Для загрузки иконы из ресурсов нужно использовать функцию LoadIcon, которая получает дескриптор экземпляра программы и имя иконы, и которая возвращает дескриптор иконы. Ниже приведен пример, в котором загружается икона "MyIcon" и ее дескриптор запоминается в переменной hMyIcon. hMyIcon = LoadIcon(hInstance,"MyIcon"); После загрузки ее можно выводить. 5.3 Иконы классов. Икона класса - это икона, которая используется для конкретного класса окон каждый раз, когда окно это класса преобразуется в икону. Установка иконы класса осуществляется присваиванием дескриптора иконы полю hIcon структуры класса окна перед регистрацией класса. После установки иконы класса при уменьшении размера окна этого класса до размеров иконы, она будет соответствовать этому классу. Ниже показан пример определения класса wc до регистрации класса. В этом определении полю hIcon присваивается дескриптор иконы, который возвращает функция LoadIcon: wc.style = NULL; wc.lpfnWndProc = MainWndProc; wc.cbClsExtra = 0; wc.cbWndExtra = 0; wc.hInstance = hInstance; (1) wc.hIcon = LoadIcon(NULL, IDI_APPLICATION); . Windows 3.0/pg/1#3 = 92 = wc.hCursor = LoadCursor(NULL, IDC_ARROW); wc.hbrBackground = GetStockObject(WHITE_BRUSH); wc.lpszMenuName = NULL; wc.lpszClassName = "Generic"; 1) Функция LoadIcon возвращает дескриптор встроенной иконы, определяемой IDI_APPLICATION. Если вы минимизируете окно этого класса, то вы увидите белый прямоугольник с черной рамкой. Это встроенная икона прикладных программ. 5.4 Отображение собственных икон. Windows индицирует икону класса при минимизации окна прикладной программы и удаляет ее при максимизации. Все, что для этого необходимо сделать прикладной программе, это указать икону в качестве иконы класса. Это удовлетворяет большинству прикладных программ, поскольку в большинстве программ нет необходимости выводить информацию, когда пользователь отложил окно. Однако, иногда вашей прикладной программе может понадобиться выводить икону самостоятельно, вместо того, чтобы это делали Windows для предопределенной иконы класса. Это нужно, если вы хотите, чтобы икона вашей прикладной программы была динамичной, как икона программы Clock. (Программа Clock продолжает показывать время даже после того, как вы ее минимизировали). Windows позволяет пользователю выводить в окне, когда оно находится в состоянии иконы, так, что он может рисовать свои собственные иконы. Если вы хотите, чтобы ваша прикладная программа выводила собственную икону, то: 1. В структуре класса окна установите икону класса в NULL перед регистрацией класса окна. Используйте для этого следующий оператор: wc.hIcon = NULL; Этот шаг нужен, чтобы сообщить Windows о том, что необходимо продолжать посылать функции окна сообщение WM_PAINT даже в том случае, если окно было минимизировано. 2. Добавьте фрагмент WM_PAINT к функции окна для рисования в области пользователя в окне после того, как оно стало иконой. Для этого используйте следующие операторы: PAINTSTRUCT ps; HDC hDC; . . . . Windows 3.0/pg/1#3 = 93 = case WM_PAINT: hDC = BeginPaint(hWnd,&ps); if (isIconic(hWnd)) { /* вывод в состоянии иконы */ } else { /* вывод в обычном состоянии */ } EndPaint(hWnd,&ps); break; Прикладная программа должна определить, в каком состоянии находится окно (т.е. в виде иконы или нет), поскольку вывод в иконе может отличаться от вывода в обычное окно. Если окно находится в состоянии иконы, функция IsIconic возвращает значение TRUE. Функция BeginPaint возвращает дескриптор контекста отображения области пользователя иконы. BeginPaint получает дескриптор окна и дальний адрес структуры параметров рисования ps. BeginPaint заполняет структуру информацией об области, в которой будет производится вывод. Как и при обычных функциях вывода, каждому вызову функции BeginPaint должен соответствовать вызов функции EndPaint. EndPaint освобождает ресурсы, полученные BeginnPaint, и сообщает Windows, что прикладная программа завершила перерисовку области пользователя. Получить размер области пользователя иконы можно с помощью поля rcPaint структуры рисования. Например, для рисования эллипса, который заполнит икону, вы можете использовать следующий оператор: Ellipse(hDC, ps.rcPaint.left, ps.rcPaint.top, ps.rcPaint.right, ps.rcPaint.bottom); Для рисования в иконе вы можете использовать любые функции GDI, включая TextOut. Единственным ограничением выступает размер иконы, который различается на различных типах дисплеев, поэтому убедитесь, что вы используете средства рисования, не зависяцие от размеров иконы. 5.5 Использование икон в панели диалога. Можно поместить икону в панель диалога, использовав управляющий оператор ICON в операторе DIALOG. Уже приводился пример оператора DIALOG панели диалога About, описанной для прикладной программы Generic. Оператор диалога для этой панели выглядит так: AboutBox DIALOG 22, 17, 144, 75 . Windows 3.0/pg/1#3 = 94 = STYLE DS_MODALFRAME | WS_CAPTION | WS_SYSMENU CAPTION "About Icon" BEGIN CTEXT "Microsoft Windows" -1, 0, 5, 144, 8 CTEXT "Icon Application" -1, 0, 14, 144, 8 CTEXT "Version 3.0" -1, 0, 34, 144, 8 DEFPUSHBUTTON "OK" IDOK, 53, 59, 32, 14, WS_GROUP END Можно добавить икону к панели диалога, вставив указанный ниже оператор ICON непосредственно после оператора DEFPUSHBUTTON: ICON "MyIcon", -1, 25, 14, 16, 21 Когда икона появляется в панели диалога, она трактуется как и любой другой блок управления. Она должна иметь идентификатор, положение ее верхнего левого угла, ширину и высоту. В данном примере -1 - это ее идентификатор, 25 и 14 определяют положение ее левого верхнего угла, а 16 и 21 определяют ее высоту и ширину соответсвенно. Однако Windows игнорирует ее высоту и ширину, устанавливая размеры иконы автоматически. Имя "MyIcon" идентифицирует используемую икону. Икона должна быть определена в операторе ICON в любом месте файла описания ресурсов. Например, можно добавить следующий оператор: MyIcon ICON myicon.ico 5.6 Пример прикладной программы Icon. Эта прикладная программа показывает, как использовать ико- ну в прикладной программе, в частности, как выполнить следующие действия: - использовать заказную икону в качестве иконы класса; - использовать икону в панели диалога About. Для создания прикладной программы Icon скопируйте и переи- менуйте исходные файлы прикладной программы Generic, а затем сделайте следующие модификации: 1) включите оператор ICON в файл описания ресурса; 2) включите управляющий оператор ICON в оператор DIALOG файла описания ресурсов; 3) загрузить заказную икону и использовать ее в функции инициализации для установки иконы класса; 4) измените make-файл программы таким образом, чтобы . Windows 3.0/pg/1#3 = 95 = компилятор ресурсов добавил икону в выполняемый файл; 5) откомпилируйте и скомпонуйте программу. Подразумевается, что икона создана с помощью SDKPaint и сохранена в файле с именем MYICON.ICO. Вместо того, чтобы вводить тексты, приведенные в следующих разделах, возможно вам будет удобнее просто переписать исходные тексты из SDK. 5.6.1 Включение оператора ICON. Необходимо включить оператор ICON в файл описания ресурсов. Вставьте указанную ниже строку в начало файла описания ресурсов сразу после директив #include: MyIcon ICON myicon.ico 5.6.2 Включение управляющего оператора ICON. Необходимо включить управляющий оператор ICON в оператор DIALOG. Вставьте указанную ниже строку непосредственно после оператора DEFPUSHBUTTON: ICON "MyIcon", -1, 25, 14, 16, 21 5.6.3 Установка иконы класса. Можно установить икону класса, добавив приведенный ниже оператор к функции инициализации исходного С-файла: wc.hIcon = LoadIcon(hInstance, "MyIcon"); /* loads icon */ 5.6.4 Добавление MYICON.ICO в MAKE-файл. В MAKE-файле добавьте файл MYICON.ICO к списку файлов, от которых зависит ICON.RES. Соответствующие строки MAKE-файла должны выглядеть следующим образом: icon.res: icon.rc icon.h myicon.ico rc -r icon.rc Этим вы добиваетесь того, что при изменении файла MYICON.ICO файл ICON.RES будет перекомпилирован. Никаких других изменений не требуется. 5.6.5 Трансляция и компоновка. Оттранслируйте и перекомпонуйте программу Icon. После перетрансляции запустите Windows и прикладную программу Icon. Теперь, если выбрана команда About, панель диалога About будет содержать икону. . Windows 3.0/pg/1#3 = 96 = 5.7 Заключение. В данной главе описывается, как создавать и использовать в прикладных программах иконы. Икона - это маленький графический образ, который представляет прикладную программу при ее минимизации. Вы можете использовать одну из встроенных в Windows икон или воспользоваться SDKPaint для создания собственной. Вы можете указать икону при регистрации класса окна, тогда Windows автоматически отобразит икону при минимизации окна прикладной программы. Кроме этого ваша программа может сама рисовать икону, используя функции BeginPaint и EndPaint. Дополнительную информацию относительно икон вы найдете в: Раздел Руководство --------------------------------------------------------------- Функции LoadIcon, Справочное руководство, том 1, глава 4, IsIconic, BeginPaint "Список функций". и TextOut Операторы описания Справочное руководство, том 2, глава 8, ресурсов "Операторы описания ресурсов". Использование "Tools", глава 4, "Создание изображений". SDKPaint Использование редак- "Tools", глава 5, "Создание панелей тора диалога для до- диалога: DialogEditor". бавления иконы к блоку диалога . Windows 3.0/pg/1#3 = 97 = Глава 6. Курсор, мышь и клавиатура. ---------------------------------------------------------------- Курсор - это растровая карта, которая показывает пользователю, где происходит действие, инициированное мышью. Большинство программ Windows использует курсор с мышью или с клавиатурой для того, чтобы дать возможность пользователю делать выборки, указывать команды и выполнять другие действия. В данной главе описаны следующие разделы: - Управление изображением курсора. - Отображение курсора. - Выбор информации пользователем с помощью мыши. - Перемещение курсора с помощью клавиатуры. Кроме этого, в данной главе описывается процесс создания простой программы Cursor, которая иллюстрирует некоторые из этих концепций. 6.1. Управление формой курсора. Поскольку ни одна форма курсора не может удовлетворить все прикладные программы, Windows позволяет изменять форму курсора в соответствии с сутью прикладной программы и действиями, которые она выполняет. Для того, чтобы использовать курсор определенной формы, вы должны получить его дескриптор с помощью функции LoadCursor. После того, как ваша программа загрузила курсор, она может использовать его при необходимости. Программа управляет формой курсора двумя методами: - Можно использовать встроенные в Windows курсоры. - Можно использовать собственные курсоры. Ниже описываются оба метода. 6.1.1. Использование встроенных курсоров. Windows предоставляет несколько встроенных форм курсора. Это стрелка, песочные часы, I-образный курсор и перекрестье. Большинство встроенных курсоров используются в специальных случаях. Например, I-образный курсор используется при редактировании текста, а курсор в виде песочных часов используется при выполнении длительных операций, таких как чтение файлов. . Windows 3.0/pg/1#3 = 98 = Для использования встроенного курсора необходимо с помощью функции LoadCursor получить его дескриптор. Первый аргумент должен быть NULL (указывает, что требуется встроенный курсор), а второй аргумент должен специфицировать загружаемый курсор. Например, ниже в переменную hCursor заносится дескриптор I-образного курсора, который обычно используется для просмотра и редактирования текста, который определяется вторым аргументом, равным IDC_IBEAM: hCursor = LoadCursor(NULL, IDC_IBEAM); Курсор можно использовать после загрузки. Например, вы можете использовать I-образный курсор для указания того, что происходит редактирование текста. В разделе 6.2 описаны методы отображения курсоров. 6.1.2 Использование собственных курсоров. Для создания собственного курсора необходимо выполнить следующие действия: - Создайте курсор с помощью SDKPaint. - Укажите ресурс курсора в файле описания ресурсов с помощью оператора CURSOR. - Загрузите курсор с помощью функции LoadCursor. - Выведите курсор, используя один из методов, описанных в разделе 6.2 "Отображение курсоров". Ниже эти шаги описаны более детально. Создание собственного курсора. Первым шагом необходимо создать сам курсор с помощью инструментальной программы SDKPaint. Эта программа позволяет видеть не только увеличенное изображение создаваемого курсора, но и его действительный вид. После создания курсора надо сохранить его в файле, используя рекомендованное расширение имени файла .cur. Использование SDKPaint описано в "Tools". Добавление курсора к ресурсам вашей программы. Затем необходимо добавить оператор CURSOR к файлу описания ресурсов. Оператор CURSOR специфицирует файл, который содержит курсор и имя, которое будет использовано прикладной программой при его загрузке: bullseye CURSOR bullseye.cur . Windows 3.0/pg/1#3 = 99 = В этом примере имя курсора - bullseye, а имя файла, в котором он содержится, - bullseye.cur. Загрузка ресурса курсора. В прикладной программе необходимо получить дескриптор курсора с помощью функции LoadCursor. Например, в данном операторе производится загрузка ресурса "bullseye" и его дескриптор заносится в переменную hCursor: hCursor=LoadCursor(hInstance,(LPSTR) "bullseye"); Функция LoadCursor загружает курсор из ресурсов прикладной программы. Дескриптор экземпляра hInstance идентифицирует ресурсы прикладной программы. Имя "bullseye" идентифицирует курсор. Это то же самое имя, которое задано в файле описания ресурсов. 6.2 Отображение курсора. После загрузки курсора, вы можете вывести его, используя один из двух методов: - Определив его как курсор класса для всех окон данного класса. - Явно установив курсор при перемещении курсора по области пользователя определенного окна. Ниже эти методы описаны более подробно. 6.2.1 Курсор класса. Курсор класса определяет форму, которую принимает курсор, появляясь в области пользователя окна, принадлежащего этому классу. Необходимо специфицировать курсор класса, присвоив дескриптор курсора полю hCursor структуры класса окна перед регистрацией класса. Например, чтобы загрузить встроенный курсор стрелки (IDC_ARROW) в окно, необходимо добавить к функции инициализации следующий оператор: wc.hCursor=LoadCursor(NULL, IDC_ARROW); Для каждого окна этого класса будет появляться встроенный курсор стрелки, когда пользователь перемещает курсор в окно. 6.2.2 Индицирование собственного курсора. Прикладная программа может и не определять курсор класса. Вместо этого программа должна установить поле hCursor в NULL для указания того, что курсора класса нет. Если окно не имеет курсора класса, Windows не будет автоматически изменять форму . Windows 3.0/pg/1#3 = 100 = курсора при перемещении в область пользователя окна. Это означает, что нужно индицировать собственный курсор. Для индицирования курсора, независимо от того встроенный он или заказной, необходимо его загрузить. В данном примере дескриптор загружаемого курсора присваивается переменной hMyCursor. static HCURSOR hMyCursor; /* статическая переменная */ hMyCursor = LoadCursor(hInstance,(LPSTR)"MyCursor"); Затем, для изменения формы курсора необходимо использовать функцию SetCursor, чтобы устанавливать форму курсора при каждом перемещении в область пользователя. Поскольку Windows посылает окну при каждом перемещении курсора сообщение WM_MOUSEMOVE, то им можно управлять, добавив следующие операторы к функции окна: case WM_MOUSEMOVE: SetCursor (hMyCursor); break; Примечание. Если необходимо индицировать свой собственный курсор, необходимо убедиться, что поле курсора класса установлено в NULL. В противном случае Windows будет пытаться изменить форму курсора. В результате это приведет к заметному мельканию при перемещении курсора по экрану. 6.2.3 Пример: Использование песочных часов при длительных операциях. Каждый раз, когда прикладная программа начинает длительную операцию по считыванию/записи больших блоков данных из/в файл, необходимо изменить форму курсора на песочные часы. Это укажет пользователю на то, что началась длительная операция, и что он должен ожидать ее завершения, не пытаясь продолжить работу. По завершении операции необходимо восстановить курсор в его первоначальную форму. Можно изменить форму курсора, использовав следующие операторы: (1) HCURSOR hSaveCursor; HCURSOR hHourGlass; . . . hHourGlass = LoadCursor(hInstance, IDC_WAIT); . . . (2) SetCapture(hWnd); (3) hSaveCursor = SetCursor(hHourGlass); . Windows 3.0/pg/1#3 = 101 = /* длительная операция */ (4) SetCursor(hSaveCursor); (5) ReleaseCapture(); . . . В этом примере: 1) Прикладная программа определяет переменные, которые будут использоваться для сохранения дескриптора курсора. Обе переменные имеют тип HCURSOR. 2) Прикладная программ вначале захватывает ввод от мыши, используя функцию SetCapture. Это предохраняет пользователя от попытки использовать мышь другой прикладной программой во время выполнения длительной операции. После захвата ввода мыши, Windows направляет его в указанное окно вне зависимости от того, находится ли курсор мыши в этом окне или нет. После этого программа может соответсвующим образом обрабатывать сообщения. 3) Затем устанавливается форма курсора с помощью функции SetCursor. Прежняя форма, возвращаемая функцией SetCursor, сохраняется, так что по завершении операции она может быть восстановлена с помощью SetCursor. Прикладная программа сохраняет дескриптор в переменной hSaveCursor. 4) После завершения длительной операции прикладная программа восстанавливает форму курсора. 5) Функция ReleaseCapture освобождает ввод от мыши. 6.3 Выбор пользователем инофрмации с помощью мыши. Мышь - это устройство, которое дает возможность пользователю перемещать курсор по экрану и осуществлять простой ввод с помощью нажатия на кнопку. В программах в среде Windows мышь используется для выполнения большого числа операций, таких как выбор команд из меню, прокрутка, выборка текста или графики. Windows автоматически выполняет ввод от мыши; например, при выборе команды в меню, Windows автоматически передает программе сообщение, содержащее идентификатор выбранной команды. Однако одна общая операция - выборка информации внутри области пользователя - должна быть выполнена самой прикладной программой. Для того, чтобы пользователь мог производить выборку информации в области пользователя окна, прикладная программа должна сделать следующее: - Начало обработки выборки. . Windows 3.0/pg/1#3 = 102 = Когда пользователь нажимает клавишу мыши для начала выборки информации, прикладная программа должна определить местоположение курсора и временно захватить весь ввод от мыши, чтобы исключить возможное влияние другой программы на процесс выборки. - Выполнить визуальное отображение процесса выборки. Во время перемещения пользователем мыши на экране, прикладная программа должна показывать пользователю выбранную информацию. Некоторые программы подсвечивают выбранную информацию, другие рисуют вокруг нее пунктирный прямоугольник. - Завершение выборки. Когда пользователь отпускает клавишу мыши, прикладная программа подразумевает окончание процесса выборки, она должна определить координаты курсора и сообщить об окончании процесса выборки. После завершения процесса выборки пользователь может выбрать действие, которое он хочет произвести над выбранной информацией. В текстовом процессоре пользователь может, например, выделить несколько слов, затем выбрать команду, которая изменит в указанных словах шрифт. В следующих разделах эти шаги обсуждаются более детально. Примечание: Мышь - это одно из многих возможных системных устройств манипулирования. Другие устройства манипулирования, такие, как графические планшеты, джойстики и световые перья, могут работать по-разному, но должны обеспечивать ввод аналогичный мыши. Приводимые ниже примеры также могут быть использованы и с этими устройствами. Помните, что когда имеется устройство манипулирования, Windows автоматически управляет местоположением и формой курсора. 6.3.1 Начало выборки графики. Поскольку графическое изображение может иметь практически любую форму, то его выборка более сложна, чем выборка текста. Простой подход к выборке графики заключается в определении пользователем прямоугольника, который заключает в себе выбранную информацию. В данном разделе описывается, как использовать метод "резинового прямоугольника", используемый для выборки графического изображения. Для создания прямоугольника можно использовать сообщения WM_LBUTTONDOWN, WM_LBUTTONUP и WM_MOUSEMOVE. Это дает возможность пользователю создать . Windows 3.0/pg/1#3 = 103 = выборку, указав точку, нажав левую кнопку, переместившись в другую точку и отпустив кнопку. Во время перемещения мыши прикладная программа может обеспечить мгновенную обратную связь, инвертируя окантовку прямоугольника, заданного начальной и текущей точками. Таким способом выборка начинается при получении сообщения WM_LBUTTONDOWN. Необходимо сделать три шага: захватить ввод от мыши, сохранить начальную точку и сохранить текущую точку: BOOL bTrack = False; int OrgX = 0, OrgY = 0; int PrevX = 0, PrevY = 0; int X = 0, Y = 0; . . . (1) case WM_LBUTTONDOWN: bTrack = TRUE; OrgX = LOWORD(lParam); OrgY = HIWORD(lParam); PrewX = LOWORD(lParam); PrevY = HIWORD(lParam); (2) InvalidateRect(hWnd,NULL,TRUE); UpdateWindow(hWnd); /* захватить весь ввод от мыши */ (3) SetCapture(hWnd); break; 1) Когда программа получает сообщение WM_LBUTTONDOWN, переменная bTrack устанавливается в TRUE для указания того, что выборка началась. Как и для любого сообщения мыши параметр lParam содержит в младших и старших разрядах слова текущие координаты мыши по осям x и y соответственно. Они запоминаются как начальные значения OrgX и OrgY, а также как предыдущие значения PrevX и PrevY. Переменные PrevX и PrevY будут корректироваться сразу же при получении следующего сообщения WM_MOUSEMOVE. Переменные OrgX и OrgY остаются без изменения и будут использованы для определения начала копируемой растровой карты. 2) Для отображения реакции на получение сообщения WM_LBUTTONDOWN прикладная программа сообщает функции окна, что необходима перерисовка. Это делается с помощью функций InvalidateRect и UpdateWindow. 3) Функция SetCapture направляет весь последующий ввод от мыши в окно, даже если курсор мыши и перемещается вне окна. Это делается для уверенности в том, что процесс выборки не прерывается. . Windows 3.0/pg/1#3 = 104 = В ответ на получение сообщения WM_PAINT программа должна перерисовать помеченную для перерисовки область окна: case WM_PAINT: { PAINTSTRUCT ps; HDC hDC; hDC = BeginPaint (hWnd, &ps); if (OrgX != PrevX || OrgY != PrevY) { MoveTo(hDC, OrgX, OrgY); LineTo(hDC, OrgX, PrevY); LineTo(hDC, PrevX, PrevY); LineTo(hDC, PrevX, OrgY); LineTo(hDC, OrgX, OrgY); } EndPaint (hWnd, &ps); } break; В некоторых прикладных программах возможно понадобится расширить существующую выборку. Один из способов сделать это - удерживать клавишу Shift при создании выборки. Поскольку параметр wParam содержит флаг, который определяет, была ли клавиша Shift нажата, то это легко проконтролировать и расширить выборку в случае необходимости. Тогда расширение выборки означает сохранение предыдущих значений OrgX и OrgY при ее начале. Для этого измените фрагмент WM_LBUTTONDOWN так, чтобы он выглядел следующим образом: case WM_LBUTTONDOWN: bTrack = TRUE; PrevX = LOWORD(lParam); PrevY = HIWORD(lParam); if (!(wParam & MK_SHIFT)) { /* Если не нажата клавиша shift */ OrgX = LOWORD(lParam); OrgY = HIWORD(lParam); } InvalidateRect (hWnd, NULL, TRUE); UpdateWindow (hWnd); /* Захватить весь ввод от мыши, даже если она находится вне окна */ SetCapture(hWnd); break; 6.3.2 Индицирование выборки. В процессе выборки необходимо обеспечить обратную связь. Это можно сделать, рисуя окантовку вокруг прямоугольника с помощью функции LineTo при каждом новом сообщении WM_MOUSEMOVE. . Windows 3.0/pg/1#3 = 105 = Для предотвращения потери информации, находящейся на дисплее, надо рисовать линию, инвертирующую точки экрана, а не рисующую по ним. Это можно сделать с помощью функции SetROP2, установив двоичный растровый режим R2_NOT. Эту функцию выполняют приведенные ниже операторы: case WM_MOUSEMOVE: { RECT rectClient; int NextX; int NextY; if (bTrack) { NextX = LOWORD(lParam); NextY = HIWORD(lParam); /* Не рисовать вне области пользователя окна */ GetClientRect (hWnd, &rectClient); if (NextX < rectClient.left) { NextX = rectClient.left; } else if (NextX >= rectClient.right) { NextX = rectClient.right - 1; } if (NextY < rectClient.top) { NextY = rectClient.top; } else if (NextY >= rectClient.bottom) { NextY = rectClient.bottom - 1; } /* Если позиция мыши измениласть, то стереть старый */ /* и рисовать новый прямоугольник */ if ((NextX != PrevX) || (NextY != PrevY)) { hDC = GetDC(hWnd); SetROP2(hDC, R2_NOT); /* Очистить старый прямоугольник */ MoveTo(hDC, OrgX, OrgY); LineTo(hDC, OrgX, PrevY); LineTo(hDC, PrevX, PrevY); LineTo(hDC, PrevX, OrgY); LineTo(hDC, OrgX, OrgY); /* Получить текущую позицию курсора */ PrevX = NextX; PrevY = NextY; MoveTo(hDC, OrgX, OrgY); /* Нарисовать новый прямоугольник */ LineTo(hDC, OrgX, PrevY); LineTo(hDC, PrevX, PrevY); LineTo(hDC, PrevX, OrgY); LineTo(hDC, OrgX, OrgY); . Windows 3.0/pg/1#3 = 106 = ReleaseDC(hWnd, hDC); } } } break; Сообщение WM_MOUSEMOVE обрабатывается только в том случае, если bTrack равен TRUE (т.е. если идет выборка). Назначение обработки WM_MOUSEMOVE состоит в удалении окантовки предыдущего прямоугольника и рисовании новой окантовки вокруг прямоугольника, который описывается начальной и текущей позициями. Поскольку окантовка - это инвертированное изображение того, что было на экране, повторное инвертирование полностью восстанавливает экран. Первые четыре функции LineTo удаляют предыдущую окантовку. Следующие четыре рисуют новую окантовку. Перед тем, как нарисовать новую окантовку, значения PrevX и PrevY корректируются (им присваиваются текущие значения, содержащиеся в параметре lParam). 6.3.3 Окончание выборки. Когда пользователь освобождает левую кнопку, необходимо сохранить конечную точку и сигнализировать об окончании процесса выборки. Выборка завершается с помощью следующих операторов: case WM_LBUTTONUP: bTrack = FALSE; /* Завершение выборки */ ReleaseCapture(); /* освободить мышь */ X = LOWORD(lParam);/* сохранить текущее значение */ Y = HIWORD(lParam); break; Когда прикладная программа получает сообщение WM_LBUTTONUP, она сразу же устанавливает значение параметра bTrack в FALSE для указания того, что обработка выборки закончена. Программа также освобождает мышь от захвата, используя функцию ReleaseCapture, а затем, сохраняет текущее положение мыши в переменных X и Y. Это вместе с информацией, сохраненной в начале выборки, составляет информацию о выборке. Теперь прикладная программа может выполнять над выборкой необходимые операции. В некоторых прикладных программах необходимо проконтролировать окончательную позицию мыши для уверенности в том, что эта точка располагается ниже и правее начальной позиции (этим способом описываются большинство прямоугольников - с помощью их верхнего левого и правого нижнего углов). Поскольку была вызвана функция SetCapture, то требуется использовать соответствующую функцию ReleaseCapture. Вообще говоря, необходимо освободить мышь немедленно, как только она не будет больше нужна. . Windows 3.0/pg/1#3 = 107 = 6.4 Использование курсора с клавиатурой. Windows не требует обязательного использования устройства манипулирования, так что многие прикладные программы дублируют действия, которые обычно выполняются (и более удобно) с помощью мыши, используя клавиатуру. Прикладные программы, которые применяют для управления курсором клавиатуру, могут использовать функции SetCursorPos, SetCursor, GetCursorPos, ClipCursor и ShowCursor для перемещения курсора при индицировании. 6.4.1 Использование клавиатуры для перемещения курсора. Можно использовать функцию SetCursorPos для перемещения курсора непосредственно из программы. Эта функция обычно используется для перемещения курсора с помощью клавиатуры. Для перемещения курсора используйте сообщение WM_KEYDOWN и отфильтруйте значения виртуальных клавиш стрелок: VK_LEFT, VK_RIGHT, VK_UP и VK_DOWN. Можно скорректировать положение курсора при каждом нажатии на клавишу. Ниже показано, как можно преобразовать позицию курсора в координаты области пользователя окна: POINT ptCursor; /* глобальные переменные */ int repiat=1; RECT Rect; . . . case WM_KEYDOWN: (1) if (wParam != VK_LEFT && wParam != VK_RIGHT && wParam != VK_UP && wParam !=VK_DOWN) break; (2) GetCursorPos(&ptCursor); /* преобразовать координаты экрана в координаты области пользователя */ (3) ScreenToClient(hWnd, &ptCursor); (4) repeat++; /* увеличить счетчик повторений */ switch (wParam) { /* изменить позицию курсора в соответствии с на- жатой клавишей. Ускорить перемещение, добавляя счетчик повторений к позиции курсора. */ case VK_LEFT: ptCursor.x -=repeat; . Windows 3.0/pg/1#3 = 108 = break; case VK_RIGHT: ptCursor.x +=repeat; break; case VK_UP: ptCursor.y -=repeat; break; case VK_DOWN: ptCursor.y +=repeat; break; default: return(NULL); } /* Убедиться, что курсор не вышел за пределы области пользователя окна */ (5) GetClientRect(hWnd, &Rect); (6) if (ptCursor.x >= Rect.right) ptCursor.x = Rect.right - 1; else if (ptCursor.x < Rect.left) ptCursor.x = Rect.left; if (ptCursor.y >= Rect.bottom) ptCursor.y = Rect.bottom - 1; else if (ptCursor.y < Rect.top) ptCursor.y = Rect.top; (7) ClientToScreen(hWnd, &ptCursor); (8) SetCursorPos(ptCursor.x, ptCursor.y); break; case WM_KEYUP: (9) repeat = 1; /* Очистить счетчик. */ break; 1) Первый оператор if фильтрует значения виртуальных клавиш для клавиш VK_LEFT, VK_RIGHT, VK_UP и VK_DOWN. 2) Можно получить текущее положение курсора, использовав функцию GetCursorPos. Поскольку пользователь в любое время может переместить курсор с помощью мыши, то нет гарантии, что позиция, сохраненная при последнем нажатии на клавишу, будет правильной. 3) Функция ScreenToClient преобразует позицию курсора в координаты области пользователя. Это необходимо по двум причинам: сообщения мыши дают координаты в области пользователя, и координаты области пользователя не изменяются при перемещении окна. Другими, словами удобнее пользоваться координатами области пользователя, т.к. их использует система и т.к. они требуют меньше внимания от прикладной программы. . Windows 3.0/pg/1#3 = 109 = 4) Переменная repeat предоставляет возможность ускоренного перемещения курсора. Если пользователю необходимо переместиться на другой конец экрана, то перемещение курсора на один элемент за одно нажатие на клавишу может показаться для него утомительным. Можно ускорить движение курсора, увеличив число элементов, на которое курсор перемещается, когда пользователь держит клавишу нажатой. В этом случае Windows посылает серию сообщений WM_KEYDOWN без соответствующих сообщений WM_KEYUP. Для ускорения движения курсора необходимо просто увеличить число элементов, на которое делается продвижение по каждому сообщению WM_KEYDOWN. 5) Также необходимо контролировать, чтобы курсор при перемещении оставался в пределах области пользователя. Для этого имеется простой способ: используя функцию GetClientRect, определить текущий размер области пользователя. 6) Этот оператор if проверяет текущую позицию курсора, чтобы убедиться, что она находится в пределах области пользователя. При необходимости она изменяется. 7) Функция SetCursorPos перемещает курсор в заданную позицию. Заметим, что эта функция в качестве параметров требует координаты экрана, а не координаты пользователя. Это означает, что необходимо преобразовать координаты области пользователя, хранящиеся в структуре ptCursor, перед вызовом функции в координаты экрана, используя функцию ClientToScreen. 8) Функция SetCursorPos перемещает курсор в заданную позицию. 9) Когда пользователь освобождает клавишу, то необходимо восстановить первоначальное значение переменной повтора. Это можно сделать, использовав сообщение WM_KEYUP. 6.4.2 Использование курсора при отсутствии мыши. При отсутствии мыши программа должна индицировать и перемещать курсор в ответ на действия клавиатуры. Чтобы определить наличие "мыши", можно использовать функцию GetSystemMetrics с параметром SM_MOUSEPRESENT: GetSystemMetric (SM_MOUSEPRESENT); Эта функция возвращает TRUE, если мышь в системе установлена. При активировании программы необходимо индицировать курсор и скорректировать его положение, а когда программа деактивируется - сделать курсор невидимым. Это делает . Windows 3.0/pg/1#3 = 110 = приведенный ниже пример: case WM_ACTIVATE: if (!GetSystemMetrics(SM_MOUSEPRESENT)) { if (!HIWORD(lParam)) { if (wParam) { SetCursor(hMyCursor); ClientToScreen(hWnd, &ptCursor); SetCursorPos(ptCursor.x, ptCursor.y); } ShowCursor(wParam); } } break; Функции курсора вызываются только в том случае, если система не имеет мыши (т.е., если функция GetSystemMetrics возвращает FALSE). Это необходимо потому, что если в системе имеется мышь, Windows позиционирует и корректирует курсор автоматически, а при выполнении функций курсора эта обработка будет нарушена. Следующий шаг - определить, является ли окно иконой. Курсор не должен индицироваться или корректироваться, если окно - в виде иконы. В этом случае в сообщении WM_ACTIVATE старшее слово не равно нулю. Последний шаг - проверить параметр wParam, чтобы определить, активировано или деактивировано окно. Этот параметр не равен нулю, если окно активировано. Когда окно активировано, функция SetCursor устанавливает форму курсора, а функция SetCursorPos позиционирует его. Функция ClientToScreen преобразует координаты положения курсора в координаты экрана, как этого требует функция SetCursorPos. Наконец, функция ShowCursor индицирует или делает невидимым курсор в зависимости от значения параметра wParam. Если в системе мышь не установлена, то программа должна быть осторожна при использовании курсора. Вообще говоря, когда окно закрывается, разрушается или теряет управление, прикладная программа должна сделать курсор невидимым. Если прикладная программа даст сбой в тот момент, когда делает курсор невидимым, то последующие окна использовать курсор не смогут. Например, если программа устанавливает курсор в виде песочных часов, индицирует его, а затем передает управление панели диалога, курсор остается на экране (возможно в новой форме), но не может быть использован панелью диалога. 6.5 Пример прикладной программы: Cursor. В данном примере показывается, как включить курсор в программу, как использовать мышь и клавиатуру в прикладной программе. Прикладная программа Cursor иллюстрирует: . Windows 3.0/pg/1#3 = 111 = - как использовать заказной курсор в качестве курсора класса; - как индицировать курсор в виде песочных часов во время длительных операций; - как использовать мышь для выбора части области пользователя; - как использовать клавиатуру для перемещения курсора. Для создания прикладной программы Cursor скопируйте и переименуйте исходные файлы программы Generic, а затем выполните следующие модификации: 1. Добавьте оператор CURSOR к файлу описания ресурсов. 2. Добавьте новые переменные. 3. Загрузите заказной курсор и используйте его для установки курсора класса в функции инициализации. 4. Подготовьте курсор в виде песочных часов. 5. Добавьте длительную операцию к функции окна (для простоты используйте клавишу Enter для переключения операции). 6. Добавьте фрагменты WM_LBUTTONDOWN, WM_MOUSEMOVE и WM_LBUTTONUP к функции окна для поддержки выборки. 7. Добавьте фрагмент WM_KEYDOWN к функции окна для поддержки управляемого клавиатурой перемещения курсора. 8. Добавьте фрагмент WM_PAINT к функции окна для перерисовки помеченного прямоугольника в области пользователя. 9. Добавьте BULLSEYE.CUR к MAKE-файлу. 10. Откомпилируйте и скомпонуйте программу. Предполагается, что система имеет мышь (в противном случае прикладная программа может не работать как описано). Однако, изменить пример так, чтобы он работал только с клавиатурой или с клавиатурой и мышью - достаточно прозрачная задача. Вместо того, чтобы вводить тексты, приведенные в следующих разделах, возможно вам будет удобнее просто переписать исходные тексты из SDK. . Windows 3.0/pg/1#3 = 112 = 6.5.1 Добавление оператора CURSOR. Для использования собственного курсора необходимо создать файл курсора, используя SDKPaint, и дать ему имя в операторе CURSOR файла описания ресурсов. Добавьте следующий оператор к файлу описания ресурсов: bullsey CURSOR bullseye.cur Необходимо быть уверенным в том, что файл курсора bullseye.cur содержит курсор. 6.5.2. Добавление новых переменных Для данной программы необходимы несколько новых переменных. Поместите приведенные ниже операторы в начало исходной С-программы: char str[255] /* строковый буфер общего назна- чения */ HCURSOR hSaveCursor; /* дескриптор текущего курсора */ HCURSOR hHourGlass; /* дескриптор курсора "песочные часы" */ BOOL bTrack = FALSE; /* TRUE, если нажата левая кнопка */ int OrgX = 0, OrgY = 0; /* начальная позиция курсора */ int PrevX = 0, PrevY = 0; /* текущая позиция курсора */ int X = 0, Y = 0; /* конечная позиция курсора */ RECT Rect; /* прямоугольник выборки */ POINT ptCursor; /* координаты курсора по осям x и y */ int repeat = 1; /* счетчик повторения нажатий на клавиши */ Переменные hSaveCursor и hHourGlass содержат дескрипторы курсора, которые будут использоваться в длительных операциях. Параметр bTrack содержит булевский флаг, указывающий, выполняется ли выборка. Переменные OrgX, OrgY, PrevX и PrevY содержат начальную и текущую позиции мыши при выборке. Переменные OrgX, OrgY, X и Y содержат начальную и конечную координаты выборки при ее завершении. Структура ptCursor содержит текущую позицию курсора в области пользователя. Она корректируется, когда пользователь нажимает одну из клавиш стрелок. Структура Rect содержит текущий размер области пользователя и используется для уверенности в том, что курсор остается в области пользователя. Переменная repeat содержит текущее значение счетчика повтора при работе с клавиатурой. 6.5.3 Установка курсора класса. Для того, чтобы установить курсор класса, необходимо модифицировать оператор в функции инициализации. Другими словами, необходимо присвоить дескриптор курсора полю hCursor структуры класса окна. Необходимо внести изменения в . Windows 3.0/pg/1#3 = 113 = исходном С-файле. Найдите строку: wc.hCursor=LoadCursor(NULL, IDC_ARROW); Измените ее: wc.hCursor=LoadCursor(hInstance, "bullseye"); 6.5.4 Подготовка курсора в виде песочных часов. Поскольку необходимо использовать курсор песочных часов во время длительных операций, нужно загрузить его. Наиболее удобный способ - загрузить его в фрагменте WM_CREATE при создании окна. Добавьте следующий оператор: hHourGlass=LoadCursor(NULL, IDC_WAIT); Это сделает песочные часы доступными при необходимости. 6.5.5 Добавление длительной операции. Длительные операции могут иметь много форм. В нашем примере это функция с именем "sieve" (сито), которая находит несколько сотен простых чисел. Операция начинается, когда пользователь нажимает клавишу Enter. Добавьте следующие операции к функции окна: case WM_CHAR: if (wParam == '\r') { SetCapture(hWnd); hSaveCursor = SetCursor(hHourGlass); strcpy (str, "Вычисление простых чисел ..."); InvalidateRect (hWnd, NULL, TRUE); UpdateWindow (hWnd); wsprintf(str, "Вычислено %d чисел. ", sieve()); InvalidateRect (hWnd, NULL, TRUE); UpdateWindow (hWnd); SetCursor(hSaveCursor); /* Восстановить старый курсор */ ReleaseCapture(); } break; При нажатии клавиши Enter Windows генерирует сообщение WM_CHAR, параметр wParam которого содержит значение в коде ANSI, представляющее код возврата каретки. Когда функция окна получает сообщение WM_CHAR, она контролирует наличие этого кода и выполняет длительную операцию (функцию sieve). Эта функция названа "Решето Эратосфена" и была приведена в "Byte", номер за Январь, 1983 года: . Windows 3.0/pg/1#3 = 114 = #define NITER 20 #define SIZE 8190 char flags[SIZE+1] = { 0}; sieve() { int i,k; int iter, count; for (iter = 1; iter <= NITER; iter++) { count = 0; for (i = 0; i <= SIZE; i++) flags[i] = TRUE; for (i = 2; i <= SIZE; i++) { if (flags[i] ) { for (k = i + i; k <= SIZE; k += i) flags[k] = FALSE; count++; } } } return (count); } 6.5.6 Добавление фрагментов WM_LBUTTONDOWN, WM_MOUSEMOVE и WM_LBUTTONUP Для выполнения выборки можно добавить операторы, как это описано в подразделе 6.3. "Выборка с использованием мыши". Добавьте следующие операторы к функции окна: case WM_LBUTTONDOWN: bTrack = TRUE; PrevX = LOWORD(lParam); PrevY = HIWORD(lParam); if (!(wParam & MK_SHIFT)) { /* Если не нажата клавиша shift */ OrgX = LOWORD(lParam); OrgY = HIWORD(lParam); } InvalidateRect (hWnd, NULL, TRUE); UpdateWindow (hWnd); /* Захватить весь ввод от мыши, даже если она находится вне окна */ SetCapture(hWnd); break; case WM_MOUSEMOVE: { RECT rectClient; int NextX; . Windows 3.0/pg/1#3 = 115 = int NextY; if (bTrack) { NextX = LOWORD(lParam); NextY = HIWORD(lParam); /* Не рисовать вне области пользователя окна */ GetClientRect (hWnd, &rectClient); if (NextX < rectClient.left) { NextX = rectClient.left; } else if (NextX >= rectClient.right) { NextX = rectClient.right - 1; } if (NextY < rectClient.top) { NextY = rectClient.top; } else if (NextY >= rectClient.bottom) { NextY = rectClient.bottom - 1; } /* Если позиция мыши измениласть, то стереть старый */ /* и рисовать новый прямоугольник */ if ((NextX != PrevX) || (NextY != PrevY)) { hDC = GetDC(hWnd); SetROP2(hDC, R2_NOT); /* Очистить старый прямоугольник */ MoveTo(hDC, OrgX, OrgY); LineTo(hDC, OrgX, PrevY); LineTo(hDC, PrevX, PrevY); LineTo(hDC, PrevX, OrgY); LineTo(hDC, OrgX, OrgY); /* Получить текущую позицию курсора */ PrevX = NextX; PrevY = NextY; MoveTo(hDC, OrgX, OrgY); /* Нарисовать новый прямоугольник */ LineTo(hDC, OrgX, PrevY); LineTo(hDC, PrevX, PrevY); LineTo(hDC, PrevX, OrgY); LineTo(hDC, OrgX, OrgY); ReleaseDC(hWnd, hDC); } } } break; case WM_LBUTTONUP: bTrack = FALSE; /* Завершение выборки */ ReleaseCapture(); /* освободить мышь */ X = LOWORD(lParam);/* сохранить текущее значение */ . Windows 3.0/pg/1#3 = 116 = Y = HIWORD(lParam); break; 6.5.7 Добавление фрагмента WM_KEYDOWN и WM_KEYUP. Для того, чтобы использовать клавиатуру для управления курсором, необходимо добавить фрагмент WM_KEYDOWN к функции окна. Операторы этого фрагмента должны получить текущую позицию курсора и скорректировать ее при нажатии одной из клавиш стрелок. Добавьте следующие операторы к функции окна: case WM_KEYDOWN: GetCursorPos(&ptCursor); if (wParam != VK_LEFT && wParam != VK_RIGHT && wParam != VK_UP && wParam !=VK_DOWN) break; /* преобразовать координаты экрана в координаты области пользователя */ ScreenToClient(hWnd, &ptCursor); repeat++; /* увеличить счетчик повторений */ switch (wParam) { /* изменить позицию курсора в соответствии с на- жатой клавишей. Ускорить перемещение, добавляя счетчик повторений к позиции курсора. */ case VK_LEFT: ptCursor.x -=repeat; break; case VK_RIGHT: ptCursor.x +=repeat; break; case VK_UP: ptCursor.y -=repeat; break; case VK_DOWN: ptCursor.y +=repeat; break; default: return(NULL); } /* Убедиться, что курсор не вышел за пределы области пользователя окна */ GetClientRect(hWnd, &Rect); if (ptCursor.x >= Rect.right) . Windows 3.0/pg/1#3 = 117 = ptCursor.x = Rect.right - 1; else if (ptCursor.x < Rect.left) ptCursor.x = Rect.left; if (ptCursor.y >= Rect.bottom) ptCursor.y = Rect.bottom - 1; else if (ptCursor.y < Rect.top) ptCursor.y = Rect.top; ClientToScreen(hWnd, &ptCursor); SetCursorPos(ptCursor.x, ptCursor.y); break; Функция GetCursorPos возвращает положение курсора в координатах экрана. Для контроля того, что курсор находится в области пользователя, эти координаты преобразуются в координаты пользователя с помощью функции ScreenToClient. Оператор switch контролирует клавиши стрелок и добавляет содержимое поля счетчика повторения параметра lParam к текущей позиции. Новая позиция проверяется на принадлежность к области пользователя, используя функцию GetClientRect, которая возвращает размеры области пользователя. Если необходимо, положение корректируется. Наконец, функция ClientToScreen вновь преобразует координаты положения курсора в координаты экрана, а функция SetCursorPos устанавливает новую позицию. Фрагмент WM_KEYUP восстанавливает исходное значение счетчика в тот момент, когда пользователь отпускает клавишу. Это делается следующим образом: case WM_KEYUP: repeat = 1; /* Очистить счетчик. */ break; Для того, чтобы быть уверенным, что текстовая строка и выбранный прямоугольник перерисовываются, когда это необходимо (например, когда другое окно временно накрывает область пользователя), добавьте следующий фрагмент к функции окна: case WM_PAINT: { PAINTSTRUCT ps; hDC = BeginPaint (hWnd, &ps); if (OrgX != PrevX || OrgY != PrevY) { MoveTo(hDC, OrgX, OrgY); LineTo(hDC, OrgX, PrevY); LineTo(hDC, PrevX, PrevY); LineTo(hDC, PrevX, OrgY); LineTo(hDC, OrgX, OrgY); } TextOut (hDC, 1, 1, str, strlen (str)); EndPaint (hWnd, &ps); . Windows 3.0/pg/1#3 = 118 = } break; 6.5.8 Добавьте файл BULLSEYE.CUR к MAKE-файлу. В MAKE-файле добавьте файл BULLSEYE.CUR к списку файлов, от которых зависит CURSOR.RES. Соответствующие строки будут выглядеть следующим образом: CURSOR.RES: CURSOR.RC CURSOR.H BULLSEYE.CUR RC -r CURSOR.RC Тогда при изменении файла BULLSEYE.CUR файл CURSOR.RES будет перекомпилирован. 6.5.9 Трансляция и компоновка. Перекомпилируйте и скомпонуйте программу Cursor. Затем запустите Windows и программу Cursor. Когда курсор переместится в область пользователя, он примет вид мишени. Нажмите и держите нажатой левую кнопку мыши, затем переместите ее в новую позицию и освободите кнопку мыши. При этом выборка будет выглядеть как показано на рис. 6.1. Рисунок 6.1. Выборка в программе Cursor. 1. Начальная точка. 2. Конечная точка. Теперь нажмите одну из клавиш стрелок для перемещения курсора. Затем нажмите клавишу Enter для того, чтобы посмотреть на выполнение длительной операции. 6.6 Заключение. В данной главе описывается, как использовать курсоры в прикладных программах Windows. Курсор - это специальная растровая карта, которая позволяет пользователю отслеживать действия, выполняемые с помощью курсора. Windows позволяет изменить форму курсора для удовлетворения различных требований прикладной программы. Вы можете использовать встроенные в Windows формы курсора или создать собственные с помощью SDKPaint. Windows автоматически отслеживает большинство действий мыши, однако выборку в области пользователя должна выполнять сама прикладная программа. Т.к. Windows не требует мыши, или другого устройства манипулирования, вы, возможно, захотите включить в программу функции для работы с клавиатурой. Дополнительную информацию относительно курсоров вы найдете в: . Windows 3.0/pg/1#3 = 119 = Раздел Руководство --------------------------------------------------------------- Ввод с использова- Руководство программиста, Глава 4, "Ввод с нием мыши и клавиа- использованием мыши и клавиатуры" туры Функции курсора Справочное руководство, том 1, глава 1, "Функции интерфейса управления окнами", глава 4, "Список функций". Сообщения ввода и Справочное руководство, том 1, глава 5, сообщения управле- "Обзор сообщений", глава 6, "Список сооб- ния окнами. щений". Операторы описания Справочное руководство, том 2, глава 8, ресурсов "Операторы описания ресурсов". Использование "Tools", глава 4, "Создание изображений". SDKPaint . Windows 3.0/pg/1#3 = 120 = Глава 7. Меню. ---------------------------------------------------------------- Большинство прикладных программ Windows используют меню для того, чтобы пользователи могли выбрать команды или действия. В данной главе описаны следующие темы: - Что такое меню. - Определение меню. - Включение меню в прикладную программу. - Обработка ввода от меню. - Модификация существующего меню. - Работа со специальными возможностями меню. В данной главе описан процесс создания простой программы, EditMenu, которая использует и обрабатывает ввод от меню. 7.1 Что такое меню. Меню - это перечень элементов, называемых элементами меню, которые являются именами или растровыми картами, представляющи- ми действия, которые может выполнять прикладная программа. Пользователь может заставить программу выполнить команду или с помощью "мыши" или с помощью клавиатуры, выбрав соответствующий элемент меню. Когда пользователь выбирает команду, Windows посылает сообщение прикладной программе, определяя, какая ко- манда была выбрана. Для использования меню в прикладной программе вы должны выполнить следующие действия: 1. Описать меню в файле описания ресурсов прикладной программы. 2. Определить меню в тексте прикладной программы. Это можно сделать двумя способами: - При регистрации класса окна указать меню как меню класса для всех окон данного класса. - Указать меню при создании окна (только для данного окна). 3. Произвести инициализацию меню, если она требуется. После создания и инициализации меню произойдет следующее: . Windows 3.0/pg/1#3 = 121 = - Пользователь может выбрать команды в меню. Когда пользователь выбирает команду (элемент меню), Windows посылает вашей программе сообщение ввода, которое включает идентификатор элемента меню. - Ваша прикладная программа может добавлять, заменять или изменять элементы меню или даже все меню, если возникнет необходимость. 7.2 Определение меню. Первым шагом при использовании меню необходимо определить меню в файле описания ресурсов (.RC) прикладной программы с помощью оператора MENU. Оператор MENU определяет: - Имя меню. - Элементы меню. - Идентификаторы каждого элемента меню. - Текст или растровую карту каждого элемента меню. - Специальные атрибуты каждого элемента меню. Оператор MENU состоит из имени меню, ключевого слова MENU и пары ключевых слов BEGIN и END, которые заключают между собой один или несколько операторов, которые могут быть следующими: - Оператор MENUITEM определяет элемент меню, его вид и идентификатор. Когда пользователь выбирает элементы меню, Windows сообщает программе о выбранном элементе. - Оператор POPUP определяет накладываемое меню, которое содержит список элементов накладываемого меню. Когда пользователь выбирает накладываемое меню, Windows выводит список элементов. Затем пользователь может выбрать элемент в накладываемом меню. Затем Windows сообщает программе о выбранном элементе. Например следующий оператор MENU определяет меню с именем "SampleMenu": (1) SampleMenu MENU BEGIN (2) MENUITEM "Exit!", IDM_EXIT MENUITEM "Recalculate!", IDM_RECALC (3) POPUP "Options" BEGIN . Windows 3.0/pg/1#3 = 122 = (4) MENUITEM "Scylla", IDM_SCYLLA MENUITEM "Charybdis", IDM_CHERRYBDIS END END В данном примере: 1) Эта строка сообщает компилятору ресурсов, что это начало определения меню с именем "SampleMenu". Оператор MENU состоит из имени меню, ключевого слова MENU, и пары ключевых слов BEGIN и END, которые заключают операторы определения элементов меню. 2) Этот оператор MENUITEM определяет первый элемент в меню. Текст "Exit!" будет первым элементом в строке меню. При выборе пользователем команды "Exit!" Windows посылает прикладной программе сообщение WM_COMMAND, которое содержит идентификатор элемента меню в параметре сообщения wParam (IDM_EXIT). Следующий оператор MENUITEM аналогичным образом определяет элемент "Recalculate!". 3) Оператор POPUP определяет накладываемое меню. В строке меню будет присутствовать строка "Options". При выборе пользователем команды "Options" Windows выведет накладываемое меню, в котором пользователь может выбирать между командой "Scylla" и "Charybdis". 4) Внутри оператора POPUP содержатся определения элементов накладываемого меню. Для меню "Options" это два элемента каждый со своим идентификатором и текстовой строкой. При выборе пользователем одного из элементов "Exit!", "Recalculate!", "Scylla" и "Charybdis", Windows сообщает об этом прикладной программе, передавая ей идентификатор соответствующего элемента меню. Заметим, что Windows не сообщает программе о выборе пользователем элемента "Options". Вместо этого Windows просто отображает накладываемое меню. Дополнительную информацию об операторах MENU, MENUITEM и POPUP вы найдете во втором томе Справочного руководства. 7.2.1 Идентификаторы меню. Каждый элемент меню имеет уникальный идентификатор, обычно называемый идентификатором меню. Когда пользователь выбирает элемент меню, Windows передает прикладной программе идентификатор этого элемента. Идентификатор меню должен быть уникальной константой. Вы можете определить все идентификаторы как константы с помощью директивы #define в файле описания ресурсов или во включаемом файле. Например: #define IDM_EXIT 111 #define IDM_RECALC 112 . Windows 3.0/pg/1#3 = 123 = #define IDM_SCYLLA 113 #define IDM_CHARYBDIS 114 Идентификаторы элементов меню используются для направления обработки команды в зависимости от идентификатора. Более подробную информацию об обработке ввода от меню вы найдете в разделе 7.4 "Обработка ввода от меню". 7.3 Подключение меню к прикладной программе. После того, как вы определили меню в файле описания ресурсов прикладной программы, вы можете включить его в саму программу. Вы указываете меню, связывая его с окном. Любое перекрывающееся или накладываемое окно может иметь меню: дочернее окно не может иметь никакого меню, за исключением системного. Для подключения меню к прикладной программе имеется два метода: - Указать меню в качестве меню класса при регистрации класса окна. Все окна этого класса в этом случае будут иметь это меню. - Указать меню при создании меню. Это окно затем будет иметь меню. Ниже описаны оба эти метода. 7.3.1 Определение меню в качестве меню класса. При регистрации класса окна вы указываете атрибуты, которые будут по умолчанию иметь все окна данного класса. Вы можете указать меню в качестве меню по умолчанию для данного класса, это меню и называется меню класса. Меню класса определяется при регистрации класса окна. Для того, чтобы определить меню класса, необходимо присвоить имя меню, заданное в файле описания ресурсов, полю lpszMenuName структуры класса окна. wc.lpszMenuName = "SampleMenu"; В этом примере поле lpszMenuName - это часть структуры данных wc типа WNDCLASS. Имя меню "SampleMenu" - это имя, присвоенное меню в файле описания ресурсов. После регистрации меню класса каждое окно этого класса будет иметь меню класса, если только это значение по умолчанию не будет изменено с помощью явного определения дескриптора меню при создании окна данного класса. 7.3.2 Установка меню окна. Окно не обязано использовать меню класса. Меню класса используется только по умолчанию. Для того, чтобы использовать . Windows 3.0/pg/1#3 = 124 = в окне другое меню, его нужно указать при создании окна. Для указания меню окна нужно сделать следующее: - Загрузить меню из ресурсов прикладной программы с помощью функции LoadMenu. Эта функция возвращает дескриптор меню. - При вызове функции CreateWindow передайте ей этот дескриптор в качестве параметра hMenu. Приведенный ниже пример показывает, как загрузить и специфицировать меню, используя функцию CreateWindow: HWND hWnd; /* инициализируйте переменную и поместите в нее дескриптор текущего окна. */ HMENU hMenu; /* инициализируйте переменную и поместите в дескриптор меню */ . . . (1) hMenu = LoadMenu(hInstance, "SampleMenu"); (2) hWnd = CreateWindow("SampleWindow", "SampleWindow", WS_OVERLAPPEDWINDOW, CW_USERDEFAULT, CW_USERDEFAULT, CW_USERDEFAULT, CW_USERDEFAULT, (HWND) NULL, (3) hMenu, hInstance, (LPSTR) NULL); В данном примере: 1) Функция LoadMenu загружает меню с именем "SampleMenu". Переменная hInstance специфицирует, что ресурс должен быть загружен из ресурсов прикладной программы. LoadMenu возвращает дескриптор меню, который помещается в переменную hMenu. 2) Программа затем вызывает функцию CreateWindow для создания окна с именем SampleWindow. 3) Возвращаемый функцией LoadMenu дескриптор меню используется в функции CreateWindow для того, чтобы заменить меню класса, если оно существует. . Windows 3.0/pg/1#3 = 125 = 7.4 Обработка ввода от меню. При выборе пользователем команды в меню Windows посылает сообщение WM_COMMAND соответствующей функции окна прикладной программы. Сообщение содержит в параметре wParam идентификатор выбранного элемента меню. Функция окна отвечает за выполнение всех действий, связанных с данной командой. Например, если пользователь выбирает команду "Open", то функция окна запрашивает имя файла, открывает его и выводит содержимое в области пользователя окна. Чаще всего ввод из меню обрабатывается в операторе switch в функции окна. Обычно оператор switch направляет обработку ввода из меню в зависимости от содержимого wParam. Каждый case обрабатывает свою команду по ее идентификатору. Например: case WM_COMMAND: (1) switch (wParam) { (2) case IDM_NEW: /* выполнение операций по созданию нового файла */ break; case IDM_OPEN: /* выполнение операций по открытию файла */ break; case IDM_SAVE: /* выполнение операций по сохранению файла */ break; case IDM_SAVEAS: /* выполнение операций по сохранению файла */ break; case IDM_EXIT: /* выполнение операций по выходу из прикладной программы */ break; } break; В данном примере: (1) Параметр wParam содержит идентификатор выбранного пользователем элемента меню. (2) Для каждого элемента меню прикладная программа должна выполнить соответствующие действия. 7.5 Работа с меню из прикладной программы. Windows предоставляет функции, которые можно использовать для изменения существующего меню и для создания нового меню во время выполнения прикладной программы. В данном разделе описано: . Windows 3.0/pg/1#3 = 126 = - Как разрешить или запретить выбор элемента меню. - Как пометить и снять контрольную отметку. - Как добавить, изменить или удалить элемент меню. - Как использовать в качестве элементов меню растровые карты. - Как заменить меню. - Как создать и инициализировать меню из прикладной программы. При создании окна оно получает собственную копию меню класса. Прикладная программа затем может изменять собственную копию меню и это не повлияет на копии этого меню других окон данного класса. Замечание: При изменении элементов в строке меню вы должны вызвать функцию DrawMenuBar для отображения на экране сделанных изменений. 7.5.1 Доступные и недоступные элементы меню. Обычно все элементы меню доступны; они выводятся стандартным шрифтом и пользователь может их выбрать. Недоступный элемент меню отображается нормальным образом, но не отвечает на нажатие курсором мыши и клавиатуры. Элемент меню, выведенный серым цветом, также не отвечает на нажатие курсором мыши и клавиатуры. Обычно вы делаете элемент недоступным или выводите его серым цветом, когда действие, за которое он отвечает, неразрешено. Например, вы можете сделать серым элемент "Print" в меню "File", если принтер не подключен. Установка исходного состояния элементов. Можно специфицировать начальное состояние меню или команд, использовав параметры INACTIVE и GRAYED с оператором MENUITEM в файле описания ресурсов. Например, приведенный ниже оператор устанавливает начальное состояние команды Печать как "серое": MENUITEM "Print", IDM_PRINT, GRAYED Этот параметр применяется только к начальному состоянию меню. Можно изменить состояние, использовав функцию EnableMenuItem в исходном С-файле. Функция EnableMenuItem позволяет сделать элемент доступным, недоступным или серым. Как сделать недоступным элемент меню. Недоступный элемент меню выглядит обычным образом, однако не реагирует на "клик" мыши или на выбор с помощью клавиатуры. . Windows 3.0/pg/1#3 = 127 = Недоступные элементы меню обычно используются в качестве заголовков для зависимых элементов. Ниже приведен оператор, который делает недоступным элемент меню: EnableMenuItem(hMenu, IDM_SAVE, MF_DISABLED); В данном примере делается недоступным элемент меню, определяемого параметром hMenu. Идентификатор элемента IDM_SAVE. Константа MF_DISABLED сообщает Windows, что данный элемент должен быть сделан недоступным. Как сделать элемент недоступным и серым. Когда вы хотите указать пользователю, что элемент недоступен, его можно сделать серым, кроме того, что он будет недоступным. Когда вы делаете элемент серым, элемент делается недоступным и его текст выводится серым цветом. Чтобы сделать элемент недоступным и серым надо в качестве параметра функции EnableMenuItem указать константу MF_GRAYED. Например: EnableMenuItem(hMenu, IDM_PRINT, MF_GRAYED); В данном примере делается недоступной команда меню, которое определяется параметром hMenu. Идентификатор элемента меню IDM_PRINT. Константа MF_GRAYED сообщает Windows, что данный элемент должен быть сделан недоступным и выведен серым цветом. Как сделать элемент доступным. Чтобы сделать доступным недоступный элемент меню необходимо указать в вызове функции EnableMenuItem константу MF_ENABLED. В данном примере делается доступным элемент IDM_EXIT: EnableMenuItem(hMenu, IDM_EXIT, MF_ENABLED); 7.5.2 Контроль элементов меню. Можно поместить или удалить контрольную отметку, располагающуюся рядом с указанным элементом меню. Элемент меню обычно контролируется, когда он принадлежит группе команд, взаимно исключающих друг друга. Контрольная отметка указывает последний выбор пользователя. Например, в группе, состоящей из элементов Left, Right и Center, вы можете пометить элемент Left, чтобы показать, что он был выбран последним. Установка исходной контрольной отметки. Можно поместить начальную контрольную отметку рядом с командой, используя параметр CHECKED в операторе MENUITEM файла . Windows 3.0/pg/1#3 = 128 = описания ресурсов. В данном примере отметка помещается рядом с элементом Left: MENUITEM "Left", IDM_LEFT, CHECKED Установка контрольной отметки. Информация из файла описания ресурсов отражается только на исходном состоянии меню. Вы можете поместить или удалить контрольную отметку элемента меню во время выполнения программы, используя функцию CheckMenuItem. В приведенном ниже примере контрольная отметка помещается рядом с элементом, ID которого равен IDM_LEFT: CheckMenuItem(hMenu, IDM_LEFT, MF_CHECKED); Удаление контрольной отметки. Для удаления контрольной отметки элемента вы вызываете функцию CheckMenuItem и передаете ей значение MF_UNCHECKED. В последующем примере контрольная отметка элемента, ID которого IDM_RIGHT, удаляется (если она есть): CheckMenuItem(hMenu, IDM_RIGHT, MF_UNCHECKED); Если изменился элемент меню в меню-строке, необходимо вызвать функцию DrawMenuBar для индикации изменений. 7.5.3 Добавление элементов к меню. Вы можете добавить новые элементы меню в конец меню или вставить после определенного элемента. Добавление элемента меню. Для добавления элемента в конец существующего меню вы используете функцию AppendMenu. Эта функция добавляет новый элемент к указанному меню и позволяет вам определить, должен ли этот элемент быть доступным, недоступным, серым и должен ли он иметь контрольную отметку. Ниже приведен пример, в котором к меню "Fruits" добавляется элемент "Raspberries". Причем, если сейчас не сезон, то данный элемент делается серым и недоступным. if (!RaspberriesInSeason) AppendMenu(hFruitMenu, MF_GRAYED, IDM_RASPBERRIES, "Raspberries"); else AppendMenu(hFruitMenu, . Windows 3.0/pg/1#3 = 129 = MF_ENABLED, IDM_RASPBERRIES, "Raspberries"); Вставка элемента меню. Для вставки элемента в существующее меню используется функция InsertMenu. Эта функция вставляет указанный элемент в указанную позицию и перемещает последующие элементы вниз, в соответствии с новым элементом. Как и AppendMenu, функция InsertMenu позволяет вам указать состояние нового элемента. В данном примере производится вставка элемента "Kumquats" перед существующим элементом "Melons". Причем новый элемент делается серым и недоступным. InsertMenu(hFruitMenu, IDM_MELONS, MF_BYCOMMAND | MF_GRAYED, IDM_KUMQUATS, "Kumquats"); Вы можете также вставить элемент в определенную позицию, а не перед каким-либо элементом. Ниже приведен пример, в котором вставляется элемент "Bananas" таким образом, что он становится третьим элементом в меню "Fruits". (Первый элемент находится на позиции 0, второй - 1 и т.д.). InsertMenu(hFruitMenu, 2, MF_BYCOMMAND | MF_GRAYED, IDM_BANANAS, "Bananas"); 7.5.4 Модификация существующего меню. Существующее меню и его элементы можно изменить с помощью функции ModifyMenu. Например, вы можете изменить текст элемента. ModifyMenu позволяет указать и состояние элемента. Ниже приведен пример, в котором текст элемента меню "Water" меняется на "Wine". Кроме этого изменяется его идентификатор. ModifyMenu(hMenu, IDM_WATER, MF_BYCOMMAND, IDM_WINE, "Wine"); При использовании функции ModifyMenu вы сообщаете Windows, что необходимо заменить элемент меню новым. Третий, четвертый и . Windows 3.0/pg/1#3 = 130 = пятый параметры функции ModifyMenu определяют атрибуты нового элемента. Например, ниже приведен пример, в котором изменяется только текст элемента "Wine" на "Cabernet". Но независимо от этого, вы должны заново определить все атрибуты элемента (в данном случае идентификатор элемента). ModifyMenu(hMenu, IDM_WINE, MF_BYCOMMAND, IDM_WINE, "Cabernet"); Выполнение нескольких изменений одновременно. При использовании функции ModifyMenu для изменения элемента вы можете также поместить или удалить контрольную отметку и сделать элемент доступным, недоступным или серым. Ниже, в примере элемент "Water" заменяется на элемент "Wine", он делается доступным (если он был недоступным), рядом с ним помещается контрольная отметка и изменяется его идентификатор. ModifyMenu(hMenu, IDM_WATER, MF_BYCOMMAND | MF_ENABLED | MF_CHECKED, IDM_WINE, "Wine"); 7.5.5 Удаление элемента меню. Вы можете удалить элемент меню и любое накладываемое меню, связанное с этим элементом с помощью функции DeleteMenu. Функция DeleteMenu удаляет указанный элемент данного меню, и перемещает следующие за ним элементы, чтобы заполнить образовавшуюся дыру. DeleteMenu(hFruitMenu, /* дескриптор меню */ 1, /* удалить второй элемент */ MF_BYPOSITION); /* элемент определяется по его позиции в меню. */ В данном примере производится удаление второго элемента меню "Fruit". Все следующие элементы Windows перемещает вверх, чтобы заполнить образовавшуюся дыру. В следующем примере удаляется тот же элемент, но он определяется не позицией, а его идентификатором: DeleteMenu(hFruitMenu, /* дескриптор меню */ . Windows 3.0/pg/1#3 = 131 = IDM_ORANGES, /* удалить элемент "Oranges" */ MF_BYCOMMAND); /* элемент определяется по его идентификатору. */ 7.5.6 Использование в качестве элементов меню растровых карт. Windows позволяет в качестве элементов меню использовать растровые карты. Это можно сделать двумя способами: - При вставке или добавлении элемента к меню вы можете указать, что вместо текста должна использоваться растровая карта. - С помощью функции ModifyMenu изменить элемент так, чтобы он отображался в виде растровой карты. Вы не можете указать элемент меню в виде растровой карты в файле описания ресурсов (.RC). В следующем примере загружается растровая карта "Apples", и затем функция ModifyMenu используется для замены текста команды "Apple" на растровую карту с изображением яблока. HMENU hMenu; HBITMAP hBitmap; . . . (1) hBitmap = LoadBitmap(hInstabce, "Apples"); (2) hMenu = GetMenu(hWnd); ModifyMenu(hMenu, (3) IDM_APPLES, /* заменяемый элемент */ (4) MF_BYCOMMAND | MF_BITMAP, (5) IDM_APPLES, /* идентификатор нового элемента */ (6) (LPSTR) MAKELONG (hBitmap,0)); В данном примере: 1) Функция LoadBitmap загружает растровую карту из файла и возвращает дескриптор растровой карты, сохраняемый в переменной hBitMap. 2) Функция GetMenu получает дескриптор меню текущего окна и помещает его в переменную hMenu. Эта переменная затем используется как первый параметр в вызове функции ModifyMenu, который определяет изменяемое меню. 3) Второй параметр функции ModifyMenu, в данном случае IDM_APPLES, который определяет изменяемый элемент меню. 4) Третий параметр определяет, как должны выполняться изменения. MF_BYCOMMAND сообщает Windows, что изменяемый . Windows 3.0/pg/1#3 = 132 = элемент определяется идентификатором, а не номером. MF_BITMAP сообщает, что новый элемент должен выводиться в виде растровой карты а не текста. 5) Четвертый параметр функции ModifyMenu определяет новый идентификатор элемента, IDM_APPLES. В данном случае идентификатор не изменяется. 6) Дескриптор растровой карты посылается в старшем слове пятого параметра функции ModifyMenu. MAKELONG создает 32-х разрядное значение, комбинируя 16-разрядный дескриптор и 16-разрядную константу. Преобразование этого числа к типу LPSTR предотвращает выдачу предупреждения, т.к. компилятор подразумевает, что в данном параметре передается строка. 7.5.7 Замена меню. Можно заменить меню окна с помощью функции SetMenu. Эта функция обычно используется, когда прикладная программа изменяет режимы и для нее необходим полностью новый набор команд. Например, прикладная программа может заменить меню электронной таблицы на меню графики, когда пользователь переключает режим на графику. В приведенном ниже примере функция GetMenu возвращает дескриптор меню электронной таблицы из указанного окна и сохраняет его для последующего восстановления. Функция SetMenu заменяет его на меню графики, загружаемое из ресурсов прикладной программы. HMENU hMenu; HMENU hSpreadsheetMenu; . . . hOldMenu = GetMenu(hWnd); hMenu = LoadMenu(hInstance, "ChartMenu"); SetMenu(hWnd, hMenu); . . . Меню может также загружаться из ресурсов, не принадлежащих данной прикладной программе (с помощью дескриптора модуля библиотеки). 7.5.8 Создание нового меню. Вы можете создать новое меню во время выполнения программы с помощью функции CreateMenu. Функция CreateMenu создает новое, пустое меню. Вы можете затем добавить элементы, используя функции AppendMenu и InsertMenu. . Windows 3.0/pg/1#3 = 133 = Следующий пример создает пустое накладываемое меню и добавляет его к меню окна. Затем к данному меню добавляются элементы. HMENU hWinMenu; HMENU hVeggieMenu; . . . hVeggieMenu = CreateMenu(); AppendMenu(hWinMenu, MFPOPUP | MF_ENABLED, hVeggieMenu, "Veggies"); AppendMenu(hVeggieMenu, MF_ENABLED, IDM_CELERY, "Celery"); AppendMenu(hVeggieMenu, MF_ENABLED, IDM_LETTUCE, "Lettuce"); AppendMenu(hVeggieMenu, MF_ENABLED, IDM_PEAS, "Peas"); 7.5.9 Инициализация меню. Если нужно, ваша прикладная программа может инициализировать меню перед тем, как Windows отобразит его. Хотя вы определяете исходное состояние элементов меню в файле описания ресурсов прикладной программы, это не помогает, если исходное состояние должно быть другим. Например, если вы хотите, чтобы команда "Print" делалась недоступной, если в системе нет принтера, то это необходимо сделать только во время инициализации меню. (Делать это в файле описания ресурсов прикладной программы бессмысленно, т.к. вы не знаете, будет ли подключен принтер к системе в тот момент, когда будет работать ваша программа.) Windows посылает сообщение WM_INITMENU функции окна, обладающего меню, сразу перед индикацией меню. Это позволяет функции окна проверить и изменить состояние элементов меню перед его индикацией. В приведенном ниже примере функция окна обрабатывает сообщение WM_INITMENU, устанавливая состояние команды, основываясь на значении переменной wChecked: WORD wChecked; . Windows 3.0/pg/1#3 = 134 = . . . (1) case WM_INITMENU: (2) if (GetMenu(hWnd) != wParam) break; CheckMenuitem(wParam, IDM_LEFT, IDM_LEFT == wChecked ? MF_CHECKED : MF_UNCHECKED); CheckMenuitem(wParam, IDM_CENTER, IDM_CENTER == wChecked ? MF_CHECKED : MF_UNCHECKED); CheckMenuitem(wParam, IDM_RIGHT, IDM_RIGHT == wChecked ? MF_CHECKED : MF_UNCHECKED); break; 1) Сообщение WM_INITMENU передает заданный дескриптор меню параметру wParam. 2) Для того, чтобы быть уверенным, что индицируемое меню - это действительно меню окна, функция GetMenu возвращает дескриптор текущего меню, который сравнивается с wParam. Если они не равны, меню окна не должно быть инициализировано. В противном случае можно использовать функцию CheckMenuItem для инициализации команд меню. 7.6 Специальные возможности меню. До сих пор мы обсуждали стандартные меню, которые выпадают из строки меню и содержат элементы, которые пользователь может выбирать, используя мышь или клавиатуру. Однако кроме этих возможностей Windows предоставляет дополнительные: - Клавиши-ускорители, которые позволяют с помощью клавиатуры ускорить выбор элементов меню. - Каскадные меню, которые позволяют иметь несколько уровней накладываемых меню. - Плавающие меню - это обычные накладываемые меню, но они могут отображаться в любом месте экрана (обычно в текущей позиции мыши). - Собственные контрольные пометки, что позволяет вам определить собственные растровые карты, которые будут использоваться в качестве контрольных пометок вместо стандартных. Оставшаяся часть данного раздела будет посвящена обсуждению этих возможностей. 7.6.1 Клавиши - ускорители. Клавиши-ускорители - это клавиши, которые позволяют выбрать в меню команду, используя единственное нажатие на . Windows 3.0/pg/1#3 = 135 = клавишу. Например, в прикладной программе для выбора команды "Delete" можно использовать просто нажатие на клавишу DEL. Клавиши-ускорители являются частью файла описания ресурсов прикладной программы и помещаются в программу в ее исходный текст. Для использования клавиш-ускорителей в вашей прикладной программе вам нужно: 1. В файле описания ресурсов прикладной программы, указать клавиши ускорители в операторах MENUITEM. 2. В файле описания ресурсов прикладной программы вы должны указать таблицу ускорителей. Таблица ускорителей содержит клавиши-ускорители и соответствующие им идентификаторы элементов меню. Вы создаете ее с помощью оператора ACCELERATORS. 3. В тексте программы необходимо загрузить таблицу ускорителей с помощью функции LoadAccelerators. 4. Изменить цикл обработки сообщений прикладной программы так, чтобы в нем производилась обработка клавиш-ускорителей. Ниже это все описано в деталях. Добавление ускорителей к тексту элементов меню. В тексте элемента меню необходимо указать ускоритель данной команды для того, чтобы пользователь мог его использовать. Добавьте их названия к тексту элементов меню в операторах MENUITEM в файле описания ресурсов прикладной программы (.RC). Например, предположим, что в вашей прикладной программе определено следующее накладываемое меню: GroceryMenu MENU POPUP "&Meats" BEGIN MENUITEM "&Beef\tF9", IDM_BEEF MENUITEM "&Chicken\tShift+F9", IDM_CHICKEN MENUITEM "&Lamb\tCtrl+F9", IDM_LAMB MENUITEM "&Pork\tAlt+F9", IDM_PORK END END Накладываемое меню "Meats" содержит четыре элемента: Beefs, Chicken, Lamb и Pork. Текст каждого элемента содержит мнемонику, выделяемую амперсандом (&), и название клавиши-ускорителя, отделенное символом табуляции (\t). Если команда имеет клавишу-ускоритель, то она должна иметь такой . Windows 3.0/pg/1#3 = 136 = вид. В данном примере используются клавиши-ускорители: F9, SHIFT-F9, CTRL-F9 и ALT-F9. Создание таблицы ускорителей. Для использования клавиш-ускорителей добавьте в файл описания ресурсов прикладной программы таблицу ускорителей, используя оператор ACCELERATORS. Этот оператор определяет список клавиш-ускорителей и соответствующих им идентификаторов элементов меню. В операторе ACCELERATORS, как и в других операторах определения ресурсов, BEGIN определяет начало описания, а END его конец. Например: GroceryMenu ACCELERATORS BEGIN VK_F9, IDM_BEEF, VIRTKEY VK_F9, IDM_CHICKEN, VIRTKEY, SHIFT VK_F9, IDM_LAMB, VIRTKEY, CONTROL VK_F9, IDM_PORK, VIRTKEY, ALT END В данном примере определены четыре клавиши-ускорителя, по одному для каждой команды. Первая клавиша-ускоритель - это ппросто клавиша F9, остальные - это комбинации этой клавиши и клавиш SHIFT, ALT и CONTROL. Клавиши-ускорители определяются с помощью виртуальных кодов клавиш Windows, что указывается ключевым словом VIRTKEY. Виртуальные коды клавиш - это независимые от устройств значения, которые Windows преобразует для каждого компьютера. Это позволяет гарантировать, что на всех компьютерах будут использоваться те же клавиши, не зависимо от их действительных кодов. Однако вы можете использовать коды ASCII, что указывается ключевым словом ASCII. Оператор ACCELERATORS присваивает клавишу-ускоритель каждому идентификатору элемента меню. В нашем примере константы IDM_BEEF, IDM_CHICKEN, IDM_LAMB и IDM_PORK являются идентификаторами элементов меню Grocery. Когда пользователь нажимает клавишу-ускоритель, идентификатор элемента меню посылается функции окна. Загрузка таблицы ускорителей. Как и любой другой ресурс, таблицу ускорителей необходимо загрузить до того, как ее можно будет использовать. Для загрузки таблицы ускорителей используется функция LoadAccelerators. Эта функция получает дескриптор текущего экземпляра прикладной программы и имя таблицы ускорителей (определяется в файле описания ресурсов прикладной программы .RC). Она возвращает дескриптор таблицы ускорителей для данного меню. Обычно таблица ускорителей для меню загружается при создании окна, содержащего данное меню, т.е. при обработке . Windows 3.0/pg/1#3 = 137 = сообщения WM_CREATE в функции окна: HANDLE hInstance; /* дескриптор текущего экземпляра */ HANDLE hAccTable; /* дескриптор таблицы ускорителей */ . . . case WM_CREATE: (1) hAccTable = LoadAccelerators(hInstance,"GroceryMenu); break; В данном примере: 1) Этот оператор загружает таблицу ускорителей для меню GroceryMenu в память. Ее дескриптор помещается в переменную hAccTable. Переменная hInstance содержит дескриптор текущего экземпляра программы. Изменение цикла обработки сообщений для обработки кла- виш-ускорителей. Для обработки таблицы ускорителей вы должны добавить в цикл обработки сообщений функцию TranslateAccelerator. Когда цикл обработки сообщений получает сообщение о вводе с клавиатуры, содержащее клавишу ускорителей, TranslateAccelerator преобразует сообщение в сообщение WM_COMMAND, содержащее идентификатор элемента меню для данной клавиши-ускорителя, и посылает это сообщение WM_COMMAND функции окна. Цикл обработки сообщений должен проверять каждое сообщение на клавишу-ускоритель. Если она есть, то и цикл обработки сообщений должен преобразовать и передать сообщение с помощью функции TranslateAccelerator. Если сообщение не содержит клавишу ускоритель, то сообщение обрабатывается обычным способом. Замечание: Функция TranslateAccelerator также преобразует ускорители для команд системного меню. В этих случаях эти сообщения преобразуются к типу WM_SYSCOMMAND. После того, как вы добавите функцию TranslateAccelerator, цикл обработки сообщений будет выглядеть следующим образом: while(GetMessage(&msg, NULL, NULL, NULL)) { (1) if(!TranslateAccelerator(hWnd,hAccTable,&msg)) { (2) TranstateMessage(&msg); DispatchMessage(&msg); } } . Windows 3.0/pg/1#3 = 138 = 1) Этот оператор выполняет проверку на клавишу-ускоритель. Дескриптор окна, hWnd, определяет окно, сообщения которого транслируются. Этот дескриптор должен определять окно, содержащее меню с ускорителями. Дескриптор таблицы ускорителей, hAccTable, определяет таблицу ускорителей, используемую для трансляции. Если сообщение генерируется с использованием ускорителей, функция TranslateAccelerator преобразует нажатие клавиши в сообщение WM_COMMAND, содержащее соответствующий идентификатор элемента меню, и затем посылает это сообщение функции окна. 2) Если сообщение не содержит ускорители, то прикладная программа обрабатывает его обычным путем, используя функции TranslateMessage и DispatchMessage. 7.6.2 Использование каскадных меню. Windows позволяет иметь больше одного уровня накладываемых меню. Такие многоуровневые накладываемые меню называются каскадными. Такая структура меню позволяет минимизировать число команд в отдельном накладываемом меню без использования панелей диалога. Рисунок 7.1 Пример каскадного меню. В данном примере пользователь выбирает меню "Software" и затем выбирает в нем команду "Languages". В этой точке справа от курсора возникает меню "Languages". Затем пользователь перемещает курсор по этому меню и выбирает "C". Появляется накладываемое меню "C", в котором пользователь может выбрать или "C версии 5.1", или "Quick C". Каскадное меню - это просто вложенные накладываемые меню. Описание меню для примера на рисунке 7.1 выглядит следующим образом: MenuMenu MENU BEGIN . . . POPUP "&Software" BEGIN POPUP "&Word Processing" BEGIN MENUITEM "&Word 5.0", IDM_WORD MENUITEM "W&rite", IDM_WRITE END POPUP "&Spreadsheet" . Windows 3.0/pg/1#3 = 139 = BEGIN MENUITEM "&Microsoft Excel", IDM_EXCEL MENUITEM "&1+2=4", IDM_124 END POPUP "&Languages" BEGIN POPUP "&C" BEGIN MENUITEM "C &5.1", IDM_C51 MENUITEM "&Quick C", IDM_QUICKC END MENUITEM "Quick &Basic", IDM_QUICKBASIC MENUITEM "&Pascal", IDM_PASCAL END END . . . END Замечание: Каскадное накладываемое меню имеет свой собственный дескриптор. Поэтому для работы с его элементами необходимо вначале получить его дескриптор с помощью функции GetSubMenu. 7.6.3 Использование плавающих накладываемых меню. Обычно накладываемые меню подключены к другому меню, т.е. они появляются, когда пользователь выбирает команду из этого меню. Однако Windows предоставляет вам возможность использовать "плавающие" меню, которые появляются при нажатии определенной клавиши или при нажатии клавиши мыши в текущей позиции курсора. Для работы с плавающими меню используются функции CreatePopupMenu и TrackPopupMenu. Если вы хотите, чтобы плавающее меню появлялось при нажатии пользователем клавиши на клавиатуре или мыши, то создайте накладываемое меню внутри фрагмента, обрабатывающего данное событие. Ниже приведен пример, в котором отображение плавающего меню происходит, когда пользователь нажимает правую кнопку мыши: POINT CurrentPoint; . . . case WM_RBUTTONDOWN: { HWND hWnd; /* дескриптор текущего окна */ HMENU hFloatingPopup; /* дескриптор плавающего меню */ (1) CurrentPoint = MAKEPOINT (lParam); . Windows 3.0/pg/1#3 = 140 = /* точка, в которой пользователь нажал на правую кнопку мыши */ . . . (2) hFloatingPopup = GreatePopupMenu(); (3) AppendMenu(hFloatingPopup, MF_ENABLED, IDM_CALC, "Calculator"); AppendMenu(hFloatingPopup, MF_ENABLED, IDM_CARDFILE, "Cardfile"); AppendMenu(hFloatingPopup, MF_ENABLED, IDM_NOTEPAD, "Notepad"); (4) ClientToScreen(hWnd,(LPPOINT)&CurrentPoint); (5) TrackPopupMenu(hFloatingPoint, NULL, (6) CurrentPoint.x, CurrentPoint.y, NULL, hWnd, NULL); (7) DestroyMenu(hFloatingPopup); break; } В данном примере: 1) Параметр lParam сообщения WM_RBUTTONDOWN содержит текущую позицию курсора мыши. Функция MAKEPOINT преобразует длинное целое значение в структуру, содержащую координаты точки, в данном случае CurrentPoint. 2) Функция CreatePopupMenu создает пустое накладываемое меню и возвращает дескриптор этого меню. Этот дескриптор помещается в переменную hFloatingPopup. 3) После создания пустого меню прикладная программа добавляет к нему три элемента: "Calculator", "CardFile" и "Notepad". 4) Функция ClientToScreen преобразует текущие координаты . Windows 3.0/pg/1#3 = 141 = курсора таким образом, чтобы они вычислялись относительно левого верхнего угла экрана. (В начале координаты курсора вычисляются относительно левого верхнего угла области пользователя). 5) После того, как меню подготовлено, программа отображает его на экране с помощью функции TrackPopupMenu. 6) Поля x и y структуры CurrentPoint содержат координаты курсора относительно начала экрана. 7) После того, как пользователь произвел выбор в меню, прикладная программа разрушает меню и очищает память, которую оно использовало. Прикладная программа создает данное меню заново при каждом нажатии пользователем правой кнопки мыши. 7.6.4 Создание собственных контрольных отметок. Обычно, после того, как вы выбрали элемент меню, Windows отображает после текста этого элемента контрольную отметку. Элемент меню, который не помечается, вообще не имеет контрольной отметки. Однако вы можете указать растровую карту, которая будет использоваться вместо стандартной контрольной отметки Windows. Вы можете указать также растровую карту, которой будет помечаться невыбранный элемент. Собственные контрольные отметки могут помочь пользователю различать команды меню, выполняющие некоторые действия, и команды, которые могут быть отмечены, но не отмечены в данный момент. Некоторые прикладные программы Windows используют следующие соглашения: . Windows 3.0/pg/1#3 = 142 = Тип элемента меню Соглашение --------------------------------------------------------------- Элемент меню, выполняю- Не отображает контрольную отметку для щий некоторые действия таких элементов (например, отображает другое меню или панель диалога). Отмеченный в настоящий Отображает или стандартную отметку момент элемент меню Windows, или собственную контрольную отметку. Когда пользователь еще раз выбирает этот элемент, удаляет конт- рольную отметку. Элемент, который может Отображает собственную контрольную быть помечен, но в нас- отметку. Когда пользователь отмечает тоящий момент не отме- или удаляет контрольную отметку, вы- чен. водит или стандартную контрольную от- метку Windows, или другую собственную контрольную отметку. --------------------------------------------------------------- Чтобы создать собственную контрольную отметку: 1. С помощью SDKPaint создайте растровую карту, которая будет использоваться как контрольная отметка. Windows требует, чтобы такая контрольная отметка имела ту же размерность, что и стандартная. Хотя во время выполнения программа может растягивать и сжимать растровую карту до нужного размера, лучше всего начинать с точного размера. (Размер стандартной контрольной отметки зависит от типа дисплея и может быть получен с помощью функции GetMenuCheckMarkDimensions.) Кроме этого, вы можете создать растровую карту вручную, как это описано в главе 11 "Растровые карты". 2. В файле описания ресурсов прикладной программы объявите каждую растровую карту с помощью оператора BITMAP и файла, содержащего саму растровую карту. Например: BitmapChecked BITMAP CHECK.BMP BitmapNotChecked BITMAP NOCHECK.BMP 3. В программе загрузите растровые карты из ресурсов прикладной программы с помощью функции LoadBitmap. 4. Используя функцию GetMenuCheckMarkDimensions получите размер стандартной контрольной отметки для данного дисплея. . Windows 3.0/pg/1#3 = 143 = 5. При необходимости растяните или сожмите растровые карты до нужного размера с помощью функции StreckBlt. 6. Для указания для каждого элемента меню контрольной отметки используйте функцию SetMenuItemBitmap. 7. Перед завершением вашей программы необходимо разрушить растровые карты, чтобы освободить память, занимаемую ими. Следующий пример демонстрирует, как указать собственную контрольную отметку для элемента меню: SetMenuItemBitmaps (hMenu, /* дескриптор меню */ 0, /* позиция эл-та меню */ MF_BYPOSITION, hbmCheckOff, /* для не отмеченного */ hbmCheckOn); /* для отмеченного */ 7.6.5 Использование меню, рисуемых владельцем. Ваша прикладная программа может осуществлять полное управление видом меню, используя элементы меню, рисуемые самой программой. Рисуемый владельцем элемент меню - это меню, в котором прикладная программа полностью отвечает за рисование нормальных, выбранного (подсвеченного), отмеченных и неотмеченных элементов. Например, предположим, что ваша прикладная программа имеет меню, которое позволяет пользователю выбрать шрифт. Ваша прикладная программа может рисовать каждый элемент меню, используя шрифт, который представляет данный элемент. Вы не можете описать меню, рисуемое владельцем в файле описания ресурсов прикладной программы (.RC). Вместо этого вы должны создать новое меню или модифицировать старое, используя флаг MF_OWNERDRAW. Для указания элементов меню, рисуемых владельцем, вы можете использовать следующие функции: - AppendMenu - InsertMenu - ModifyMenu При вызове любой из этих функций вы передаете ей 32-битовое значение в качестве параметра lpNewItem. Это 32-битовое значение может представлять любую инофрмацию, необходимую для рисования элемента вашей программой, и доступно вашей программе при рисовании элемента. Например, оно может содержать указатель на структуру данных. Структура данных в свою очередь может содержать строку и дескриптор логического . Windows 3.0/pg/1#3 = 144 = шрифта, который должен использоваться для вывода данного элемента. Перед первым отображением такого элемента Windows посылает окну, которому принадлежит данное меню, сообщение WM_MEASHUREITEM. Это сообщение в параметре lParam содержит указатель на структуру MEASUREITEMSTRUCT, которая описывает элемент меню и содержит необязательное 32-битовое значение для данного элемента. При получении данного сообщения прикладная программа должна перед возвратом из обработки сообщения заполнить поля itemWidth и itemHeight структуры данных MEASUREITEMSTRUCT. Windows использует данную информацию для определения прямоугольника, в котором программа будет рисовать данный элемент, а также для определения взаимодействия пользователя с элементом. При возникновении необходимости вывести данный элемент (например, когда он отображается впервые, или когда пользователь выбирает данный элемент), Windows посылает окну, которому принадлежит данное меню, сообщение WM_DRAWITEM. Параметр lParam этого сообщения содержит указатель на структуру данных DRAWITEMSTRUCT. Как и структура MEASHUREITEMSTRUCT, DRAWITEMSTRUCT содержит информацию, идентифицирующую элемент меню и его необязательное 32-битовое значение. Кроме этого, структура DRAWITEMSTRUCT содержит флаг, который определяет состояние элемента (такое как серый или отмеченный), а также ограничивающий его прямоугольник и контекст устройства, с помощью которого ваша прикладная программа будет рисовать данный элемент. При получении сообщения WM_DRAWITEM перед возвратом управления ваша программа должна выполнить следующие действия: 1. Определить тип рисования, которое необходимо выполнить. Для этого протестируйте поле itemAction структуры DRAWITEMSTRUCT. 2. Нарисуйте соответствующий элемент меню, используя прямоугольник и контекст устройства из структуры DRAWITEMSTRUCT. Ваша программа может рисовать только внутри этого прямоугольника. Чтобы не ухудшать характеристики, Windows не обращает изображение, выходящее за пределы прямоугольника. 3. Восстановите все объекты GDI, выбранные для контекста устройства элемента меню. Например, если выбран элемент меню, Windows присваивает полю itemActions структуры данных DRAWITEMSTRUCT значение ODA_SELECT и устанавливает бит ODS_SELECTED поля itemState. Таким образом ваша программа должна перерисовать элемент, чтобы показать, что он выбран. . Windows 3.0/pg/1#3 = 145 = 7.7 Пример прикладной программы: EditMenu. Программа EditMenu иллюстрирует использование следующих элементов: - Двух часто используемых меню: Edit и File. - Клавиш-ускорителей. Замечание: Ускорители, показанные в данном примере, специально зарезервированы для использования в меню Edit. Стандартные ускорители описаны в System Application Architecture, Common User Access: Advanced Interface Design Guide. Для создания программы EditMenu скопируйте и переименуйте файлы с программой Generic. Затем выполните следующее: - Добавьте меню Edit и File к файлу описания ресурсов прикладной программы. - Добавьте описания во включаемый файл. - Добавьте таблицу ускорителей к файлу описания ресурсов прикладной программы. - Добавьте новые переменные. - Загрузите таблицу ускорителей. - Модифицируйте цикл обработки сообщений. - Модифицируйте фрагмент WM_COMMAND. - Откомпилируйте и скомпонуйте программу. EditMenu не показывает, как работать с системным буфером. Эта задача описана в главе 13, "Системный буфер". Вместо того, чтобы вводить тексты, приведенные в следующих разделах, возможно вам будет удобнее просто переписать исходные тексты из SDK. 7.7.1 Добавление новых меню к файлу описания ресуросов. Вам необходимо добавить к оператору MENU следующие меню: File и Edit. Оператор MENU будет теперь выглядеть следующим образом: EditMenuMenu MENU BEGIN POPUP "&File" BEGIN . Windows 3.0/pg/1#3 = 146 = MENUITEM "&New", IDM_NEW MENUITEM "&Open...", IDM_OPEN MENUITEM "&Save", IDM_SAVE MENUITEM "Save &As...", IDM_SAVEAS MENUITEM "&Print", IDM_PRINT MENUITEM SEPARATOR MENUITEM "E&xit", IDM_EXIT MENUITEM SEPARATOR MENUITEM "&About EditMenu...", IDM_ABOUT END POPUP "&Edit" BEGIN MENUITEM "&Undo\tAlt+BkSp", IDM_UNDO ,GRAYED MENUITEM SEPARATOR MENUITEM "Cu&t\tShift+Del", IDM_CUT MENUITEM "&Copy\tCtrl+Ins", IDM_COPY MENUITEM "&Paste\tShift+Ins", IDM_PASTE ,GRAYED MENUITEM "&Delete\tDel", IDM_CLEAR ,GRAYED END END Меню "File" содержит семь команд и два разделителя. Каждая команда имеет мнемонику, которая выделяется символом амперсанд ("&"). Меню "Edit" содержит пять команд и разделитель. Каждая команда имеет и мнемонику, и ускоритель, отделенный от имени команды символом табуляции (\t). Если команда имеет ускоритель, она должна отображаться таким образом. В меню Edit используются ускорители: ALT+BACKSPACE, DELETE, CONTROL+INSERT, SHIFT+INSERT, SHIFT+DELETE. Разделитель помещает между командами "Undu" и "Cut" горизонтальную черту. Рекомендуется использовать разделитель между командами, которые не имеют ничего общего. Например, "Undo" влияет на всю прикладную программу, в то время как все остальные команды влияют на системный буфер. Примечание: Назначение и содержимое меню "Edit" и "File" описаны в "System Application Architecture, Common User Access: Advanced Interface Design Guide. 7.7.2 Добавление определений во включаемый файл. Вы должны объявить во включаемом файле идентификаторы всех элементов меню. Эти константы будут использоваться в файле с исходным текстом программы на языке С и в файле описания ресурсов прикладной программы. Идентификатором элемента меню может быть любое целое значение. Единственное ограничение заключается в том, что это число должно быть уникальным в данном меню: не должно быть двух команд меню с одинаковыми идентификаторами. . Windows 3.0/pg/1#3 = 147 = Добавьте следующее во включаемый файл: #define IDM_ABOUT 100 /* идентификаторы элементов меню File */ #define IDM_NEW 101 #define IDM_OPEN 102 #define IDM_SAVE 103 #define IDM_SAVEAS 104 #define IDM_PRINT 105 #define IDM_EXIT 106 /* идентификаторы элементов меню Edit */ #define IDM_UNDO 200 #define IDM_CUT 201 #define IDM_COPY 202 #define IDM_PASTE 203 #define IDM_CLEAR 204 7.7.3 Добавление к файлу описания ресурсов таблицы ускорителей. Добавьте к файлу описания ресурсов прикладной программы следующий оператор ACCELERATORS: EditMenuAcc ACCELERATORS BEGIN VK_BACK, IDM_UNDO, VIRTKEY, ALT VK_DELETE, IDM_CUT, VIRTKEY, SHIFT VK_INSERT, IDM_COPY, VIRTKEY, CONTROL VK_INSERT, IDM_PASTE, VIRTKEY, SHIFT VK_DELETE, IDM_CLEAR, VIRTKEY END Этот оператор определяет клавиши-ускорители для каждой команды. Четыре ускорителя используют комбинации клавиши с клавишами ALT, SHIFT, CONTROL. Оператор ACCELERATORS позволяет связать идентификатор элемента меню с ускорителем. Константы IDM_UNDU, IDM_CUT, IDM_COPY, IDM_PASTE и IDM_CLEAR являются идентификаторами элементов меню "Edit". Когда пользователь нажимает клавишу ускоритель, функции окна посылается идентификатор элемента меню, связанного с этим ускорителем. 7.7.4 Добавление новых переменных. Добавьте в начало С-файла следующий оператор: HANDLE hAccTable; /* дескриптор таблицы ускорителей */ . Windows 3.0/pg/1#3 = 148 = Переменная hAccTable содержит дескриптор таблицы ускорителей. В нее заносится значение, возвращаемое функцией LoadAccelerators, и оно используется в функции TranslateAccelerators для идентификации используемой таблицы ускорителей. 7.7.5 Загрузка таблицы ускорителей. Перед использованием таблицы ускорителей вы должны ее загрузить из ресурсов прикладной программы. Добавьте следующий оператор в функцию InitInstance: hAccTable = LoadAccelerators(hInst, "EditMeruAcc"); Этот оператор загружает в память таблицу ускорителей и присваивает ее дескриптор переменной hAccTAble. Переменная hInstance идентифицирует файл ресурсов прикладной программы, а "EditMenuAcc" это имя таблицы ускорителей. После загрузки таблица может использоваться в функции TranslateAccelerators. 7.7.6 Модификация цикла обработки сообщений. Для использования таблицы ускорителей вы должны добавить функцию TranslateAccelerators в цикл обработки сообщений. После ее добавления цикл должен выглядеть следующим образом: while (GetMessage(&msg, NULL, NULL, NULL)) { if (!TranslateAccelerator(hwnd, hAccTable, &msg)) { TranslateMessage(&msg); DispatchMessage(&msg); } } 7.7.7 Модификация фрагмента WM_COMMAND. В прикладной программе необходимо обрабатывать команды меню. В данной прикладной программе при выборе команд не выполняется никакой работы, вместо этого выводится панель сообщения "Command not implemented" (команда не реализована). Замените фрагмент WM_COMMAND следующими операторами: switch (message) { case WM_COMMAND: switch (wParam) { case IDM_ABOUT: lpProcAbout = MakeProcInstance(About, hInst); DialogBox(hInst, "AboutBox", hWnd, lpProcAbout); FreeProcInstance(lpProcAbout); break; /* Команды меню File */ . Windows 3.0/pg/1#3 = 149 = case IDM_NEW: case IDM_OPEN: case IDM_SAVE: case IDM_SAVEAS: case IDM_PRINT: MessageBox ( GetFocus(), "Command not implemented", "EditMenu Sample Application", MB_ICONASTERISK | MB_OK); break; case IDM_EXIT: DestroyWindow(hWnd); break; /* Команды меню Edit */ case IDM_UNDO: case IDM_CUT: case IDM_COPY: case IDM_PASTE: case IDM_CLEAR: MessageBox ( GetFocus(), "Command not implemented", "EditMenu Sample Application", MB_ICONASTERISK | MB_OK); break; } break; 7.7.8 Компиляция и компоновка. Для компиляции и компоновки программы EditMenu не требуется никаких изменений в MAKE-файле. Запустите Windows, затем EditMenu, и не открывая накладываемые меню нажмите клавиши-ускорители. Вы увидите панель с сообщением "Command not Implemented". 7.8 Заключение. В данной главе описано, как использовать меню в прикладной программе. Меню предоставляет список команд, которые может выбрать пользователь. Windows обрабатывает большинство функций меню автоматически, например, когда пользователь выбирает в строке меню команду, Windows автоматически отображает накладываемое меню, связанное с данной командой. Когда пользователь выбирает команду в меню, Windows посылает прикладной программе сообщение WM_COMMAND, содержащее идентификатор выбранного элемента меню. Затем прикладная программа может выполнить действия в соответствии с выбранной . Windows 3.0/pg/1#3 = 150 = командой. Кроме этого, Windows предоставляет более сложные возможности, вроде каскадных меню, собственных контрольных отметок и меню, рисуемых пользователем. Дополнительную информацию относительно курсоров вы найдете в: Раздел Руководство --------------------------------------------------------------- Ввод с использова- Руководство программиста, Глава 4, "Ввод с нием мыши и клавиа- использованием мыши и клавиатуры" туры Растровые карты Руководство программиста, Глава 11, "Раст- ровые карты" "Tools", глава 4, "Создание изображений". Функции меню Справочное руководство, том 1, глава 1, "Функции интерфейса управления окнами", глава 4, "Список функций". Операторы описания Справочное руководство, том 2, глава 8, ресурсов "Операторы описания ресурсов". Демонстрационная Диск "SDK Sample Source Code Disk" программа MENU.EXE, иллюстрирует исполь- зование каскадных меню, меню, рисуемых владельцем и конт- рольных отметок, ри- суемых владельцем. . |
::Главная ->Литература ->Руководство по программированию в Windows | |
(c) 2000 by AlmigoR
|