18104

Разработка графических программ для Windows

Лекция

Информатика, кибернетика и программирование

Лекция №1 Разработка графических программ для Windows Для разработки разнообразных программ для операционной системы Windows существует много инструментальных средств. Различные средства могут воплощать в практику различные методологические подходы. Одну и ту же пр...

Русский

2013-07-06

539.46 KB

5 чел.

Лекция1

Разработка графических программ для Windows

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

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

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

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

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

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

1. Первый пример программы для Windows

Наша первая графическая программа названа StudEx. Она предназначена для работы в среде 32-битных ОС Windows различных версий, например, 95, 98, 2000, Millennium, NT. Программа написана на языке C++ (строго говоря, почти что на чистом С, если не считать комментариев) с использованием функций API Windows. Текст программы состоит из трех файлов:

studex.cpp, studex.rc, studex.def.

Файл studex.срр (это главный файл текста программы):

//————— учебный пример программы для Windows ———

#define STRICT

#include <windows.h>

//———————Объявление функций (прототипы) —————

LRESULT CALLBACK WndProc(HWND hWnd, UINT message,

                WPARAM wParam, LPARAM lParam);

void DrawStudyExample(HWND hWnd);

//———— Затем следует определение функций ———————

//——————Главная функция программы ————————

//——Она определяет первые шаги работы программы —————

//----до создания окна и вхождения в цикл сообщений —————

//--для Windows-программ функция С,C++ main названа WinMain

int WINAPI WinMain (HINSTANCE hInstance,

                   HINSTANCE hPrevInstance,

                   LPSTR lpszCmd,

                   int nCmdShow)

{

MSG msg;

HWND hWnd;

WNDCLASS WndClass;

//--- сначала регистрируем класс главного окна программы

WndClass.style          = NULL;

WndClass.lpfnWndProc    = WndProc;   //адрес функции окна

WndClass.cbClsExtra     = 0;

WndClass.cbWndExtra     = 0;

WndClass.hInstance      = hInstance;

WndClass.hIcon          = LoadIcon( NULL, IDI_APPLICATION );

WndClass.hCursor        = LoadCursor(NULL, IDC_ARROW);

WndClass.hbrBackground  =(HBRUSH)GetStockObject(WHITE_BRUSH);

WndClass.lpszMenuName    = "STUDEXMENU";

WndClass.lpszClassName   = "StudEx";

if (!RegisterClass(&WndClass)) return 0;

//——————потом создаем окно класса StudEx—————

hWnd = CreateWindow("StudEx",

                   "Учебный пример",      //заголовок

                   WS_OVERLAPPEDWINDOW, //стиль окна

                   CW_USEDEFAULT,

                   CW_USEDEFAULT,

                   400,                //размеры окна

                   300,

                   NULL,

                   NULL,

                   hInstance,

                   NULL);

if (!hWnd) return NULL;           //окно создано успешно?

ShowWindow(hWnd, nCmdShow);       //отобразить окно 

UpdateWindow(hWnd);               //обновить окно        ^

//-——————организация цикла обработки сообщений-———

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

{

 TranslateMessage(&msg);

 DispatchMessage(&msg);

}

return msg. wParam;

}

//-———————функция главного окна программы———-——

//ее вызывает Windows, когда посылает сообщение нашей

//———-———--—————— программе—— —--———————

LRESULT CALLBACK WndProc(HWND hWnd, UINT message,

                        WPARAM wParam, LPARAM lParam)

{

switch (message)   //Обработка всех сообщений для окна

{

 case WM_COMMAND:      //Обработка сообщений пунктов меню

      switch (LOWORD(wParam))

      {

      case 201:      //выбран пункт меню Графика

           DrawStudyExample(hWnd); //это наша собственная

                                   //функция

           break;

      case 108:

           DestroyWindow(hWnd);    //для завершения работы 

           break;

      default : break;

      }

      break;

 case WM_DESTROY:

      PostQuitMessage(0); //для окончания цикла сообщений

      break;

 default : return DefWindowProc(hWnd,     //обработка

                                message,  //всех 

                                wParam,   //остальных

                                lParam);   //сообщений

  } return 0L;

}

//———————это наша собственная функция, она ——————

//-будет содержать графические операторы рисования в окне

void DrawStudyExample(HWND hWnd)

