Классы приложения



Классы приложения

Раскройте элемент дерева классов под именем CMyView. Класс CMyView происходит от MFC-класса cview и дает вам возможность управлять обликами (views) документов в рамках модели программирования архитектуры «документ — представление», считающейся стандартной технологией разработки MFC-приложений. О ней достаточно много сказано. (См., например, Круглински Д. Основы Visual C++, М: «Русская редакция», 1997; Черносвитов A. Visual C++ и MFC, СПб.: «Питер», 2000.) Здесь мы также будем рассматривать особенности технологии, по позднее. А пока попробуем изменить коды нашего приложения так, чтобы оно умело отображать данные документа. Выполнив двойной щелчок над элементом дерева OnDraw (CDC *pDC), вы увидите новое окно-страницу, управляемое вкладкой MyView.cpp. Так именуется файл реализации (implementation file) класса CMyView. Курсор должен находиться на теле метода перерисовки:

void CMyView::OnDraw(CDC* pDC)

{

CMyDoc* pDoc = GetDocument();

ASSERT_VALID(pDoc);

// TODO: add draw code for native data here

}

Здесь вместо подсказки // TODO: мы должны вставить код, отображающий данные документа. Функция OnDraw(CDC *pDC) входит в состав класса CMyView, являсь методом этого класса, и вызывается каркасом приложения в тех случаях, когда необходимо перерисовать окно, обслуживаемое классом CMyView.

Примечание 1
Примечание 1

Каркасом приложения (Application Framework) называется совокупность классов и других структур библиотеки MFC, которые присутствуют в вашем приложении неявно. Дело в том, что классы вашего приложения произведены (с помощью механизма наследования ООП) от классов MFC. Данные и методы этих классов компилятор включил в исполняемый модуль, и они работают на вас. (Данные используются, методы вызываются.) Но вы можете и не знать об этом.

Представлением документа называется клиентская область одного из окон-рамок, обслуживаемых классом CChildFrame и живущих внутри главного окна приложения. В MDI приложении их может быть много. Это те окна, которые можно видеть по очереди, все сразу, каскадом или рядом, не перекрывая друг друга (Cascade или Tile).

Перед тем как начать отображение данных, надо эти данные создать или добыть из класса, обслуживающего документ. В соответствии с концепцией архитектуры «документ — представление» все стратегические данные приложения должны храниться в классе документа, то есть в классе CMyDoc. Метод GetDocument (он вызывается в заготовке OnDraw) класса CMyView позволяет добыть указатель на объект класса CMyDoc, который управляет активным в данный момент документом.

Примечание 2
Примечание 2

Макроподстановка ASSERT_VALID в отладочной (Debug) версии проекта проверяет на осмысленность полученный указатель и дает сообщение об ошибке, в случае когда указатель равен нулю или в действительности не является адресом объекта класса, производного от класса CObject. Если вы просмотрите иерархию классов MFC, то увидите, что CObject является отцом-прародителем всех классов, потомки которых использованы в нашем приложении.



Итак, имея адрес документа, мы можем начинать отображение его данных в окне представления. В системе, поддерживающей графический интерфейс пользователя, все данные не просто выводятся на экран, они скорее «рисуются» в контексте устройства, связанном с окном. Подсистема Windows GDI (Graphics Device Interface) дает вам набор средств для рисования, среди которых одним из главных является контекст устройства, управляемый классом CDC (Device Context). Указатель на используемый системой в данный момент объект класса CDC мы получили в качестве параметра функции OnDraw.

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



Концепция решений и проектов



Концепция решений и проектов

Сеанс работы в Studio.Net начинается с открытия существующего или создания нового решения (solution). В дальнейшем вместо термина решение я иногда буду использовать термин рабочее пространство, так как буквальный перевод — решение — не всегда точен. Файлы с расширением sin используются IDE (Integrated Development Environment) для хранения настроек и начальных установок конкретных решений. Концепция решений помогает объединить проекты и другие элементы в одном рабочем пространстве. Множество файлов разного типа, в рамках одного решения составляют приложение (application) Visual Studio.Net 7.0. Рабочее пространство может содержать несколько проектов, быть пустым или содержать файлы, которые имеют смысл и вне контекста решений. В любом случае, вы должны начинать работу в студии с открытия существующего или создания нового рабочего пространства.

Проект как часть решения состоит из отдельных компонентов, например файлов, описывающих форму окна или шаблон диалога (re-файл), файлов с исходными кодами программных модулей (.срр, .cs) и/или файлов, представляющих собой описание запроса к базе данных (database script), HTML-документов и, т. д. Настройки проектов хранятся в специальных файлах проектов. Они могут иметь разные расширения, так как в одном пространстве можно объединять проекты совершенно разных типов. Например, проект MFC-приложения хранит свои установки в файле с расширением vcproj, а файл проекта, реализованного на языке

С#, имеет расширение csproj. Такой файл является читаемым, его можно открыть вне рамок Studio.Net (например, с помощью Notepad) и увидеть описание установок проекта на еще одном из «секретных» языков. Например, проект типа MFC Application с именем MyProj содержит файл MyProj.vcproj, начальный фрагмент которого мы приведем здесь:

<?xml version="1.0"?>

<VisualStudioProject ProjectType="Visual C++" Version="7.00" Name="MyProj" Keyword="mfc">

<Build>

<Settings>

<Platform Name="Win32"/>

<Configuration

Name="Debug|Win32"

InterraediateDirectory="Debug"

OutputDirectory="Debug"

ConfigurationType="l"

UseOfMFC="2"

CharacterSet="2">

<Tool Name="VCBscMakeTool"/>

<Tool

Name="VCCLCorapilerTool"

Optimization="0"

Нет необходимости углубляться в анализ языка описания проекта. Поверхностного взгляда достаточно, чтобы понять, что мы имеем дело с последовательностью <предложений>, описывающих тип проекта, настройки и перечень инструментов Studio.Net для его обработки. То же самое можно сказать про sin-файл. Он читаем, и если открыть его в текстовом режиме, то можно увидеть предложения некоего служебного языка, описывающие состав и настройки рабочего пространства.



Контейнер точек



Контейнер точек

Зададимся целью нарисовать в логической системе координат плоский многоугольник (Polygon), координаты точек которого будем хранить в динамической структуре данных. Специалисты советуют в таких случаях пользоваться одним из множества шаблонов, реализующих поведение фундаментальных структур данных и присутствующих в рамках STL (Standard Template Library). Библиотека шаблонов STL доступна на любой платформе, так как является стандартом. Она станет доступной и нам, если мы подключим необходимый файл заголовков. Для хранения точек многоугольника мы выберем шаблон стандартного контейнера, который называется vector. Это динамическая структура данных, которая ведет себя как «умный» массив элементов произвольного типа. Любой контейнер удобно представлять себе в виде резиновой сумки с одинаковыми объектами любой природы, которая почти всегда полна и в которую всегда можно положить еще разумное количество объектов того же типа. Это возможно, потому что она растягивается, то есть способна динамически (на этапе выполнения) изменять свои размеры как в сторону увеличения, так и в сторону уменьшения.

Подробнее о контейнерах будет сказано позже, а сейчас надо решить, что должно в нем храниться. Так как многоугольник определяется координатами точек, то контейнер целесообразно «скроить» (по шаблону vector) так, чтобы в нем можно было хранить объекты класса CPoint. Этот класс является вспомогательным в MFC (не происходит от CObject). Найдите этот класс в иерархии классов библиотеки MFC. Для этого:

Дайте команду Help > Index. В появившемся окне Index (Look for:) задайте CPoint. В окне Index Results for CPoints — 3 topics found выберите строку CPoint Class (MFC). Внизу появившегося окна CPoint Class найдите ссылку Hierarchy Chart и щелкните ее мышью.

Класс CPoint находится в правой части карты под заголовком Simple Value Types. После этого отыщите классы: CObject, CDocument, cview, cwnd, которые так или иначе присутствуют в каркасе нашего приложения. Закройте окна Index, Index Results и Hierarchy Chart.

Теперь вы знаете, что CPoint содержит внутри себя две целые координаты (х, у) произвольной точки и множество полезных методов для управления точкой. Итак, мы решили хранить точки многоугольника (объекты класса CPoint) в контейнере, скроенном по шаблону vector<CPoint>. Параметр шаблона (в угловых скобках) указывает тип объектов, которые будут храниться в контейнере. Воспользуемся контекстным меню, возникающим при правом щелчке мыши (right-click) на имени класса CMyDoc в окне Class View. В этом меню:

Выберите команду Add > Add Variable. Появится диалог типа wizard (мастер). В окне Variable Type диалога задайте тип нового объекта: vector<CPoint> В окне Variable Name — имя: m_Points Нажмите кнопку Finish.



Начало работы с Visual Studio Net



Начало работы с Visual Studio.Net
Концепция решений и проектов
Создание нового проекта
Классы приложения
Контейнер точек


Общий вид Studio Net



Рисунок 1.1. Общий вид Studio.Net




Окно диалога New Project



Рисунок 1.2. Окно диалога New Project





Окно мастера Add Variable



Рисунок 1.5. Окно мастера Add Variable


Просмотрите описание класса CMyDoc, дважды щелкнув на имени класса в окне Class View. В конце файла вы должны увидеть строку

vector<CPoint> m Points;

Теперь просмотрите тело конструктора класса. Для этого раскройте элемент дерева CMyDoc и дважды щелкните на имени конструктора CMyDoc (void). Вы должны увидеть такой заголовок и тело конструктора:

CMyDoc::CMyDoc()

: m Points (0)

{

}

Обратите внимание на инициализатор m_Points (0), который был автоматически вставлен мастером Add Variable. Инициализатор вызывает один из конструкторов шаблона классов vector и сообщает ему, что перед тем, как создать объект класса CMyDoc, надо создать объект m_Points типа vector и задать ему нулевой размер. Нам не нужен этот инициализатор, так как мы собираемся записать в контейнер m_Points координаты тестового многоугольника. Тело конструктора документа пока пусто. Наполним его кодами, вычисляющими точки многоугольника, так чтобы он имел вид пятиконечной звезды. Звезда удобна тем, что позволяет продемонстрировать способы закраски самопересекающихся многоугольников. Измените коды конструктора:

CMyDoc: : CMyDoc ()

//====== Вспомогательные переменные

double pi = 4 . * atari (1.),

al = pi / 10. , // Углы

a2 = 3. * al,

// ====== 2 характерные точки

x1 = cos (al) ,

yl = sin (al) ,

x2 = cos (a2) ,

y2 = sin(a2) ,

x[5], у [5];

//===== Вещественные (World) координаты углов звезды

//===== Считаем, что начало координат находится

//===== в геометрическом центре звезды

х [ 0 ] = 0 . ; у [ 0 ] = 1 . ; // Макушка звезды

х[1] = -х2; у[1] = -у2; // Нижний левый угол

х[2] = xl; У [2] = yl; // Верхний правый угол

х[3] = -xl; y[3] = yl; // Верхний левый угол

х[4] = х2; У [4] = -у2; // Нижний правый угол

//===== Логические координаты углов звезды

//===== запоминаем в контейнере

for (int i=0; i<5; i++)

//===== Точка в логической системе координат

// Увеличиваем в 100 раз, переводим в целые

// и сдвигаем

CPoint pt(200 + int(100. * x[i]), 150 - int(100. * y[i]));

//===== Записываем в конец контейнера

m_Points.push_back(pt);

}

}



Окно приложения My



Рисунок 1.7. Окно приложения My


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

pDC->SetPolyFillMode(WINDING);