{

 HDC hdc;

 hdc = GetDC(hWnd);                //контекст окна

 if (hdc)

 {

   //после того, как получено ненулевое hdc, можно рисовать,

   //например, десять прямоугольников размером 70 х 50

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

     Rectangle(hdc, i*30, i*20, i*30+70, i*20+50);

 }

  ReleaseDC(hWnd,hdc);    //этот контекст больше не нужен

}

Файл studex.rc (здесь содержится описание меню):

STUDEXMENU MENU

{

POPUP "&Файл"

{

  MENUITEM "&Открыть...", 101

  MENUITEM "&3аписать как...", 102

  MENUITEM SEPARATOR

  MENUITEM "&Печать", 103

  MENUITEM SEPARATOR

  MENUITEM "&Закончить", 108

}

MENUITEM "&Графика", 201 

}

Файл studex.def (файл описания для Windows):

NAME StudEx

DESCRIPTION 'Study Example'

EXETYPE WINDOWS

CODE PRELOAD MOVEABLE DISCARDABLE

DATA PRELOAD MOVEABLE MULTIPLE

Приведенный выше текст программыэто только исходный текст. Его необходимо скомпилировать, чтобы получить выполняемый файл в машинных кодах. Для компиляции и отладки этой программы можно использовать разнообразные инструментальные средства программирования, например, Borland C++ 5.02, Borland C++ Builder, Microsoft Visual C++ 6.0 и другие. После создания проекта в среде системы программирования в результате компилирования получим выполняемый файл. Имя этого файла studex.exe (или другое, что определяется при создании проекта). Запустите программу в среде Windows. После появления окна программы выберите пункт меню "Графика". На экране дисплея будет изображение, как на рис. 1.

Рис. 1. Первый пример графической программы

2. Модульность программ

Приведенный выше текст первого примера графической программы для ОС Windows достаточно объемный. Для того чтобы упростить тексты других примеров графических программ, необходимо спланировать наше дальнейшее программирование. Рассмотрим структуру главного файла (studex.cpp):

                                                                   //начало текста, объявление функций

WinMain                                                    // главная функция

   … …

WndProc                                                    // оконная функция

  … …

DrawStudyExample                                  // другие функции (пока что одна)

  … …

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

//-------функция WinMain и объявление WndProc -----

#define STRICT

#include <windows.h>

LRESULT CALLBACK WndProc(HWND hWnd, UINT message,

                        WPARAM wParam, LPARAM lParam);

int WINAPI WinMain (HINSTANCE hInstance,

                   HINSTANCE hPrevInstance,

                   LPSTR lpszCmd,

                   int nCmdShow)

{

   //тело функции WinMain

}

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

#include "winmain.cpp"

void DrawStudyExample(HWND hWnd);

LRESULT CALLBACK WndProc(HWND hWnd, UINT message,

                        WPARAM wParam, LPARAM lParam)

{

 switch (message)

 {

    case WM_COMMAND:

         switch (LOWORD(wParam))

         {

           case 201:

                DrawStudyExample(hWnd);

                break;

           case 108:

                DestroyWindow(hWnd);

                break;

           default : break;

         }

         break;

    case WM_DESTROY:

         PostQuitMessage(0);

          break;

    default:return DefWindowProc(hWnd,message,wParam,lParam);

 }

 return 0L;

}

В файле winmain1. срр содержится текст оконной функции, объявление функции DrawStudyExample и директива #include "winmain.срр", благодаря которой при компиляции используется текст функции WinMain. Следовательно, текст первого примера программы можно трансформировать в такой:

#include "winmain1.срр"

void DrawStudyExample(HWND hWnd)

{

HDC hdc;

hdc = GetDC(hWnd);

if (hdc)

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

   Rectangle(hdc, i*30, i*20, i*30+70, i*20+50) ;

ReleaseDC (hWnd, hdc);

}

Это значительно короче. Большой кусок программы, включающий функции создания и обслуживания окна, спрятан в отдельных файлах, ссылка на которые выполнена директивой #include. Такая модульность с использованием отдельных кусков текста, которые вначале собираются в единый текст и затем компилируются все вместе, может эффективно применяться лишь для небольших программ, которые мы и рассмотрим в качестве примеров. Для более сложных программ лучше использовать проекты из раздельно компилируемых модулей OBJ, а также библиотеки DLL и компоненты типа ActiveX.

3. Использование графических функций API Windows