и нажмите Ctrl+F5. Характер заливки изменился. Объяснение этого факта (и многих других) надо научиться искать в документации, сопровождающей Studio.Net. Дайте команду Help > Index, в окно Look for введите SetPolyFillMode и нажмите Enter. Появится окно Index Results for..., в котором следует сделать выбор между API-функцией SetPolyFillMode и одноименным методом класса CDC. Так как мы работаем с библиотекой MFC, то выбор почти всегда падает на методы классов, а не на одноименные функции API. Текст справки появится в окне Web Browser (многовато окон), и если вы действительно хотите понять алгоритм закрашивания кистью внутренних частей полигона, то вам придется немного потрудиться, даже имея хороший английский. К таким ситуациям тоже надо выработать правильное отношение. Программист должен быть кропотлив и терпелив.

Подведем итог:

мы слегка затронули концепцию решений (solutions); научились создавать начальную заготовку MFC-приложения; немного привыкли к скользким как мыло окнам Visual Studio.Net; вспомнили (или узнали) об архитектуре документ — представление; ввели в документ фундаментальную структуру данных — контейнер точек, скроенный по шаблону (template) vector; узнали кое-что о выводе в контекст устройства и координатных пространствах Windows; получили первый опыт сражений с ошибками.

Окно Task List со списком ошибок



Рисунок 1.6. Окно Task List со списком ошибок


Воспользуйтесь контекстным меню окна Task List и выберите команду Sort by > Category. В результате ее выполнения на первое место попадает сообщение:

error C2238: unexpected token (s) preceding ";" ...

Это примерно значит: «Неизвестная лексема предшествует точке с запятой». Такое сообщение, на мой взгляд, значительно более точно определяет причину. Вот она: компилятор не знает, что такое vectoro. Причина: мы забыли подключить файл с заголовками библиотеки STL Это легко исправить, вставив в нужное место строки:

//====== Подключает часть определений STL

#include <vector>

//====== Задает область видимости имен STL

using namespace std;

Теперь надо решить, куда вставить эти строки. Их можно вставить в начало файла MyDoc.h, по это будет расточительно. Дело в том, что для подключения файлов заголовков различных библиотек существует специальное место — файл Stdafx.h. Этот файл (совместно с файлом StdAfx.cpp) используется для построения файла скомпилированных заголовков My.pch (precompiled header) и файла скомпилированных типов StdAfx.obj.

Они расположены в папке Debug вашего проекта и служат для ускорения повторных компиляций файлов проекта после внесенных вами изменений, если они незначительны. Таким образом, подключаемые файлы библиотек, а они внушительны по размерам и компилируются только при необходимости. Сейчас настала такая необходимость, так как исправления затрагивают файл Stdafx.h. Вставьте две вышеуказанные строки в конец файла Stdafx.h и запустите музыку (Ctrl+F5).

Отметьте, что все файлы проекта компилируются заново, так как все файлы типа .срр имеют в качестве первой строку:

#include "stdafx.h"

Мы изменили stdafx.h, и компилятор заново проходит по всем зависящим (dependent) от него файлам. После построения и запуска изображение звезды должно появиться в клиентской области дочернего окна-рамки, то есть в окне, управляемом классом CMyView.



Представление классов в окне Class View



Рисунок 1.4. Представление классов в окне Class View




Реакция на ошибки



Реакция на ошибки

Полезно откомпилировать и запустить приложение в незавершенном состоянии, так как это позволит увидеть, как проявляются ошибки и недомолвки. Процесс компиляции и сборки совместно называется построением (Build) проекта. Самым быстрым способом построить и запустить проект является ввод Ctrl+F5 и согласие с необходимостью повторения всего процесса.

Ход процесса компиляции и сборки проекта освещается и комментируется Studio.Net в окне Output. Сообщения об ошибках, выявленных на стадии построения, также выводятся в этом окне, но по завершении процесса появится диалоговое окно с сообщением о наличии ошибок. Теперь вы должны выбрать: продолжать ли компоновку или нет. Разумным выбором будет No. Теперь сообщения об ошибках в более подробном виде появляются в окне Task List, которое имеет универсальный характер, но в нашем частном случае используется для отображения ошибок компиляции. Окно имеет вид списка, который помогает идентифицировать и локализовать ошибки.

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

error C2143: syntax error : missing ';' before '<' C:\My Projects\My\MyDoc.h(38)

Здесь мне хочется поговорить о том, как выудить из этих сообщений более или менее точное указание на истинное местоположение и причину ошибки или ошибок. Прежде всего надо психологически подготовиться к тому, что ошибки всегда и неизбежно будут преследовать вас. Если код содержит более 20 операторов, то он не может быть создан и введен без ошибок. Если же код содержит более 5000 операторов, то он всегда будет содержать их. Это почти аксиома (с долей иронии). Разработчик программного обеспечения вынужден большую часть жизни проводить за компьютером, бесконечно повторяя цепочку одних и тех же действий, которые составляют суть процесса отладки приложения. Чем спокойнее вы относитесь к своим ошибкам, тем быстрее вы с ними расправитесь.

Итак, первое сообщение говорит нам о том, что отсутствует точка с запятой перед знаком ' <' в строке 38 файла с описанием класса CMyDoc. Сделайте двойной щелчок па этом сообщении и курсор в окне MyDoc.h перейдет на строку

vector<CPoint> m_Points;

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



Рисование в контексте устройства



Рисование в контексте устройства
Реакция на ошибки

Итак, вы успешно преодолели все трудности установки Microsoft Visual Studio. Net 7.0 (если они были, а они в изобилии присутствовали в бета-версии продукта, с которой я имел дело в момент написания книги) и готовы покорить определенные высоты с помощью вашей неудержимой фантазии программиста и возможностей студии. Инструменты Studio.Net, несомненно, помогут воплотить ваши идеи в реальные проекты, которые теперь принято называть решениями (solutions) — термин, обозначающий новую концепцию логического хранилища проектов.

Если вы имеете опыт работы в среде Microsoft Visual Studio 6.0, то, открыв Studio.Net, вы сразу отметите значительные изменения в интерфейсе. Общий облик вызывает ассоциации с пультом управления летательного аппарата или какого-то другого сложного технического объекта. Задача одна — в небольшом пространстве разместить множество инструментов контроля и управления за состоянием объекта. Но в отличие от осязаемого пульта управления самолетом ваш иллюзорный пульт на экране может динамично изменяться, отчасти благодаря сравнительно новым элементам управления — tabbed windows — окнам с вкладками. Открыв Studio.Net, вы увидите такое окно на самом видном месте. Это окно самое большое по площади. В начальный момент оно имеет только одну страницу (page), открываемую с помощью вкладки (tab) VS Home Page. Далее в этой группе будут появляться другие вкладки, позволяющие открывать другие страницы составного окна. В случае если вы «потеряете» начальное окно, то его можно вернуть на свое место, дав команду View > Web Browser. Вот другой способ сделать это:

открыть контекстное меню, щелкнув правой клавишей мыши над пустым местом планки обычного меню или над пустым местом стандартной инструментальной панели;
выбрать панель под именем Web;
нажать кнопку Ноmе на новой панели.

При поиске кнопок используйте всплывающие подсказки (tooltips).

Обозревая окна Studio.Net, отметьте усовершенствования косметического характера: пункты меню теперь имеют значки, изменились цвета элементов интерфейса в разных состояниях, нарушив тем самым рекомендации Microsoft по созданию UI-элементов (User Interface).

Примечание 1
Примечание 1

Visual C++, Microsoft Developer Network (MSDN), fj, Visual Basic и другие компоненты составляют интегрированную среду разработки приложений — Visual Studio Integrated Development Environment (IDE). Совместное использование одной и той же среды имеет очевидные преимущества, так как в процессе разработки приложений на разных языках можно пользоваться одними и теми же или сходными инструментами Studio.Net: Web Browser, Command Window, Tabbed Documents, редакторами кодов, HTML-страниц,XML-схем и редакторами ресурсов.



Рисование в контексте устройства


Сейчас важно понять следующее. Пример моделирует ситуацию, когда мы имеем реальные World-координаты (термин, принятый в GDI) какого-то объекта, например разрез корабля, и хотим начертить его детали, которые всегда можно аппроксимировать многоугольниками в некоторой логической системе координат (например, листе ватмана размером 1000x1000 мм). При этом мы преобразуем реальные вещественные координаты корабля в логические, то есть целого типа, так как мы не можем чертить с погрешностью менее 1 мм. В соответствии с концепцией рисования в контексте устройства именно эти (логические) координаты мы и должны использовать в функциях рисования. При последующем выводе рисунка на экран или принтер операционная система автоматически преобразовывает каждую его точку в аппаратные (device) координаты, зависящие от типа и возможностей устройства вывода. Таким образом, мы имеем дело с тремя системами координат и двумя их преобразованиями.

Примечание 1
Примечание 1

Если вы посмотрите справку по теме Coordinate Spaces and Transformations (Пространства и преобразования координат), то вы увидите, что в GDI рассматриваются четыре координатных пространства: World, Page, Device и Physical device, однако часто можно использовать только два (Page и Device). При этом пространства World и Page считаются одним логическим координатным пространством, а пространства Device в Physical device — физическим. Преобразование из пространства Device в Physical device ограничивается только подстройкой начала координат при отображении рисунка на каком-то конкретном устройстве вывода.

Вызовите в окно редактора функцию On Draw. Для этого снова щелкните вкладку MyView.cpp группы окон, вероятно, слева и введите изменения в соответствии со следующим фрагментом:

void CMyView::OnDraw(CDC* pDC)

{

CMyDoc* pDoc = GetDocument();

ASSERT_VALID(pDoc) ;

//======= Узнаем размер контейнера точек

UINT nPoints = pDoc->m_Points.size() ;

//======= Уходим, если он пуст

if (InPoints)

return;

//=== Сохраняем текущее состояние контекста

//=== (инструменты GDI)

pDC->SaveDC () ;

//=== Создаем перо Windows для прорисовки контура

CPen pen (PS_SOLID,2,RGB(0,96,0));

//=== Выбираем его в контекст устройства

pDC->SelectObject (Spen);

//===Создаем кисть Windows для закраски внутренности

CBrush brush (RGB(240,255,250));

pDC->SelectObject (&brush);

//===== Изображаем полигон

pDC->Polygon (spDoc->m_Poihts[0], nPoints);

//Восстанавливаем контекст (предыдущие инструменты GDI)

pDC->RestoreDC(-l);

}



Окно мастера MFC Application Wizard



Рисунок 1.3. Окно мастера MFC Application Wizard


Нажмите ОК и проанализируйте предлагаемые по умолчанию настройки проекта, которые определены в появившемся окне диалога, обслуживаемом инструментом Studio.Net под именем MFC Application Wizard.
Нажмите кнопку Finish.

Мы отложим разговор о различных типах шаблонов (стартовых заготовках) MFC-приложений в предположении, что читатель имеет представление о них по опыту работы в Visual Studio б.0. Если это не так, то все равно не прерывайте процесс чтения и исследования Studio.Net. Примиритесь, временно, с дискомфортом недопонимания. Итак, Application Wizard потрудился и создал стартовую заготовку нового Windows-приложения, которое поддерживает многодокументный интерфейс (MDI). Вы можете немедленно его запустить, дав команду Debug > Start Without Debugging и согласившись с сообщением о том, что конфигурация проекта устарела (подразумевается, ехе-файл либо отсутствует, либо старше, чем какой-либо из исходных файлов проекта). После этого вы имеете возможность наблюдать за процессом компиляции и компоновки в окне Output, которое, скорее всего, появится внизу главного окна Studio.Net. Далее вы увидите окно нового приложения My, поддерживающего стандарт MDI. Опробуйте команды File > New и все команды меню Window этого приложения, следя за заголовками новых дочерних окоп. Закройте стартовое приложение и сосредоточьте внимание на окне Studio.Net с заголовком Solution Explorer, которое, скорее всего, расположено справа от окна VS Home Page.