Операционная система Windows предоставляет программистам возможность использовать в своих программах сотни разнообразных функций, доступ к которым сделан в виде интерфейса API (Application Program Interface). При разработке программы на основе компьютерных языков, таких как С, C++, Pascal, в текст программы можно включать вызовы функций, которые входят в состав API Windows. После компиляции создается выполняемый файл (*.ехе), который можно запускать на разных компьютерах с различными версиями ОС Windows, и программа будет корректно выполняться. Поскольку сами функции располагаются в модулях операционной системы, а в нашей программе содержатся только вызовы функций (call), то код выполняемого файла имеет небольшой размер.

Необходимо заметить, что мы будем рассматривать программы согласно спецификации Win32 для ОС Windows версий 95, 98, частично для NT и 2000. Существует несколько систем программирования, которые поддерживают разработку таких программ, например, Borland C++.

4. Контекст графического устройства

Графические функции из состава API Windows объединены в отдельную группуподсистему GDI (Graphic Device Interface), Важная черта подсистемы GDI аппаратная независимость многих функций от конкретного графического устройства.

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

Если ваша программа делает вызов графических функций API Windows, таких как рисование точек, линий, фигур, текста и тому подобных, необходимо указывать идентификатор контекста (handle of device context) и координаты. Вызов необходимого драйверадля экрана дисплея, принтера или другого устройстваделает уже сама Windows. Это в значительной мере освобождает программиста от второстепенных дел и облегчает разработку программ, однако желательно учитывать специфику работы конкретного устройства.

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

1. Создание, активизация контекста, получение значения hdc.

2. Рисование с помощью графических функций API Windows.

3. Уничтожение, деактивизация контекста соответствующего hdc.

Контекст окна на экране дисплея

Для рисования в окне программы на экране дисплея можно использовать два способа. Эти способы различаются как до особенностям получения значения hdc, так и по возможностям рисования.

Первый способ основывается на использовании пары функций GetDC и ReleaseDC.

HDC hdc;

hdc = GetDC(hWnd);

...                  //здесь вызовы функций рисования

ReleaseDC(hWnd,hdc);

Функция GetDC получает hdc для окна, заданного кодом hWnd. Например, для главного окна программы. Использование контекста графического вывода завершается вызовом функции ReleaseDC. Другую функцию для освобождения контекста в этом случае использовать не следует. Такой способ работы с контекстом использован в приведенном выше примере программы. Вообще этот способ можно рекомендовать везде, за исключением рисования во время обработки сообщения wm_paint.

Второй способ. Используется исключительно в теле обработчика сообщения WM_PAINT оконной функции.

PAINTSTRUCT ps;

HDC hdc;

hdc=BeginPaint(hWnd, &ps);

...            //рисование

EndPaint(hWnd, &ps);

Во время обработки сообщения wm_paint функция BeginPaint обязательно должна вызываться первой, а EndPaintпоследней.

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

Приведем пример программы, рисующей согласно второму способу.

#include "winmain.cpp"

void DrawStudyExample(HWND hWnd);

LRESULT CALLBACK WndProc(HWND hWnd, UINT message,

                        WPARAM wParam, LPARAM lParam)

{

 switch (message)

 { 

   case WM_PAINT:

        DrawStudyExample(hWnd);

        break;

   case WM_COMMAND:

        switch (LOWORD(wParam))

        { 

          case 108:

               DestroyWindow(hWnd);

               break;

          default: break;

        } 

        break;

   case WM_DESTROY:

        PostQuitMesSage(0);

        break;

   default:return DefWindowProc(hWnd,message,wParam,lParam);

 }

 return 0L;

}

void DrawStudyExample(HWND hWnd)

{

HDC hdc;

PAINTSTRUCT ps;

hdc = BeginPaint(hWnd, &ps);

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

 Rectangle(hdc, i*30, i*20, i*30+70, i*20+50) ;

EndPaint(hWnd, &ps);

}

Контекст принтера

Рассмотрим, как можно нарисовать (а точнее, напечатать) что-то на принтере. Для этого также сначала необходимо получить значение hdc соответствующего графического устройства. Потом выполняется рисование. А затем контекст освобождается. Общая схема такая же. Пример программы:

#include "winmain.cpp"

void DrawStudyExample(HWND hWnd);

LRESULT CALLBACK WndProc(HWND hWnd, UINT message,

                        WPARAM wParam, LPARAM lParam)

{ 

 switch (message)

 {

   case WM_COMMAND:

        switch (LOWORD(wParam))

        {

         case 103:   //выбран пункт меню Печать

              DrawStudyExample(hWnd);

              break;

         case 108:

              DestroyWindow(hWnd);

              break;

         default: break;

        }

        break;

   case WM_DESTROY:

        PostQuitMessage(0);

        break;

   default:return DefWindowProc(hWnd,message,wParam,lParam) ;

 }

return 0L;

}

void DrawStudyExample(HWND hWnd)

{ 

 HDC hdc;

 PRINTER_INFO_5 pinfo5[3];

 DWORD dwNeeded, dwReturned;

 DOCINFO di = {sizeof (DOCINFO), "StudEx", NULL};

 if (!EnumPrinters (PRINTER_ENUM_DEFAULT,

                    NULL,

                    5,

                    (LPBYTE) pinfo5,

                    sizeof (pinfo5),

                    &dwNeeded, &dwReturned)) return;

if (!pinfo5[0].pPrinterName) return;

hdc = CreateDC (NULL,

               pinfo5[0].pPrinterName,   //имя.принтера

               NULL, NULL);

if (hdc == NULL) return;     //ошибка создания контекста

if (StartDoc(hdc, &di) > 0)

{

 if (StartPage(hdc) > 0)

 {

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

     Rectangle(hdc, i*30, i*20, i*30+70, i*20+50);

   EndPage(hdc) ;

 }

 EndDoc(hdc);

}

DeleteDC(hdc);

}

Для принтера значение hdc получить несколько сложнее. Сначала необходимо узнать имя принтераздесь это делается с помощью функции EnumPrinters. Обратите внимание на то, что использована не одна структура, а массив из трех структур pinfo5. Потом создается контекст вывода с помощью функции CreateDC. Печать на принтере выполняется с помощью функций StartDoc, StartPage, EndPage и EndDoc. После вызова функции EndDoc контекст принтера необходимо освободить с помощью функции DeleteDC.

Следует заметить, что приведенный пример предназначен для Windows 95, 98. Для Windows NT функцию DrawStudyExample необходимо модифицироватьоб этом можно узнать в документации Win32 SDK.

После печати на бумаге можно заметить отличия результата от изображения на экране. Прежде всего, это касается размера прямоугольников. Для лазерного принтера размеры значительно меньше, а для матричного размеры могут быть и больше. Это объясняется различными разрешающими способностями (dpi) принтеров и экрана дисплея. Кроме того, на матричном принтере рисунок может изменить пропорциив случае, когда (dpi) по вертикали отличается от (dpi) по горизонтали. При вызове функции Rectangle мы использовали координаты в виде пикселов. Функции API Windows разрешают использовать и другие системы координат.

Контекст метафайла

Рассмотрим еще одну разновидность контекстаконтекст метафайла. Здесь рисунок создается в виде файла на диске. Известны два формата метафайлов для Windows: WMF и EMF. Формат EMF более совершенный. В качестве примера приведем текст программы, записывающей рисунок в файл *.emf после выбора пункта меню "Записать Как".

#include "winmain.cpp"

void DrawStudyExample(HWND hWnd);

LRESULT CALLBACK WndProc(HWND hWnd, UINT message,

                        WPARAM wParam, LPARAM lParam)

{

 switch (message)

 {

    case WM_COMMAND:

         switch (LOWORD(wParam))

         {

           case 102:      //выбран пункт меню Записать Как

                DrawStudyExample(hWnd);

                break;

           case 108:

                DestroyWindow(hWnd);

                break;

           default: break;

         }

         break;

    case WM_DESTROY:

         PostQuitMessage(0);

         break;

    default: return DefWindowProc(hWnd,message,wParam,lParam);

 }

 return 0L;

}

#include <mem.h>

void DrawStudyExample(HWND hWnd)