Примечание 1
Примечание 1

Неуверенность относительно местоположения окон объясняется тем, что окна Studio.Net проявляют удивительную подвижность. При желании вы можете разместить их в разных группах tabbed-окон или сделать их свободными (floating), причаливаемыми (docable) или скрыть (hide). Поэкспериментируйте с командами (Docable, Hide, Floating, Auto Hide контекстного меню, которое появляется при щелчке правой клавишей мыши над заголовками окон.

Опробуйте также команды меню Window. Например, выберите произвольное окно и дайте команду Window > Docable. Отыщите выбранное окно, затем повторите ту же команду и вновь отыщите окно. При работе с кодами в окне редактора наиболее удобным режимом для вспомогательных окон представляется Auto Hide. Studio.Net позволяет очень гибко управлять местоположением своих окон. Отметьте, что вышеупомянутое контекстное меню динамически изменяется в зависимости от текущего состава группы tabbed-окон. В нем появляются другие команды, которые расширяют ваши возможности по управлению интерфейсом Studio.Net. Окно Solution Explorer дает возможность управлять проектами и файлами проектов. Оно является аналогом окна File View в Visual Studio 6 и теперь по умолчанию входит в группу окон, составляющих блок очень полезных страниц (pages), которыми вы будете часто пользоваться. Сейчас активизируйте страницу Class View из этого блока, для того чтобы увидеть состав классов библиотеки MFC, использованный в новом приложении. При поиске вкладки Class View в блоке используйте всплывающие подсказки. Раскройте дерево классов. Отметьте, что теперь кроме шести классов(CAboutDlg, CChildFrame, CMainFrame, CMyApp, CMyDoc, CMyView) в дерево входят и другие элементы, также являющиеся логическими компонентами MFC-приложения. Это глобальные функции и переменные (Global Functions and Variables), макроподстановки И константы (Macros and Constants).



Создание нового проекта



Создание нового проекта

При создании нового проекта Studio.Net автоматически создает рабочее пространство и помещает в него этот проект. Вот перечень шагов для создания нового проекта и нового рабочего пространства (solution), его содержащего.

В меню File > New выберите команду Project. В появившемся окне диалога New Project, в окне Project Type раскройте узел дерева под именем Visual C++ Projects и выберите узел Win32 Projects. В окне Templates выберите тип проекта MFC Application. В окне Name задайте имя проекта My. В окне Location задайте или оставьте без изменения местоположение новой папки с файлами рабочего пространства.



Изображение объекта в режиме MM_SOTROPIC



Рисунок 2.1. Изображение объекта в режиме MM_SOTROPIC





Масштабирование изображения



Масштабирование изображения

Зададимся целью внести изменения в приложение My, которое мы создали в предыдущей главе, таким образом, чтобы изображение геометрической фигуры всегда было в центре окна и следило за изменением размеров окна приложения, меняясь пропорционально. Напомним, что фигурой является многоугольник с произвольным количеством вершин, и он выводится в контекст устройства Windows с помощью объекта класса CDC. Для того чтобы упростить процесс слежения за размерами окна представления, введем в число членов класса CMyView новую переменную, которая будет хранить текущие размеры окна.

Вызовите контекстное меню над именем класса CMyView в окне Class View и выберите команду Add > Add Variable. Заполните поля окна мастера так, чтобы он создал private-переменную m_s zView типа csize. Убедитесь в том, что мастер Add Variable выполнил свою работу. В окне Class View вы должны увидеть ее в числе данных класса. Повторите эти же действия и введите в состав CMyView private-переменную UINT m_nLogZoom, в которой мы будем хранить коэффициент увеличения, используемый при переходе из пространства World в пространство Page. В пространстве Page мы увеличим изображение в 100 раз. Измените уже созданный В конструкторе CMyView инициализатор m_nLogZoom (0) на m_nLogZoom (100).

Примечание 1
Примечание 1

Если вы помните (об этом я уже говорил в связи с конструктором документа), наш многоугольник «в миру» задан вещественными координатами и имеет размах в 2 единицы, так как мы вписали его в окружность единичного радиуса, то есть диаметром в 2 единицы. Там же мы преобразовали все координаты в логическую систему, увеличив размеры многоугольника в 100 раз и перевернув его (так как ось Y экрана направлена вниз). Кроме того, мы сдвинули изображение вправо и вниз, чтобы попасть в центр «листа ватмана». Логические координаты (уже целого типа) мы занесли в массив m_Points, который использован в функции OnDraw класса представления при изображении многоугольника. Теперь надо изменить ситуацию. Обычно документ хранит истинные («мировые») координаты объектов, а класс представления преобразовывает их в логические и изображает в физическом устройстве с помощью рассмотренных преобразований. Так делают, потому что пользователя не интересуют логические (Page) координаты. Он должен видеть и иметь возможность редактировать реальные (World) координаты объекта.

Чтобы реализовать указанный подход, надо заменить в классе документа массив целых координат на массив вещественных, а в классе CMyView создать еще один массив, но уже целых координат. Используя уже знакомую технику, введите в состав класса CMyView private-переменную

vector<CPoint> m_Points;

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

vector<CPoint> m_Points;

на

VECPTS m_Points;

Этой заменой мы оставили для контейнера то же имя, но изменили тип его элементов. Тип данных VECPTS — вектор точек с вещественными (World) координатами — пока не определен, но мы собираемся его декларировать и определить для того, чтобы было удобно хранить реальные координаты объекта. Для начала создадим свой собственный класс CDPoint, инкапсулирующий функциональность точки с вещественными координатами. Вставьте в начало файла MyDoc.h после директивы препроцессора #pragma once, но до объявления класса CMyDoc декларацию нового класса1:

//====== Новый класс "Вещественная точка"

class CDPoint

{

public:

double x;

double у; // Вещественные координаты

//====== Конструктор по умолчанию

CDPoint()

{

х=0.;

у=0.;

}

//====== Конструктор копирования

CDPoint(const CDPointS pt)

{

x = pt.x;

y = pt.y;

}

//====== Конструктор с параметрами

CDPoint(double xx, double yy)

{

x = x x;

у = yy;

}

//====== Операция умножения (увеличение в п раз)

CDPoint operator*(UINT n)

{

return CDPoint (x*n, y*n);

}

//====== Операция присваивания

CDPointS operator=(const CDPointS pt)

{

x = pt.x;

у = pt.y;

return *this; // Возвращаем свой объект

}

//====== Операция сложения двух точек

CDPoint operator*(CDPointS pt)

{

return CDPoint(x + pt.x, у + pt.y);

}

//====== Операция вычитания двух точек

CDPoint operator-(CDPointS pt)

{

return CDPoint(x - pt.x, у - pt.y);

}

// Метод приведения к типу CPoint (целая точка)

CPoint Tolnt()

{

return CPoint(int(x),int(у)); }

//====== Операция сложения с записью результата

void operator+=(CDPointS pt) { x += pt.x; у += pt.y; }

//====== Операция вычитания с записью результата

void operator-=(CDPoint& pt) { x — pt.x; у -= pt.y; }

// Операция вычисления нормы вектора, заданного точкой

double operator!() { return fabs(x) + fabs(y); } };

При использовании контейнера объектов класса полезно декларировать новый тип данных:

typedef vector<CDPoint, allocator<CDPoint> > VECPTS;

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

Кроме трех конструкторов для удобства пользования в классе CDPoint заданы правила сложения и вычитания точек. Метод Tolnt позволяет создать стандартную точку Windows (CPoint) из нашей вещественной точки. Операция умножения точки на целое число без знака (CDPoint operator* (UINT n);) позволяет увеличивать координаты объекта, что мы используем при переходе из World- в Page-пространство. Операция вычисления нормы вектора, начало которого находится в точке (0,0), а конец в данной точке, полезна при оценке степени близости двух точек. В одном из следующих уроков она нам пригодится. Тело конструктора документа можно упростить, так как теперь он помнит реальные координаты объекта и необходимость преобразовывать координаты в пространство Page исчезла. Это преобразование мы сделаем в классе CMyView:

CMyDoc::CMyDoc()

{

//====== Вспомогательные переменные

double pi = 4. * atan(l.), //====== Углы

al = pi / 10., a2 = 3. * a1,

//===== 2 характерные точки

xl = cos(al), yl = sin(al), x2 = cos(a2), y2 = sin(a2);

//==== Вещественные (World) координаты углов звезды

m_Points.push_back(CDPoint( 0., 1.) ) ;

m_Points.push_back(CDPoint(-x2, -y2));

m_Points.push_back(CDPoint( xl, yl)) ;

m_Points.push_back(CDPoint(-xl, yl));

m_Points.push_back(CDPoint( x2, -y2));

}

Масштабирование изображения можно упростить, если следить за текущими размерами клиентской области окна. При изменении пользователем размеров окна-рамки в момент отпускания кнопки мыши система посылает приложению сообщение WM_SIZE, на которое должен среагировать класс CMyView и запомнить в переменной m_szView новые размеры.

Сейчас мы введем в состав CMyView новую функцию отклика, которая будет вызываться в ответ на приход сообщения WM_SIZE. Она должна иметь имя OnSize (так устроена MFC) и иметь особый спецификатор afx_msg, который невидим компилятором (препроцессор заменит его пустым местом), но нужен инструментам Studio.Net. Спецификатор несет информацию о том, что функция OnSize особая — она является эффективно реализованным обработчиком сообщения (message handler). В Studio.Net процесс создания функций-обработчиков и виртуальных функций сильно изменен. Теперь это делается не с помощью ее инструмента ClassWizard, следы которого однако присутствуют в студии, а в окне Properties.

Выделите имя класса CMyView в окне Class View и перейдите на страницу Properties, выбрав соответствующую вкладку.
Обратите внимание на панель инструментов окна Properties. Она динамически изменяется в зависимости от выбора (selection) в других окнах. Сейчас на ней должна быть кнопка с подсказкой Messages. Нажмите эту кнопку. В появившемся списке сообщений найдите сообщение WM_S IZE. В правой ячейке (типа Combo box) таблицы выберите <Add> OnSize.
Вновь перейдите в окно Class View, найдите новую функцию-обработчик OnSize в составе класса CMyView и сделайте на ней двойной щелчок.
Фокус ввода переходит в окно редактора текста для файла MyView.cpp. Введите изменения так, чтобы функция приобрела вид:

void CMyView::OnSize(UINT nType, int ex, int cy)

{

//========== Вызов родительской версии

CView::OnSize(nType, ex, cy) ;

if (cx==0 || cy==0)

return;

//========= Запоминаем размеры окна представления

m_szView = CSize (ex, су); } ;

Проверка if (cx==0...) необходима потому, что каркас приложения вызывает OnSize несколько раз и иногда с нулевыми размерами. Обратите внимание на то, что мастер вставок добавил также и прототип (объявление) функции обработки в интерфейс класса CMyView (см. файл MyView.h):

public:

afx_msg void OnSize(UINT nType, int ex, int cy) ;

Теперь покажем, как с помощью Studio.Net следует определять в классе собственные версии виртуальных функций. Мы собираемся однократно (при открытии окна) преобразовать «мировые» координаты в логические и запомнить их. Это удобно сделать внутри виртуальной функции OnlnitialUpdate, которая унаследована от класса cview. Она вызывается каркасом приложения в тот момент, когда окно еще не появилось на экране, но уже существует его Windows-описатель (HWND) и объект класса CMyView прикреплен (attached) к окну. Напомним также, что документ имеет и поддерживает динамический список всех своих представлений.

В окне Class View поставьте курсор на имя класса CMyView и щелкните правой клавишей мыши. Перейдите в окно Properties, щелкнув вкладку, с помощью подсказок отыщите на панели инструментов именно этого окна кнопку Overrides и нажмите ее. Появится длинный список виртуальных функций родительских классов, которые можно переопределить в классе ему view. Найдите в нем функцию OnlnitialUpdate и выберите в правой половине таблицы действие <Add>.

Результат ищите в конце файла MyView.cpp. Внесите изменения в тело функции:

void CMyView::OnlnitialUpdate()

{

CView::OnlnitialUpdate();

// Создаем ссылку на контейнер World-координат точек

VECPTSS pts = GetDocument()->m_Points;

UINT size = pts.size ();

//====== Задаем размер контейнера логических точек

m_Points. resize (size);

for (UINT i=0; i < size;

m_Points[i] = (pts[i] * m_nLogZoom) .Tolnt () ;

}

Здесь мы добываем из документа World-координаты объекта, умножаем их на коэффициент m_nLogZoom и преобразуем к целому типу. Обратите внимание на использование операций и методов вновь созданного класса CDPoint и на то, что переменная pts создана и инициализирована как ссылка на контейнер точек документа. Теперь осталось изменить коды для перерисовки представления так, чтобы воспользоваться техникой масштабирования, которую мы обсудили в начале главы:

void CMyView: :OnDraw(CDC* pDC)

{

CMyDoc* pDoc = GetDocument () ;

ASSERT_VALID(pDoc) ;

//====== Узнаем размер контейнера точек

UINT nPoints = m_Points.size () ;

if (! nPoints) // Уходим, если он пуст return;

// Сохраняем текущее состояние контекста pDC->SaveDC() ;

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

CPen pen (PS_SOLID,2,RGB(0, 96,0) ) ;

//====== Выбираем его в контекст устройства

pDC->SelectObject (spen);

// Создаем кисть Windows для заливки внутренности

CBrush brush (RGB (240, 255, 250) );

pDC->SelectObject (Sbrush);

//====== Задаем режим преобразования координат

pDC->SetMapMode(MM_ISOTROPIC) ;

//====== Сдвиг в логической системе

pDC->SetWindowOrg(0,0) ;

//====== Сдвиг в физической системе

pDC->SetViewportOrg (m_szView.cx/2, m_szView. су/2) ;

//====== Знаменатель коэффициента растяжения

pDC->SetWindowExt (3*m_nLogZoom, 3*m_nLogZoora) ;

//====== Числитель коэффициента растяжения

pDC->SetViewportExt (m_szView.cx, -m_szView.cy) ;

//====== Изображаем полигон

pDC->Polygon (Sra_Points [0] , nPoints) ;

// Восстанавливаем контекст (предыдущие инструменты GDI)

pDC->RestoreDC (-1) ;

}

Коэф(рициент 3 в параметрах SetWindowExt моделирует ситуацию, когда лист ватмана в 3 раза превышает размер детали, на нем изображенной. Знак минус в параметре SetViewportExt позволяет компенсировать изменение направления оси Y при переходе из Page space в Device space. При рисовании мы используем логические (Page) координаты, которые хранятся в классе CMyView.



Режимы отображения координат



Режимы отображения координат

Масштабирование изображения

В Windows любые операции вывода являются графическими. Подсистема GDI делает вывод анпаратно независимым. Это означает, что информация, выводимая на любое физическое устройство, будет выглядеть почти одинаково. На экране монитора, на плоттере и на принтере будут отражены все детали текста или изображения. Кроме того, GDI поддерживает логические устройства вывода, такие как память или диск. Для осуществления вывода в среде Windows необходимо сначала создать так называемый контекст устройства (device context). Контекст устройства — это объект, определенный в GDI, который содержит содержат подробную информацию об устройстве, куда предполагается направить графический вывод. Например: основной цвет и цвет фона, используемая палитра, шрифт по умолчанию и т. д.

MFC имеет специальный набор классов, упрощающий процедуру общения с контекстом устройства. Класс CDC содержит большую часть функций, которые могут понадобиться для управления выводом. Классы, производные от CDC, обеспечивают специальные возможности, например класс cciientoc обеспечивает доступ к клиентской области окна, где в основном разворачиваются события, управляемые программистом. Класс CPaintDC позволяет управлять процессом перерисовки окон, обеспечивая вызовы функций BeginPaint и EndPaint в ответ на сообщение WM_PAINT. Создание рисунка в окне производится с помощью функций API, инкапсулированных в одноименных методах класса CDC

Если мы зададимся целью масштабировать изображение, то есть дать пилы-юним-;-лю возможность изменять его размеры, то нам следует научиться управлять режимами и параметрами отображения логической системы координат в физическую. Вы уже знаете, что Win32 API использует четыре координатных пространства (spaces): World space, Page space, Device space, и Physical Device space. Однако большая часть документации и книг но MFC оперируют только двумя терминами: логическая и физическая системы координат. Мы будем использовать термин, логическая система координат, имея в виду Page space.

Заметьте, что и World space и Page space измеряют плоскую область, размах которой по обеим координатам равняется 232 логических единиц, то есть более 4 миллиардов единиц. Page space работает совместно с Device space, чтобы обеспечить приложение единицами, не зависящими от типа физического устройства, такими как миллиметры и дюймы-(inches). Конечным координатным пространством, Physical Device space обычно является клиентская область окна приложения, или весь экран монитора, или страница бумаги принтера (плоттера). Размеры области физического устройства изменяются в зависимости от марки, технологии и т. д. Чтобы верно передать детали изображения, созданного в логической системе, в физическое устройство, система преобразовывает их путем отображения (mapping) прямоугольной области из одного координатного пространства в другое. При копировании каждой точки прямоугольной области из одного пространства в другое Windows применяет алгоритм трансформации, который в конечном счете изменяет размеры, ориентацию и форму всего объекта.

Так как рисование производится в логической структуре, называемой контекстом устройства, то рисунок не зависит от конкретного устройства, в которое будет осуществлен вывод изображения. Функциональность контекста устройства поддерживается классом CDC или одним из его потомков. Для выбора режима отображения используются методы этого класса:

SetMapMode — задание режима отображения координат;
SetWindowOrg — задание выделенной точки (начала отображения) в логической системе координат;
Setviewportorg — задание выделенной точки (начала отображения) в физической системе координат; SetwindowExt — характеристика протяженности окна вдоль двух логических координат;
SetviewportExt — характеристика протяженности окна вдоль двух физических координат.

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

#define MM_TEXT 1 // 1 лог. ед. - 1 пиксел

#define MM_LOMETRIC 2 // 1 лог. ед. - 0,1 мм

#define MM_HIMETRIC 3 // 1 лог. ед. - 0,01 мм

#define MM_LOENGLISH 4 // 1 лог. ед. - 0,01 дюйма

#define MM_HIENGLISH 5 // 1 лог. ед. - 0,001 дюйма

#define MMJTWIPS 6 //1 лог. ед. - 1/1440 дюйма

//========== Преобразования по формуле ==========//

#define MM_ISOTROPIC 7 // Растяжение одинаково

#define MM_ANISOTROPIC 8 // Растяжение различно

По умолчанию действует режим ММ_ТЕХТ, в котором ось Y имеет направление сверху вниз. Последующие пять режимов предполагают, что ось Y направлена снизу-вверх. В двух последних режимах преобразование координат из логической системы в физическую выполняется в соответствии с формулами, которые приведены ниже. При этом используются следующие обозначения:

Viewport — область вывода, задаваемая физическими координатами; Window — окно, задаваемое логическими координатами.

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

Dx=Vox+(Vex/Wex)*(Lx-Wox)

где: Dx — аппаратная (device) или физическая Х-координата точки,

Lx - логическая (logical) Х-координата точки,

Vex - протяженность области вывода, задаваемая SetVievvportExt,

Wex — протяженность окна, задаваемая SetWindowExt,

Vox — X начала координат области вывода (SetViewportOrg),

Wox — X начала координат логического окна (SetWindowOrg).

Аналогичная формула справедлива для Y-координаты точки. Опробуем формулу на произвольном наборе данных. (Такого типа вопросы вы можете встретить на сертификационном экзамене Microsoft.) Предположим, что в режиме MM_ANISOTROPIC заданы такие параметры отображения:

//====== Выделенная точка в логическом окне

pDC->SetWindowOrg (300, 0) ;

//====== Выделенная точка в физическом окне

pDC->SetViewportOrg (200, 200);

//====== Протяженность логического окна pDC->SetWindowExt (100, 100);

//====== Протяженность физического окна

pDC->SetViewportExt (50, -200);

Какие координаты в окне на экране будут иметь точки, заданные оператором: CPoint pl(0, 0), р2(100, 100)? Ответ: они преобразуются в аппаратные (физические) координаты: (50, 200) и (100, 0). Проверим первую координату подстановкой в формулу:

200 + 50/100(0-300)=50

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

Vex / Wex— коэффициент растяжения (сжатия) вдоль оси X,

Vey / Wey— коэффициент растяжения (сжатия) вдоль оси Y.

Эти формулы работают независимо только в режиме MM_ANISOTROPIC. Несколько иначе они работают в режиме MM_ISOTROPIC и вовсе не работают в остальных шести режимах.

В режиме MM_ISOTROPIC система обеспечивает одинаковое расширение (сжатие) по обеим осям, поэтому результат вычислений по приведенным формулам зависит от соотношения величин коэффициентов растяжения (сжатия). Теперь видно, что режим MM_ANISOTROPIC обеспечивает наибольшую свободу и гибкость в преобразовании координат. Числитель и знаменатель в формулах для коэффициентов растяжения (сжатия) задаются по отдельности с помощью методов класса CDC. Метод SetviewportExt задает числитель обоих отношений, следовательно, определяет свойства физического устройства, а метод SetwindowExt задает знаменатель, то есть задает свойства логической системы координат. Имена функций вводят нас в заблуждение, так как на самом деле эти функции ничего не задают и не определяют. Работая в паре, они дают способ задания двух вещественных коэффициентов растяжения (сжатия) путем задания четырех целых чисел. Параметры функций должны быть целыми или объектами класса csize, которые тоже создаются из двух целых. Значительно проще было бы задать два вещественных числа и использовать их в качестве коэффициентов. Объяснение наблюдаемой реальности, видимо, кроется в истории разработки функций API (сложности с floating point-арифметикой).



Изображение объекта в режиме MM_ANISOTROPIC



Рисунок 2.2. Изображение объекта в режиме MM_ANISOTROPIC


Запустите приложение (Ctrl+F5) и попробуйте изменять размеры окна, по отдельности сжимая его до предела вдоль обеих координат. Наблюдайте за тем, как размеры звезды следят за размерами окна. Отметьте, что фигура остается в центре окна, а ее пропорции остаются без изменений. Затем задайте режим отображения MM_ANISITROPIC и вновь запустите приложение. Проведите те же манипуляции с окном и отметьте, что пропорции объекта теперь искажаются. Тем не менее оба режима используются на практике. Например, нет смысла изменять пропорции какой-либо конструкции или ее детали, но вполне допустимо изменять пропорции графика функции.

Анализируя трансформации звезды, имейте в виду, что ее размах 2 абстрактные, не определенные нами единицы в world-пространстве и 200 логических единиц в Page-пространстве. Само Page-пространство мы задали с помощью выражения 3*m_nLogZoom, то есть 300 на 300 единиц. При переходе к Device-координатам система растягивает или сжимает его так, чтобы оно занимало все окно. При этом она сдвигает изображение и центр звезды (0,0) попадает в центр окна (m_szview. сх/2, m_szView. су/2). Полезно поэкспериментировать и с другими режимами преобразования координат. При этом следует помнить, что все они, кроме режима ММ_ТЕХТ, предполагают ось Y направленной вверх.



Анализ стартовой заготовки



Анализ стартовой заготовки

Первые две строки являются директивами препроцессора, которые сообщают ему, что до того, как начать процесс компиляции модуля, следует вставить в указанное место файлы заголовков (stdafx.h и API.h). Первый файл мы обсуждали в уроке 1. Он содержит директивы подключения библиотечных файлов-заголовков. Директива

//======== Исключает редко используемые элементы

//======== Windows-заголовков

#define WIN32_LEAN_AND_MEAN

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

#pragma once

#include"resource.h"

Директива fpragma once сообщает компилятору, что данный файл (API.h) должен быть использован при построении кода приложения только один раз, несмотря на возможные повторы (вида #include "API.h"). Вторая директива подключает файл с идентификаторами ресурсов. Сами ресурсы вы видите в окне Resource View. Все ресурсы приложения и их отдельные элементы должны быть идентифицированы, то есть пронумерованы. Новичкам рекомендуется открыть файл resource.h с помощью окна Solution Explorer и просмотреть его содержимое. В этом файле символическим именам (идентификаторам) IDS_APP_TITLE, IDR_MAINFRAME и т. д. соответствуют числовые константы, которые препроцессор вставит вместо идентификаторов еще до процесса компиляции. В конце файла содержатся пометки Studio.Net, определяющие дальнейший способ нумерации ресурсов различного типа. Рассматриваемый файл не рекомендуется редактировать вручную, так как в случае ошибок вы получите труднолокализуемые отказы. Studio.Net сама следит за состоянием resource.h, вставляя и удаляя макроподстановки #def ine по мере того, как вы редактируете ресурсы с помощью специальных редакторов.

Возвращаясь к коду заготовки, отметим, что далее следует объявление глобальных переменных

HINSTANCE hlnst; // Текущий экземпляр

TCHAR szTitle[MAX_LOADSTRING];

// Текст заголовка окна

TCHAR szWindowClass[MAX_LOADSTRING];

// Текст регистрации

Рассматривайте описатель hlnst как адрес исполняемого модуля в пространстве процесса, соответствующего приложению. Если вы не знакомы с понятиями поток и процесс, то обратитесь к последнему уроку этой книги, где приведены некоторые сведения об архитектуре Windows. Текст регистрации szWindowClass будет загружен из ресурсов при выполнении winMain (см. вызов LoadString).

Примечание 1
Примечание 1

Этот текст представляет собой строку символов «API», которая хранится в ресурсах. Ее можно увидеть, раскрыв дерево ресурсов в окне Resource View, узел String Table и дважды щелкнув на элементе String Table (group). С помощью этой строки ваше приложение регистрируется в операционной системе.

При вызове функции winMain система передает ей параметры:

hinstance — описатель экземпляра приложения. Это адрес приложения, загруженного в память. В Windows NT/2000 этот адрес для всех приложений имеет одно и то же значение 0x00400000 (4 Мбайт);
hPrevlnstance — описатель предыдущего экземпляра приложения. Этот параметр устарел и теперь не используется в приложениях Win32;
lpCmdLine — указатель на командную строку. Мы не будем использовать этот параметр;
nCmdShow — состояние окна при начальной демонстрации.

Ранее в Win 16 второй параметр использовался в целях экономии ресурсов, но в Win32 — это NULL, так как каждый экземпляр приложения теперь выполняется в своем собственном виртуальном адресном пространстве процесса емкостью 4 Гбайт. Все экземпляры процесса загружаются начиная с одного и того же адреса в этом пространстве (см. последний урок). Теперь рассмотрим алгоритм функции WinMain:

она загружает из ресурсов две рассмотренные выше строки текста;
создает, заполняет и регистрирует структуру типа WNDCLASS;
создает главное окно приложения;
загружает клавиатурные ускорители;
запускает цикл ожидания и обработки сообщений.

Основные атрибуты главного окна приложения задаются в структуре типа WNDCLASSEX. Понятие класс окна появилось до того, как появились классы C++. Поэтому структура WNDCLASSEX не имеет ничего общего с классами MFC. Она является типом структур языка С. Дело в том, что каждое Windows-приложение должно зарегистрировать атрибуты своего окна, а система использует их при создании окна. Структура WNDCLASSEX своими полями определяет некий шаблон или модель для создания окон данного класса. В полях структуры вы указываете необходимые атрибуты окна: адрес исполняемого модуля приложения, .адрес оконной процедуры, имя ресурса меню, набор битов для описания стиля окна, местонахождение изображения курсора, значка и т. д. Эту «неинтересную» работу выполняет большая часть кодов функции MyRegisterClass. Используя классы MFC, вы избавляете себя от подобной рутинной работы.

При регистрации класса окон (точнее, переменной типа WNDCLASSEX) операционная система связывает оконную процедуру (WndProc) с приложением. В winMain вы должны зарегистрировать главное окно приложения, остальные же окна, если они нужны, могут быть зарегистрированы и в других местах программы. Адрес заполненной структуры передается в функцию RegisterClassEx, которая говорит Windows, что от нее ожидается, когда окна подобного класса появляются на экране. Теперь система знает, какой вид курсора использовать при попадании указателя мыши в пределы окна, кому посылать сообщения о событиях, происходящих в области окна, какие значки (большой 32 х 32 и маленький 16 х 16) будут связаны с приложением и т. д. Функция RegisterClassEx возвращает число типа АТОМ (16-ти битное целое без знака), которое соответствует строке регистрации в таблице атомов, поддерживаемой системой.

После регистрации класса главного окна идет вызов функции Initlnstance, которая пытается создать окно (CreateWindow) зарегистрированного класса. Если система нашла класс окна в трех поддерживаемых ею списках зарегистрированных классов окон, то функция CreateWindow возвращает описатель окна (HWND). Мы запоминаем его для того, чтобы использовать в качестве параметра при вызове других функций. Если нет, то функция вернет нулевое значение и приложение молча завершает свою работу. Попробуйте при вызове CreateWindow вставить пустую строку, вместо szWindowClass. Запустив приложение, вы поймете, что в ветвь if (ihwnd) неплохо бы вставить вызов:

MessageBox(0,"Не нашла класс окна","Ошибка",МВ_ОК);

При успешном создании окна происходит вызов функций

//====== Показать окно

ShowWindow(hWnd, nCmdShow);

//====== Сделать это, минуя очередь

UpdateWindow(hWnd);

Далее в коде winMain загружается таблица акселераторов (соответствий клавиатурных комбинаций командам меню), которая используется в цикле ожидания и обработки сообщений. Функция GetMessage выбирает сообщение из очереди сообщений потока и помещает его в структуру типа MSG, служащей для хранения информации о сообщении Windows. Функция TranslateAccelerator пытается транслировать (преобразовать) сообщения WM_KEYDOWN (нажата клавиша) или WM_SYSKEYDOWN (нажата F10 или ALT+другая клавиша) в сообщения WM_COMMAND или WM_SYSCOMMAND, но только в том случае, если в таблице акселераторов присутствует код клавиши.

Преобразованное сообщение направляется непосредственно в оконную процедуру. Характерно то, что TranslateAccelerator ждет завершения обработки сообщения. Функция TranslateMessage транслирует сообщения, поступившие в виде виртуальных кодов клавиш, в символьные сообщения и снова отправляет их в очередь сообщений потока для того, чтобы отреагировать на него на следующей итерации цикла. Например, сообщение WM_KEYDOWN (virtual key message) она может преобразовать в WM_CHAR (character message) или в WM_DEADCHAR (см. MSDN). Функция DispatchMessage отправляет сообщение оконной процедуре.

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

Стартовая заготовка иллюстрирует стандартную последовательность действий при создании Windows-приложения на базе API-функций. Обратите внимание на то, что функция WndProc нигде явно не вызывается, хотя именно она выполняет всю полезную работу. Для проверки усвоения прочитанного ответьте на вопрос: «Когда и кем она вызывается?»



Геометрическое перо



Геометрическое перо

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

//====== Узоры штрихов (hatch) кисти, на основе

//====== которых будет основано перо

static UINT uHatch[] =

{

HS_BDIAGONAL, HS_CROSS, HS_DIAGCROSS,

HS_FDIAGONAL, HS_HORIZONTAL, HS_VERTICAL

};

//===== Строки текста для пояснений

static string brush[] =

{

"HS_BDIAGONAL", "HS_CROSS", "HS_DIAGCROSS",

"HS_FDIAGONAL", "HS_HORIZONTAL", "HS_VERTICAL"

};

Вставьте следующий код в ветвь WM_PAINT перед вызовом EndPaint. Этот фрагмент по структуре такой же, как и предыдущий, но здесь мы создаем перо, используя штриховую (hatched) кисть. Запустите и проверьте, что получилось. Попробуйте объяснить, почему линия со штрихом типа HS_HORIZONTAL невидима. Замените строку

LineTo(hdc, iXMax, iYPos);

на

LineTo(hdc, iXMax, iYPos + 3);

и запустите вновь. Теперь линия должна быть видна. Найдите объяснение и попробуйте обойтись без последнего изменения кода, то есть уберите +3:

//======== геометричесое перо

Ib.lbStyle = BS_HATCHED; // Узорная кисть

sText = "Стили на основе кисти (Geometric pen)";

GetTextExtentPoint(hdc,sText.c_str(), sText.size(),SszText);

//======= Сдвиг позиции вывода

iYPos += 2 * szText.cy;

iXPos = iXCenter - szText.cx/2;

TextOut(hdc, iXPos, iYPos, sText.c_str() , sText.size ());

nLines = sizeof(brush)/sizeof(brush[0]);

for (i = 0; i < nLines; i ++ )

{

//======= Выбираем узор штриха кисти

Ib.lbHatch = uHatch[i];

//== Создаем на его основе перо тощиной 5 пиксел

HPEN hp = ExtCreatePen(PS_GEOMETRIC, 5, Sib,0,0);

HPEN hOld = (HPEN)SelectObject(hdc, hp) ;

iYPos += szText.cy; MoveToEx(hdc, 10, iYPos, NULL);

LineTo(hdc, iXMax,iYPos);

SelectObject(hdc, hold);

DeleteObject(hp);

TextOut(hdc, 10, iYPos, brush[i].c_str(), brush[i].size());

}

Геометрическое перо может быть создано на основе заданного пользователем или программистом узора (PS_USERSTYLE). Узор задается с помощью массива переменных типа DWORD. Элементы массива определяют циклический алгоритм закраски линии по схеме «играем — не играем». Например, массив DWORD а [ ] = { 2,3,4 } определяет линию, у которой 2 пиксела закрашены, 3 — не закрашены, 4 — закрашены. Затем цикл (2,3,4) повторяется. Для моделирования этой возможности введите в WndProc еще один двухмерный массив (так как линия будет не одна):

//======= три узора геометрического пера

static DWORD dwUser[3][4] =

{

{ 8, 3, 3, 3),

{ 3, 3, 3, 3),

(15, 4, 3, 4}

};

Затем добавьте вслед за фрагментом, моделирующим штриховую линию, следующий код:

//======= Геометричесое перо (user-defined)

Ib.lbStyle - BS_SOLID;

sText = "Стили на основе узора (Geometric pen)";

GetTextExtentPoint(hdc,sText.c_str(), sText.size(),SszText);

iYPos += 2 * szText.cy;

iXPos = iXCenter - szText.cx/2;

TextOutfhdc, iXPos, iYPos,

sText.c_str(), sText .size () } ,

nLines = sizeof(dwUser)/sizeof(dwUser[0]) ;

//====== Перебираем узоры пользователя

//====== (строки массива dwUser)

for (i = 0; i < nLines; i++)

{

DWORD dw = PS_GEOMETRIC | PS_USERSTYLE | PS_ENDCAP_FLAT;

HPEN hp = ExtCreatePen(dw, i+2, Sib, 4, dwUser[i]);

HPEN hOld = (HPEN)SelectObject(hdc, hp) ;

iYPos += szText.cy;

MoveToEx(hdc, 10, iYPos, NULL);

LineTo(hdc, iXMax,iYPos);

SelectObject(hdc, hold);

DeleteObject(hp);

TextOut(hdc, 10, iYPos, user[i].c_str(), user[i].size());

}

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



Косметическое перо



Косметическое перо

Сначала исследуем косметическое перо. Некоторые его стили, задаваемые символьными константами, занесем в массив. Введем внутрь тела оконной процедуры (после объявления CustColors) объявления новых локальных переменных:

//====== х-координаты:

static int iXCenter; // центра окна,

static int iXPos; // текущей позиции

static int iXMax; // допустимой позиции

int iYPos =0; // Текущая у-координата вывода

int nLines; // Количество линий

SIZE szText; // Экранные размеры строки текста

//====== Стили пера Windows

static DWORD dwPenStyle[] =

{

PS_NULL, PS_SOLID, PS_DOT, PS_DASH,

PS__DASHDOT, PS_DASHDOTDOT

};

//====== Строки текста для вывода в окно

static string style[] =

{

"PS_NULL","PS_SOLID","PS_DOT","PS_DASH",

"PS_DASHDOT","PS_DASHDOTDOT"

};

string sText; // Дежурная строка текста

//===== Логическая кисть — как основа для создания пера

LOGBRUSH lb = { BS_SOLID, color, 0 };

Если вы хотите, чтобы ваш вывод в окно реагировал на изменения пользователем размеров окна, то всегда вводите в оконную процедуру ветвь обработки WM_SIZE. Сделайте это сейчас вместе с изменениями в ветви WM_PAINT:

case WM_SIZE:

//==== В IParam упрятаны размеры окна.

//==== Нас интересует только ширина окна

iXMax = LOWORD(IParam) - 50;

iXCenter = LOWORD(IParam)/2; break;

case WM_PAINT:

hdc = BeginPaint(hWnd, &ps);

//===== Режим выравнивания текста (см. MSDN)

SetTextAlign(hdc, TA_NOUPDATECP | TA_LEFT | TA_BASELINE) ;

sText = "Стили линий в Win32 (Cosmetic pen)";

//== Выясняем размеры строки с текстом заголовка GetTextExtentPoint(hdc,sText.c_str(), sText.size(),

//== Сдвигаем точку вывода вниз на одну строку

iYPos += szText.cy;

iXPos = iXCenter - szText.cx/2;

//==== Выводим заголовок

TextOut(hdc,iXPos, iYPos, sText.c_str(), sText. size ()

}

//==== Перебираем массив стилей пера

nLines = sizeof(style)/sizeof(style[0]);

for (int i = 0; i < nLines; i++)

{

//===== Устанавливаем биты стиля пера

DWORD dw = PS_COSMETIC | dwPenStyle[i];

// Создаем перо толщиной в 1 пиксел

HPEN hp = ExtCreatePen(dw, 1, Sib, 0,NULL);

//===== Выбираем перо в контекст устройства

HPEN hOld = (HPEN)SelectObject(hdc,hp); iYPos += szText.cy;

// Сдвиг позиции

//===== Помещаем перо в точку (10, iYPos)

MoveToEx(hdc, 10, iYPos, NULL);

//==== Проводим линию до точки (iXMax, iYPos)

LineTo(hdc, iXMax, iYPos);

//== Возвращаем старое перо в контекст устройства

SelectObject(hdc, hold);

//=== Освобождаем ресурс пера DeleteObject(hp);

//=== Выводим поясняющий текст

TextOut(hdc, 10, iYPos, style[i].c_str(), style [i] .size ()

} ;

EndPaint(hWnd, &ps) ;

break;

Комментарии в тексте поясняют суть происходящего. Отметьте, что здесь применена стандартная тактика работы с ресурсами GDI, которая состоит из последовательности следующих шагов:

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

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

Будет ли изменяться цвет линий при пользовании стандартным диалогом, который мы уже реализовали?
Будет ли изменяться цвет текста при тех же условиях?

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

static string alt[] = {"PS_ALTERNATE", "PS_COSMETIC" };

Вставьте следующий код в ветвь WM_PAINT перед вызовом EndPaint, затем запустите и проверьте результат:

//======= Косметическое перо (alternate - solid)

Ib.lbStyle = BS_SOLID;

sText = "Косметическое перо alternate или solid";

GetTextExtentPoint(hdc,sText.c_str(), sText.size(),SszText);

iYPos += 2 * szText.cy;

iXPos = iXCenter - szText.cx/2;

TextOut(hdc, iXPos, iYPos, sText.c_str(), sText.size());

for (i = 0; i < 2; i+ + ) {

DWORD dw = i ? PS_COSMETIC : PS_COSMETIC I PS_ALTERNATE;

HPEN hp = ExtCreatePen(dw, 1, &lb, 0, NULL);

HPEN hOld = (HPEN)SelectObject(hdc, hp) ;

iYPos += szText.cy;

MoveToEx(hdc, 10, iYPos, NULL);

LineTo(hdc, iXMax,iYPos);

SelectObject(hdc, hold);

DeleteObject(hp);

TextOut(hdc, 10, iYPos, alt[i].c str(), alt [i] . size ());



Меню и диалог



Меню и диалог

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

Запуск диалога в модальном режиме обеспечивает API-функция DialogBox, последним параметром которой является адрес функции About. Он явно приводится к типу DLGPROC (диалоговые процедуры). Этот тип определен как указатель на функцию обратного вызова (реакцию на сообщение) с определенным прототипом. Функция About будет вызываться системой для обработки сообщений, посылаемых уже не главному окну приложения, а окну диалога. Отметим, что описатели CALLBACK, WINAPi и FAR PASCAL идентичны. Они появились на разных этапах развития Windows и одновременно используются системой для обеспечения совместимости со старыми версиями.

Параметры функции About имеют следующий смысл:

HWND hDlg — Windows-описатель окна диалога; UINT message — код сообщения; WPARAM wParam, LPARAM IParam — два параметра, сопровождающих сообщение.

Диалоговая процедура имеет характерную, давно устоявшуюся структуру. Первая ветвь switch-блока (WM_INITDIALOG) вызывается при открытии диалога, а вторая (WM_COMMAND) — при нажатии кнопок, расположенных в нем. Вместе с сообщением WM_COMMAND приходят два параметра, в которых запакована сопровождающая информация. В нашем случае это идентификатор (ШОК) кнопки ОК, так как другой традиционной кнопки Cancel (IDCANCEL) просто нет в шаблоне диалога. В Win32 идентификатор элемента управления спрятан в младших 16 битах wParam, и его приходится распаковывать. Функция EndDialog закрывает окно диалога.



Оконная процедура



Оконная процедура

Теперь рассмотрим, как устроена оконная процедура wndProc. Ее имя уже дважды появлялось в тексте программы. Сначала был объявлен ее прототип, затем оно было присвоено одному из полей структуры типа WNDCLASSEX. Поле имеет тип указателя на функцию с особым прототипом оконной функции. Здесь полезно вспомнить, что имя функции трактуется компилятором C++ как ее адрес.

Оконная процедура должна «просеивать» все посылаемые ей сообщения и обрабатывать те из них, которые были выбраны программистом для обеспечения желаемой функциональности. Типичной структурой оконной процедуры является switch-блок, каждая ветвь которого содержит обработку одного сообщения. В первом приближении наша оконная процедура реагирует только на три сообщения:

WM_COMMAND — о выборе пользователем одной из команд меню;
WM_PAINT — о необходимости перерисовать клиентскую область окна;
WM_DESTROY — о необходимости закрыть окно.

Сообщение WM_DESTROY (уничтожить окно) посылается системой уже после того, как окно исчезло с экрана. Мы реагируем на него вызовом функции PostQuitMessage, которая указывает системе, что поток приложения требует своего завершения, путем посылки сообщения WM_QUIT. Его параметром является код завершения, который мы указываем при вызове PostQuitMessage.

Примечание 1
Примечание 1

Рассмотренная структура приложения Win32 позволяет сделать вывод, что в подавляющем числе случаев развитие приложения сосредоточено внутри оконной процедуры, а не в функции WinMain. Развитие приложения заключается в том, что в число обрабатываемых сообщений (messages) включаются новые. Для этого программист должен вставлять новые case-ветви в оператор switch (msg).

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

Вы, конечно, обратили внимание на обилие новых типов данных, которые используются в приложениях Win32. Многие из них имеют префикс Н, который является сокращением слова Handle — дескриптор, описатель. Описатели разных типов (HWND, HPEN, HBITMAP и т. д.) являются посредниками, которые помогают найти нужную структуру данных в виртуальном мире Windows. Объекты Windows или ее ресурсы, такие как окна, файлы, потоки, перья, кисти, области, представлены в системе структурами языка С, и адреса этих структур могут изменяться. В случае нехватки реальной памяти Windows выгружает из памяти ненужные в данный момент времени объекты и загружает на их место объекты, требуемые приложением. В системной области оперативной памяти Windows поддерживает таблицу, в которой хранятся физические адреса объектов. Для поиска объекта и управления им сначала следует получить у системы его описатель (место в таблице, индекс). Важно иметь в виду, что физический адрес объекта — понятие для Windows, а не для программиста. Описатель типа HANDLE можно уподобить номеру мобильного телефона, с помощью которого вы отыскиваете объект, перемещающийся в виртуальном мире Windows.



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



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

Последним испытанием для геометрического пера будет произвольное bitmap-изображение. Если задать BS_PATTERN в качестве стиля кисти, на основе которой создается перо, то линия рисунка может иметь произвольный узор и толщину, что дает волю фантазии разработчика. Однако сначала следует создать ресурс bitmap-изображения, загрузить его и задать его описатель в поле IbHatch логической кисти. Для создания изображения:

Перейдите в окно Resource View.
Раскройте узел дерева под именем API.rc и убедитесь, что в дереве нет узла с именем Bitmap.
Вызовите контекстное меню на узле API.rc и дайте команду Add Resource.
В диалоге Add Resource выберите тип Bitmap и нажмите кнопку New.
Откройте окно Properties и в поле справа от текста ID задайте идентификатор IDB_PAT1 новому точечному изображению.
Измените размер изображения, уменьшив его до 5x5. Используйте для этой цели элементами управления (resize handles) рамки.
Создайте произвольное изображение с помощью контрастирующих цветов.
В окне Resource View вызовите контекстное меню на узле дерева IDВ_РАТ1 и выберите команду Insert Copy.
Измените язык копии на тот, который поддерживается системой, например English (United States), иначе не получится, и нажмите ОК.
Теперь вы имеете копию изображения с теми же размерами и идентификатором. Здесь удобно перевести окно Properties в режим Floating или сдвинуть его нижнюю границу вверх. Измените идентификатор нового изображения на юв_РАТ2 и, при желании, возвратите язык ресурса. Измените узор второго изображения и повторите пункты 7-10, задав идентификатор ЮВ_РАТЗ для третьего изображения.

Возвратитесь в окно API.CPP и введите в число локальных переменных функции wndProc новые массивы:

//===== Массив идентификаторов bitmap

static UINT nPatterns[] =

{

IDB_PAT1, IDB_PAT2, IDB_PAT3

};

static string bitmap!] =

{

"BS_PATTERN, 1", "BS_PATTERN, 2", "BS_PATTERN, 3"

);

Вслед за фрагментом, моделирующим стиль PS_USERSTYLE , вставьте следующий код:

//======= Геометричесое перо (bitmap)

Ib.lbStyle = BS_PATTERN;

sText = "Стили на основе bitmap-узора (Geometric pen)";

GetTextExtentPoint(hdc,sText.c_str(), sText.size 0,SszText);

iYPos += 2 * szText.cy;

iXPos = iXCenter - szText.cx/2;

TextOut(hdc, iXPos, iYPos, sText.c_str(), sText. size () ) ;

nLines = sizeof(nPatterns)/sizeof(nPatterns[0]);

for (i =0; i < nLines; i++)

{

HBITMAP hBmp;

hBmp = LoadBitmap(GetModuleHandle(NULL), (char*)nPatterns[i]);

Ib.lbHatch = long(hBmp);

HPEN hp = ExtCreatePen(PS_GEOMETRIC, 5, &lb, 0, 0) ;

HPEN hOld = (HPEN)SelectObject(hdc, hp) ;

iYPos += szText.cy;

MoveToEx(hdc, 10, iYPos, NULL);

LineTofhdc, iXMax,iYPos);

SelectObject(hdc, hOld);

DeleteObject(hp);

TextOut(hdc, 10, iYPos, bitmap[i].c_str(),

bitmap[i] .size () ) ;

}

Запустите на выполнение и проверьте результат. Вы должны получить окно, которое выглядит так, как показано на Рисунок 3.3. Отметьте, что остались неисследованными еще несколько возможностей по управлению пером — это стили типа PS_ENDCAP_* и PS_JOIN_*. Вы, вероятно, захотите их исследовать самостоятельно. При этом можете использовать уже надоевшую, но достаточно эффективную схему, которой мы пользуемся сейчас.



Программы управляемые событиями



Программы, управляемые событиями

В этом уроке мы с помощью Studio.Net научимся разрабатывать традиционные приложения Win32, основанные на использовании функций API (Application Programming Interface). Вы, конечно, знаете, что приложения для Windows можно разрабатывать как с использованием библиотеки классов MFC, так и на основе набора инструментов, объединенных в разделе SDK (Software Development Kit) студии разработчика. Обширную документацию по SDK вы можете найти в MSDN (Microsoft Developer's Network), которая поставляется вместе со студией. Отдельные выпуски MSDN, зачастую содержат еще более свежую информацию по SDK. Без MSDN успешная деятельность в рамках студии разработчика просто немыслима.

Использование всей мощи MFC облегчает процесс разработки приложений, однако далеко не все возможности Win32 API реализованы в библиотеке MFC, многие из них доступны только средствами API. Поэтому каждому разработчику необходимо иметь представление о структуре и принципах функционирования традиционного Windows-приложения, созданного на основе API-функций. Другими доводами в пользу того, что существует необходимость знать и постоянно углублять свои познания в технологии разработки приложений с помощью SDK, могут быть следующие:

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

В состав API входят не только функции, более 2000, но и множество структур, более 800 сообщений, макросы и интерфейсы. Цель настоящей главы:

показать традиционную структуру Windows-приложения;
продемонстрировать способы управления таким инструментом подсистемы GDI (Graphics Driver Interface), как перо Windows.

Основной чертой всех Windows-приложений является то, что они поддерживают оконный интерфейс, используя при этом множество стандартных элементов управления (кнопки, переключатели, линейки, окна редактирования, списки и т. д.). Эти элементы поддерживаются с помощью динамических библиотек (DLL), которые являются частью операционной системы (ОС). Именно поэтому элементы доступны любым приложениям, и ваше первое приложение имеет почти такой же облик, как и любое другое. Принципиально важным отличием Windows-приложений от приложений DOS является то, что все они — программы, управляемые событиями (event-driven applications). Приложения DOS — программы с фиксированной последовательностью выполнения. Разработчик программы последовательность выполнения операторов, и система строго ее соблюдает. В случае программ, управляемых событиями, разработчик не может заранее предсказать последовательность вызовов функций и даже выполнения операторов своего приложения, так как эта последовательность определяется на этапе выполнения кода.

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

Примечание 1
Примечание 1

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

Наступление события обозначается поступлением сообщения. Все сообщения Windows имеют стандартные имена, многие из которых начинаются с префикса WM_ (Windows Message). Например, WM_PAINT именует сообщение о том, что необходимо перерисовать содержимое окна того приложения, которое получило это сообщение. Идентификатор сообщения WM_PAINT — это символьная константа, обозначающая некое число. Другой пример: при создании окна система посылает сообщение WM_CREATE. Вы можете ввести в оконную процедуру реакцию на это сообщение для того, чтобы произвести какие-то однократные действия.

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

#define WM_MYEDIT_PRESSED WM_USER + 1

Каждое новое сообщение должно увеличивать значение идентификатора по сравнению с WM_MYEDIT_PRESSED. Максимально-допустимым значением для идентификаторов такого типа является число 0x7 FFF. Если вы хотите создать сообщение, действующее в пределах всего приложения и не конфликтующее'с системными сообщениями, то вместо константы WM_USER следует использовать другую константу WM_APP (0x8000). В этом случае можно наращивать идентификатор вплоть до 0xBFFF.



Прохождение сообщений в системе



Прохождение сообщений в системе

Рассмотрим ситуацию, когда пользователь приложения нажимает клавишу, а система вырабатывает сообщение об этом событии. Вы знаете, что Windows обеспечивает поддержку клавиатуры, не зависящую от типа устройства (device-independent support). Для каждого типа клавиатуры она устанавливает соответствующий драйвер, то есть специальную программу, которая служит посредником между клавиатурой и операционной системой. Клавиатурная поддержка Windows не зависит от языка общения с системой. Это достигается использованием специальной клавиатурной раскладки (layout), которую пользователь выбрал в данный момент. Каждой клавише на уровне аппаратуры присвоено уникальное значение — идентификатор клавиши, зависящий от типа устройства и называемый скан-кодом.

Примечание 1
Примечание 1

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

Клавиатурный драйвер интерпретирует скан-код и преобразует его в определяемый Windows код виртуальной клавиши (virtual-key code), не зависящий от типа устройства и идентифицирующий функциональный смысл клавиши. После этого преобразования скан-кода драйвер создает сообщение, в которое включает: скан-код, виртуальный код и другую сопутствующую информацию. Затем он помещает сообщение в специальную очередь системных сообщений.

Windows выбирает сообщение из этой очереди и посылает в очередь сообщений соответствующего потока (thread). В конце концов, цикл выборки сообщений данного потока передает его соответствующей оконной процедуре для обработки. Модель ввода с клавиатуры в системе Windows представлена на Рисунок 3.1.



Путь прохождения сообщений от клавиатуры



Рисунок 3.1. Путь прохождения сообщений от клавиатуры


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

//======= Ярлык типа

typedef struct tagMSG

{

//===== Описатель окна, чья оконная процедура

//===== получает сообщение

HWND hwnd;

UINT message; // Код сообщения

// Дополнительная информация, зависящая от сообщения

WPARAM wParam;

LPARAM iParam; // Тоже

DWORD time; // Время посылки сообщения

//==== Точка экрана, где был курсор

//==== в момент посылки сообщения

POINT pt;

}

MSG; //===== Тип структур, эквивалентный ярлыку

Универсальные параметры wParam и IParam используются различным образом в различных сообщениях. Например, в сообщении WM_LBUTTONDOWN первый из них содержит идентификатор одновременно нажатой клавиши (Ctrl, Shift и т.д.), а второй (IParam) — упакованные экранные координаты (х, у) курсора мыши. Чтобы выделить координаты, программист должен расщепить «длинный параметр» (4 байта) на два коротких (по 2 байта). В нижнем слове, которое можно выделить с помощью макроподстановки, например,

int х = LOWORD(IParam);

хранится координата х, а в верхнем — координата у, которую вы можете выделить с помощью макроса:

int у = HIWORD(IParam);

Отметьте, что классы библиотеки MFC избавляют вас от необходимости распаковывать параметры сообщения.

Следующая схема (Рисунок 3.2) в общих чертах иллюстрирует путь прохождения сообщений. Она любезно предоставлена Мариной Полубенцевой, вместе с которой мы ведем курс Visual C++ в Microsoft Certified Educational Center при Санкт-Петербургском государственном техническом университете.

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

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



Путь прохождения сообщений Windows



Рисунок 3.2. Путь прохождения сообщений Windows




Развитие начальной заготовки



Развитие начальной заготовки

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

В нашем случае диалог вызывается ЛР1-функцией chooseColor, которая требует задать в качестве параметра адрес структуры типа CHOOSECOLOR. Ее надо предварительно создать и использовать для хранения текущего цвета, а также цвета, выбранного пользователем в рамках диалога. Цвет должен храниться в поле rgbResult этой структуры. Вы помните, что оконная процедура многократно, при обработке каждого сообщения, получает и вновь отдает управление системе. Ее локальные (автоматические) переменные будут каждый раз создаваться и погибать. Следовательно, они не в состоянии запомнить текущий выбранный цвет. Выход — использовать либо глобальные, либо статические переменные. Используя второй способ, вставьте в начало тела функции WndProc следующие переменные:

//===== Структура для работы со стандартным диалогом

CHOOSECOLOR cc;

//===== Переменная для хранения текущего цвета

static COLORREF color = RGB(255,0,0);

//===== Массив цветов, выбираемых пользователем

static COLORREF CustColors[16];

Структура CHOOSECOLOR определена в библиотеке, которая сейчас недоступна, поэтому вставьте в конец файла stdafx.h директиву #include <CommDlg.h>. Заодно добавьте туда еще две строки:

#include <string>

using namespace std;

так как ниже мы будем пользоваться объектами типа string из библиотеки STL. Затем в блок switch (wmld) функции WndProc введите ветвь обработки команды меню ID_EDIT_COLOR (саму команду создадим позже):

// Если выбрана команда с идентификатором ID_EDIT_COLOR

case ID_EDIT_COLOR:

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

ZeroMemory(Sec, sizeof(CHOOSECOLOR));

//====== Ее размер

cc.lStructSize = sizeof(CHOOSECOLOR);

//====== Адрес массива с любимыми цветами

cc.lpCustColors = (LPDWORD)CustColors;

if (ChooseColor (ice)) // Вызов диалога

{

// Если нажата кнопка OK,

// то запоминаем выбранный цвет

color = cc.rgbResult;

// Объявляем недействительной

// клиентскую область окна

InvalidateRect(hWnd, NULL, TRUE);

}

break;

Функция ChooseColor запускает диалог в модальном режиме. Это означает, что пользователь не может управлять приложением, пока не завершит диалог. Тактика работы с диалогом такого типа стандартна:

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

Функция InvalidateRect сообщает системе, что часть окна стала недействительной, то есть требует перерисовки. В ответ на это система посылает приложению сообщение WM_PAINT. Наша оконная процедура уже реагирует на это сообщение, но пока еще не рисует. Теперь создадим команду меню, при выборе которой диалог должен появится на экране. Для этого:

Перейдите в окно Resource View.
Раскройте узел дерева ресурсов под именем Menu.
Выполните двойной щелчок на идентификаторе всей планки меню IDC_API.
В окне редактора меню переведите фокус ввода в окно на планке меню с надписью Type here (Внимание, там два таких окна!).
Введите имя меню Edit и переведите курсор вниз в пустое поле для команды.
Введите имя команды меню Color.
Откройте окно Properties и убедитесь, что команда получила идентификатор ID_EDIT_COLOR.
Перетащите мышью меню Edit на одну позицию влево.

Запустите приложение (Ctrl+F5) и опробуйте команду меню Edit > Color. Диалог имеет две страницы. Для того чтобы убедиться в правильном функционировании статического массива любимых цветов (custColors), раскройте вторую страницу, выберите несколько цветов в ее правой части, нажимая кнопку Add to Custom Colors. Обратите внимание на то, что выбранные цвета попадают в ячейки левой части диалога. Закройте и вновь откройте диалог. Новые цвета должны остаться на месте, так как они сохранились в массиве CustColors.



Если вы знакомы со структурой



Стартовая заготовка приложения Win32

Если вы знакомы со структурой приложения Win32, то можете безболезненно пропустить несколько параграфов и перейти к параграфу с заголовком «Развитие начальной заготовки».

Рассмотрим более подробно структуру традиционного Windows-приложения, представленную нам мастером Win32 Application Wizard Studio.Net. Программа спроектирована как шаблон (стартовая заготовка), который можно развивать, внося в него желаемую разработчиком функциональность.

Создайте новый проект приложения Win32. Для этого:

В меню File > New выберите команду Project.
В появившемся диалоге New Project, в окне Project Type раскройте узел дерева под именем Visual C++ Projects и выберите узел Win32 Projects.
В окне Templates выберите тип проекта Win32 Project.
В окне Name задайте имя проекта: API. В окне Location задайте или оставьте без изменения местоположений новой папки с файлами решения (solution).
Нажмите ОК и проанализируйте предлагаемые по умолчанию мастером Win32 Application Wizard настройки проекта.
Нажмите кнопку Finish. Запустите стартовую заготовку и убедитесь, что она создает окно с планкой меню и реагирует на shortcut-комбинацию Alt+? или Alt+/, создавая диалог About. Раскройте дерево в окне Class View студии и щелкните два раза имя глобальной переменной hlnst. Курсор переходит в окно редактора, где вы видите заготовку традиционного приложения Win32. Надо отметить, что она богаче оснащена, чем аналогичные заготовки предыдущих версий Visual C++. Кроме пяти функций здесь содержатся ресурсы (меню, диалог, значок, строки текста, и клавиатурный ускоритель). Вы можете убедиться в этом, раскрыв дерево ресурсов в окне Resource View, которое входит в блок страниц вместе с окном Class View. Анализ и развитие этой заготовки мы произведем немного позже, а сейчас приведем листинг, который создал мастер Win32 Application Wizard.1

// API.cpp : Определяет точку входа приложения

//

#include "stdafx.h"

#include "API.h"

#define MAX_LOADSTRING 100

//======== Глобальные переменные:

HINSTANCE hlnst;

// Текущий экземпляр

TCHAR szTitle[MAX_LOADSTRING];

// Текст заголовка окна

TCHAR szWindowClass[MAX_LOADSTRING];

// Текст регистрации

//======== Прототипы функций, входящих в данный модуль

ATOM MyRegisterClass(HINSTANCE hlnstance);

BOOL Initlnstance(HINSTANCE, int);

LRESULT CALLBACK WndProc(HWND, UINT, WPARAM, LPARAM);

LRESULT CALLBACK About(HWND, UINT, WPARAM, LPARAM);

int APIENTRY WinMain(HINSTANCE hlnstance,

HINSTANCE hPrevInstance,

LPSTR IpCmdLine,

int nCmdShow)

{

//======= TODO: Помещайте код здесь

MSG msg;

HACCEL hAccelTable;

//======= Инициализация глобальных строк текста

LoadString(hlnstance, IDS_APP_TITLE, szTitle, MAX_LOADSTRING); LoadString(hlnstance, IDC_API, szWindowClass, MAX_LOADSTRING);

//======= Вызов функции регистрации приложения

MyRegisterClass(hlnstance);

//======= Инициализация приложения:

if (!Initlnstance (hlnstance, nCmdShow))

{

return FALSE;

}

//======= Загрузка клавиатурных ускорителей

hAccelTable = LoadAccelerators (hlnstance, (LPCTSTR)IDC_API);

//======= Цикл ожидания и обработки сообщений:

while (GetMessage(&msg, NULL, 0, 0))

if (!TranslateAccelerator(msg.hwnd, hAccelTable, Smsg))

{

TranslateMessage(Smsg);

DispatchMessage(Srasg);

}

}

return msg.wParam;

}

//

// FUNCTION: MyRegisterClass ()

//

// НАЗНАЧЕНИЕ: Регистрирует оконный класс

//

// COMMENTS: //

// Эта функция нужна только если вы хотите, чтобы код

// был совместим с Win32 системами, которые

// существовали до создания функции 'RegisterClassEx ' ,

// введенной в Windows 95.

// Вызов 'RegisterClassEx' необходим для правильного

// создания маленького (small) значка, ассоциированного

// с приложением.

//

ATOM MyRegisterClass (HINSTANCE hlnstance)

{

WNDCLASSEX wcex;

wcex.cbSize = sizeof (WNDCLASSEX) ;

wcex. style = CS_HREDRAW | CS_VREDRAW;

wcex.lpfnWndProc = (WNDPROC) WndProc;

wcex. cbClsExtra = 0;

wcex.cbWndExtra = 0;

wcex. hlnstance = hlnstance;

wcex.hlcon = Loadlcon (hlnstance,

(LPCTSTR) IDI_API) ;

wcex.hCursor = LoadCursor (NULL, IDC_ARROW) ;

wcex.hbrBackground = (HBRUSH) (COLOR_WINDOW+1) ;

wcex.lpszMenuName = (LPCSTR) IDC_API;

wcex. IpszClassName = szWindowClass;

wcex.hlconSm = Loadlcon (wcex. hlnstance, (LPCTSTR) IDI_SMALL)

return RegisterClassEx (&wcex) ;

}

//

// FUNCTION: Initlnstance (HANDLE, int)

//

// НАЗНАЧЕНИЕ: Запоминание описателя экземпляра

// приложения и создание главного окна приложения

//

// COMMENTS:

// В этой функции мы запоминаем описатель экземпляра

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

// главное окно приложения.

//

BOOL Initlnstance(HINSTANCE hlnstance, int nCmdShow)

{

HWND hWnd;

//======= Запоминаем экземпляр приложения

hlnst = hlnstance;

//======= Создаем главное окно

hWnd = CreateWindow(szWindowClass, szTitle, WSJDVERLAPPEDWINDOW,CW_USEDEFAULT, 0, CW_USEDEFAULT, 0, NULL, NULL, hlnstance, NULL),

if (IhWnd) {

return FALSE; }

ShowWindow(hWnd, nCmdShow);

UpdateWindow(hWnd) ;

return TRUE; }

//

// FUNCTION: WndProc(HWND, unsigned, WORD, LONG)

//

// НАЗНАЧЕНИЕ: Обработка сообщений главного окна.

//

// WM_COMMAND - обработка команд меню

// WM_PAINT - перерисовка окна

// WM_DESTROY - посылка сообщения о завершении и выход

//

//

LRESULT CALLBACK WndProc (HWND hWnd, UINT message,

WPARAM wParam, LPARAM IParam)

{

int wmld, wmEvent;

PAINTSTRUCT ps;

HDC hdc;

switch (message)

{

case WM_COMMAND:

wmld = LOWORD (wParam) ;

wmEvent - HIWORD (wParam) ;

//====== Расшифровка выбора в меню:

switch (wmld)

{

case IDM_ABOUT:

DialogBox (hlnst, (LPCTSTR) IDD_ABOUTBOX, hWnd,

(DLGPROC)About) ;

break;

case IDM_EXIT:

DestroyWindow(hWnd);

break;

default:

return DefWindowProc(hWnd, message, wParam, IParara);

{

break;

//======= Ветвь перерисовки содержимого окна

case WM_PAINT:

hdc = BeginPaint(hWnd, sps);

//======= TODO: Вставьте сюда рисующий код

EndPaint(hWnd, Sps);

break; //======= Ветвь закрытия окна

case WM_DESTROY:

PostQuitMessage(0);

break; default:

return DefWindowProc(hWnd, message, wParam, IParam);

}

return 0;

}

//======= Обработчик команды вызова диалога About

LRESULT CALLBACK About(HWND hDlg, UINT message,

WPARAM wParam, LPARAM IParam)

{

switch (message)

{

//======= Ветвь инициализации окна диалога

case WM_INITDIALOG:

return TRUE;

//======= Ветвь обработки команд, исходящих

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

case WM_COMMAND:

if (LOWORD(wParam) == IDOK ||

LOWORD(wParam) == IDCANCEL)

EndDialog(hDlg, LOWORD(wParam));

return TRUE;

}

break;

}

return FALSE;

}


Теперь ответим на один из



Рисунок 3.3. Стили пера в Win32


Теперь ответим на один из вопросов, которые были заданы по ходу текста этой главы. Для того чтобы изменился цвет текста, надо вставить вызов API-функции SetTextColor. И сделать это надо в ветви обработки сообщения WM_PAINT перед вызовом функции TextOut.

SetTextColor(hdc, color) ;

Подведем итоги. Наш пример иллюстрирует характерные особенности строения приложения, управляемого событиями:

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


Структура Windowsприложения



Структура Windows-приложения

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

функции winMain, с которой начинается выполнение программы и которая «закручивает» цикл ожидания сообщений (message pump);
оконной процедуры, которую вызывает система, направляя ей соответствующие сообщения.

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

Примечание 1
Примечание 1

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

Функция winMain выполняется первой в любом приложении. Ее имя зарезервировано операционной системой. Она в этом смысле является аналогом функции main, с которой начинается выполнение С-программы для DOS-платформы. Имя оконной процедуры произвольно и выбирается разработчиком. Система Windows регистрирует это имя, связывая его с приложением. Главной целью функции winMain является регистрация оконного класса, создание окна и запуск цикла ожидания сообщений.



Традиционное Windowsприложение



Традиционное Windows-приложение
Программы, управляемые событиями
Прохождение сообщений в системе Структура Windows-приложения
Стартовая заготовка приложения Win32 и ее анализ
Оконная процедура
Меню и диалог
Развитие начальной заготовки
Управление пером Windows



Управление пером Windows



Управление пером Windows

Если вы хотите самостоятельно освоить какой-либо технологический прием или способ управления ресурсами, а так же инструментами Windows, то лучше всего обратиться к разделу Platform SDK документации (MSDN). В блоке страничных окон, которыми вы успешно пользуетесь, имеется страница Dynamic Help, которая помогает быстро отыскать необходимую информацию в море документации, сопровождающей Studio.Net. Предположим, вы хотите научиться создавать перо Windows и начать с получения справки. Надо открыть вкладку Dynamic Help и набрать в окне редактора текст, который, как вам кажется, имеет отношение к искомой теме, например Реn.

Окно динамической справки следит за вашим вводом и пытается найти подходящий раздел в документации. В нашем случае вы должны увидеть пару тем, связанных с пером Windows. Открыв первую из них (Pen Class), вы убеждаетесь, что попали в раздел Visual Basic Help, то есть не туда. Второй попыткой может быть выбор строки СРеп или CreatePen. Теперь динамическая справка приводит вас ближе к цели. Если вы вспомните, что сейчас мы пользуемся функциями API, то выбор темы CreatePen будет точным.

Примечание 1
Примечание 1

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

Внимательно прочтя всю страницу текста справки из раздела Platform SDK, вы поймете, что перо Windows — это достаточно сложный и гибкий инструмент рисования. Не пренебрегайте также гипертекстовыми ссылками внизу экрана под рубрикой See Also. Выберите там ссылку ExtCreatePen для одноименной функции, которую мы собираемся использовать. Правила игры с функцией ExtCreatePen не так просты, как хотелось бы, но они позволяют управлять атрибутами пера в широком диапазоне. Оказывается кроме «простых» перьев можно создавать перья на основе кисти.