{

//———-сначала используем стандартное окно SaveAs-——

//-——-для определения полного имени файла (szFname)——

OPENFILENAME ofn;

char szFname[256];

szFname[0] = 0;

memset(&ofn, 0, sizeof (OPENFILENAME));

ofn.lStructSize = sizeof (OPENFILENAME);

ofn.hwndOwner   = hWnd;

ofn.lpstrFilter = "Метафайлы (*.emf)\0*.emf\0\0",

ofn.lpStrDefExt = "emf";

ofn.lpstrFile   = szFname;

ofn.nMaxFile    = 256;

if (!GetSaveFileName(&ofn)) return;

//—————————теперь создание метафайла———————————

HDC hdc;

HENHMETAFILE hemf;

hdc = CreateEnhMetaFile(NULL, szFname ,NULL,NULL);

if (hdc == NULL) return; //ошибка, контекст не создан

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

 Rectangle(hdc, i*30, i*20, i*30+70, i*20+50);

hemf = CloseEnhMetaFile(hdc); //запись на диск и закрытие

//--демонстрация возможностей отображения метафайла в окне

HDC hdcWin;

RECT rc;

hdcWin= GetDC(hWnd);             //берем контекст окна

for (int i=0;i<4;i++)      //16 раз отобразим метафайл

 for (int j=0;j<4;j++)    //в ячейках 45х45 пикселов

 {

    rc.left = 50*i;    //текущие границы отображения

    rc.top  = 50*j;

    rc.right = 45 + rc.left;

    rc.bottom = 45 + rc.top;

    PlayEnhMetaFile(hdcWin, hemf, &rc);

 }

//———еще одно отображение метафайла (крестиком) ————

 rc.left = 210;

 rc.top = 20;

 rc.right = 380;

 rc.bottom = 180;

 PlayEnhMetaFile(hdcWin,hemf, &rc);

 rc.top = 180;      //а здесь зеркальный поворот

 rc.bottom = 20;

 PlayEnhMetaFile(hdcWin,hemf, &rc);

 ReleaseDC(hWnd,hdcWin);        //освобождаем контекст окна

 DeleteEnhMetaFile(hemf);       //освобождаем память

}                              //а файл на диске остается

Запустите программу и выберите меню "Файл \ Записать как". После определения имени файла и записи метафайла на диск в окне должно появиться следующее изображение (рис. 2).

Рис. 2.

Метафайл описывает изображение в виде последовательности вызовов графических функций, использованных при создании изображения (в приведенном примере программы это 10 вызовов функции Rectangle). В метафайл записываются функции вместе со значениями аргументов-координат, границы рисунка и другая информация. Общие параметры рисунка можно узнать, если прочитать заголовок метафайла с помощью функции GetEnhMetaFileHeader. Для отображения метафайла нужно задать границы вывода. Поскольку метафайлвекторное описание изображения, то это дает возможность при отображении плавно растягивать рисунок, причем намного лучше, чем растровые изображения. В нашей программе метафайл отображен 18 раз в контексте главного окна. Обратите внимание на крестикздесь использовано зеркальное отображение.

В результате работы этой программы на диске должен появиться файл *.emf. Чтобы проверить содержимое файла, вызывайте любую программу, которая может читать файлы формата EMFнапример, Word для Windows. Выполите вставку рисунка *.emf и вы увидите десять прямоугольников. Рисунок векторный, поэтому его можно свободно растягивать. Границы рисунка определяются согласно диапазону координат всех элементов рисункааргументов вызова функции Rectangle.

Необходимо заметить, что нижний прямоугольник этого рисунка обычно выглядит поврежденным (как именноэто еще зависит от версии Word). Этот прямоугольник рисуется согласно координатам при i = 9:

Rectangle (hdc, 9*30, 9*20, 9*30+70, 9*20+50). Координаты правого нижнего угла (9*30+70, 9*20+50) этого прямоугольника определяют правую и нижнюю границу всего рисунка. Границы рисунка записываются в метафайл в виде координат левого верхнего и правого нижнего углов. Чтобы узнать, какие границы определены для метафайла, можно использовать функцию GetEnhMetaFileHeader:

ENHMETAHEADER emh;

GetEnhMetaFileHeader(hemf, sizeof(ENHMETAHEADER), &emh);

Эта функция заполняет поля структуры emh, имеющей тип enhmetaheader. Границы рисунка записываются в поля emh.rclBounds, и имеют такие значения:

emh.rclBounds.left = 0;

emh.rclBounds.top = 0;

emh.rclBounds.right = 339;

emh.rclBounds.bottom =229;

Обратите внимание на то, что координаты правого нижнего угла имеют значения на единицу меньше, чем должны быть. Это  проявление особенностей функции Rectangle, рисующей правый нижний угол контура как раз на один пиксел ближе к левому верхнему углу. К тому можно привыкнуть, но почему Microsoft Word (версий 6.0 для 95 и 97-й) искажает рисунок (отрезает границы крайнего прямоугольника)? Средствами Word можно расширить границы этого рисунка, но для других файлов *.emf это может привести, например, к повреждению стиля линий. Поэтому попробуем расширить границы рисунка собственноручно, например, так:

SetPixel(hdc, 9*30+70, 9*20+50, RGB(255,255,255));

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

 Rectangle(hdc, i*30, i*20, i*30+70, i*20+50);

Здесь мы сначала рисуем один белый пиксел с помощью функции SetPixel, что никак не изменяет сам рисунок, но координаты его (х,у) = (9*30+70, 9*20+50) = (340, 230) будут учтены при определении границ рисунка. Запустите модифицированную программу, запишите метафайл, и попробуйте загрузить этот метафайл в редактор Word. Рисунок должен выглядеть неповрежденным.

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

HPEN hPenOld,hPen;

SetPixel(hdc, -1, -1, RGB(255,255,255));

SetPixel(hdc, 9*30+71, 9*20+51, RGB(255,255,255));

hPen = CreatePen(PS_SOLID, 3, RGB(0,0,0));//толщина пера

                                         //три пиксела

hPenOld = (HPEN)SelectObject(hdc, hPen);

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

 Rectangle(hdc, i*30, i*20, i*30+70, i*20+50);

SelectObject(hdc, hPenOld);

DeleteObject(hPen);

В приведенном примере две функции SetPixel использованы для установления границ рисунка (-1, -1, 341, 231). Если выбросить эти две функции, то границы будут (0, 0, 339, 229), что может привести к проблемам с использованием метафайла в программе Word.

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

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

Контекст памяти

Рассмотрим контекст памяти (memory context), для которого поверхность рисования создается на основе битовой карты (bitmap).

#include "winmainl.cpp"

void DrawStudyExample(HWND hWnd)

{

HDC hdc,hdcWin;

HBITMAP hBitmap;

hdcWin = GetDC(hWnd);

hdc = CreateCompatibleDC(hdcWin);

hBitmap = CreateCompatibleBitmap(hdcWin, 400, 300);

SelectObject(hdc,hBitmap);

Rectangle(hdc, -1, -1, 401, 301);  //очистка поверхности

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

 Rectangle(hdc, i*30, i*20, i*30+70, i*20+50);

BitBlt(hdcWin,0,0,400,300,hdc,0,0,SRCCOPY); //копирование

DeleteDC(hdc);

DeleteObject(hBitmap);

ReleaseDC(hWnd,hdcWin);

}

Контекст в памяти создается вызовом функции CreateCompatibleDC. Здесь он создается по образцу контекста окна программы. Но в таком контексте отсутствует поверхность рисования. Эту поверхность создаем по образцу цветового формата поверхности окна экрана вызовом функций CreateCompatibleBitmap и SelectObject. Потом можно рисовать в соответствии с hdc. Сначала мы очищаем поверхность (закрашиваем белым цветом), а потом рисуем десять прямоугольников. Потом вызов функции BitBlt, копирующей растровое изображение из контекста памяти (hdc) в контекст окна (hdcWin). После использования контекста памяти его необходимо закрыть и освободить выделенную память, которая была выделена. Здесь это сделано функциями DeleteDC и DeleteObject.

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

//————— попытка анимации - растущие прямоугольники ——

#include "winmain1.срр"

void DrawStudyExample(HWND hWnd)

{

HDC hdc;

hdc = GetDC(hWnd);

for (int size=5; size<=300; size++)

{

 Rectangle(hdc, -1, -1, 401, 301);

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

 Rectangle(hdc, i*30, i*20, i*30+size, i*20+size);

 ReleaseDC(hWnd,hdc);

}

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

//———— более совершенная анимация ————

^include "winmaini.срр"

void DrawStudyExample(HWND hWnd)

{

HDC hdc,hdcWin;

HBITMAP hBitmap;

hdcWin = GetDC(hWnd) ;

hdc = CreateCompatibleDC(hdcWin);

hBitmap = CreateCompatibleBitmapthdcWin, 400, 300);

SelectObject(hdc,hBitmap);

for (int size=5; size<=300; size++)

{                                       •

Rectangle(hdc, -1, -1, 401, 301);

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

Rectangle(hdc, i*30, i*20, i*30+size, i*20+size) ;

BitBlt(hdcWin,0,0,400,300, hdc,0,0,SRCCOPY) ;

••: } De.leteDC(hdc) ;

DeleteObject(hBitmap) ;

ReleaseDC(hWnd.hdcWin);

}

Изображение вначале рисуется в контексте памяти, а затем быстро копируется на экран с помощью функции BitBlt.

Программы КГ часто применяют подобный способ рисования  "за экраном". Этот способ называется двойной буферизацией (double buffering). Кроме анимации, использование двойной буферизации иногда позволяет ускорить рисование  если графические функции могут рисовать в контексте памяти быстрее, чем в окне на экране. Также полезно использование контекста памяти тогда, когда программа, например графический редактор, манипулирует изображением, которое по размерам больше, чем способен отобразить экран дисплея.

Параметры контекста графического устройства

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

value = GetDeviceCaps (hdc, index);

Для того чтобы узнать необходимые параметры контекста hdc, нужно в качестве аргумента index задавать соответствующие значения. Например:

сх   = GetDeviceCaps (hdc, HORZRES);

су   = GetDeviceCaps (hdc, VERTRES) ;

dpiX = GetDeviceCaps (hdc, LOGPIXELSX) ;

dpiY = GetDeviceCaps (hdc, LOGPIXELSY) ;

bits = GetDeviceCaps (hdc, BITSPIXEL) ;

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

Такие параметры, как размеры изображения (сх, су) для окна можно получить и другими способами, например, вызовом функции GetClientRect:

RECT rc;

GetClientRect (hWnd, &rс);

Эта функция заполняет поля структуры rc типа RECT. Поля rc.left и rc.top заполняются нулями, а поля rc.right и rc.bottom хранят координаты соответственно правого и нижнего края данного окна.


 

А также другие работы, которые могут Вас заинтересовать

70822. Измерения активного сопротивления 564.5 KB
  Получение навыков измерения активного сопротивления. Ознакомление с методами измерения активного сопротивления. Ознакомьтесь с принципами организации измерения активного сопротивления косвенным методом.
70823. УПРОЩЕННАЯ ПРОЦЕДУРА ОБРАБОТКИ РЕЗУЛЬТАТОВ ПРЯМЫХ ИЗМЕРЕНИЙ С МНОГОКРАТНЫМИ НАБЛЮДЕНИЯМИ 401 KB
  Ознакомление с упрощенной процедурой обработки результатов прямых измерений с многократными наблюдениями. Знакомство с методами планирования количества наблюдений получение навыков обработки результатов наблюдений и оценивания погрешностей результатов измерений.
70824. Исследование модели шинной ЛВС со случайным доступом 134.5 KB
  Исследовать особенность построения и функционирования шинной ЛВС со случайным методом доступа и определение основных характеристик сети. В результате выполнения лабораторной работы получены знания по структуре, форматам кадров и протоколам физического и канального уровней...
70825. Изучение параметров сигнала с помощью программы SpectrLAB 4.08 MB
  Записать с помощью предоставленного микрофона звуковой сигнал определенной длительности. Изучить его параметры с помощью программы SpectraLAB v4.32.8. Выполнение задания: Исходный сигнал записан в звуковом формате wav, со следующими параметрами: Длительность: 5.28 с...
70827. Дослідження аналогової інтегральної мікросхеми 597 KB
  Експериментальне визначення параметрів не потребує знання схеми і може бути здійснено як для будь-якого чотириполюсника шляхом вимірювання струмів і напруг вхідного і вихідного сигналів.
70828. Счетчик импульсов 130 KB
  Цель: исследование работы счетчика импульсов. Приборы: модель счетчика импульсов СИ блок питания на 5В БП5 соединительные провода. Подсоединить провода питания 5В к выходу БП5 и к входу модели счетчика импульсов СИ. Однократным нажатием на кнопку Счет прибора СИ подаем импульс на вход счетчика.
70829. Децимация и интерполяция 104 KB
  Выполнение процедуры децимации (уменьшения частоты дискретизации в заданное целое число раз) приводит к уменьшению частоты дискретизации исходной последовательности. В процессе децимации исходная последовательность обрабатывается НЧ фильтром, после чего производится выборка с необходимой частотой.
70830. Функции реализуемые АЛУ 112 KB
  Изучить назначение и состав узла АЛУ на примере ИМС К155ИПЗ и К 561 ИПЗ. В состав различных серий микросхем лежащих в основе МП входят стандартные узлы арифметическо-логических устройств АЛУ например К 155 ИПЗ К 561 ИПЗ. Кроме того имеются вход Р0 и выход Р сигналов переноса...