Глава 19. Создание MFC-приложений

Ранее, в главах 16 и 17, нами были рассмотрены ресурсы Windows, такие как меню, диалоговые окна, горячие клавиши. В предыдущей главе речь шла об основных принципах применения библиотеки MFC при создании приложений Windows. В настоящей главе мы попытаемся, воспользовавшись полученными знаниями о ресурсах и MFC, создать несколько полноценных 32-разрядных приложений.

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

Шаблон MFC-приложения

В предыдущей главе вы познакомились с простейшей MFC-программой, которая отображала на экране свое окно. Ее код можно рассматривать как основу для любого другого приложения Windows, выводящего в окно текст или графику.

Первое из рассматриваемых в этой главе приложений, которое называется MFCSWP (MFCSimpleWindowsProgram), просто выводит строку текста в рабочую область окна. Прежде чем приступать к его изучению, обратимся к листингу программы. Собственно говоря, ниже показано два листинга: файл заголовков и программный файл.

Вспомните, что в предыдущей главе содержимое файла заголовков было включено в программный файл. Здесь в файле заголовков приводится информация о том, как классы приложения порождаются от базовых классов библиотеки MFC. Такой стиль рекомендован разработчиками Microsoft. Вот текст файла MFCSWP.H:

class CMainWnd : public CFrameWnd

{

public:

CMainWndO;

afx_rasg void OnPaintО;

DECLARE MESSAGE MAP() ;

};

class CmfcswpApp : public CWinApp

{

public:

BOOL Initlnstance(); } ;

Ниже показан текст файла MFCSWP.CPP:

//

//  mfcswp.cpp

//  Эту программу можно использовать как шаблон

//  для разработки других MFC-приложений.

//

#include <afxwin.h>
#include "mfcswp.h"

CmfcswpApp theApp;

CMainWnd::CMainWnd() {

Create(NULL, "A MFC Windows Application",

WS_OVERLAPPEDWINDOW, rectDefault, NULL, NULL); }

void CMainWnd: :OnPaint () {

CPaintDC dc(this);

dc.TextOut(200, 200, "Using the MFC Library", 21); }

BEGIN_MESSAGE_MAP(CMainWnd, CFrameWnd)

ON_WM_PAINT() END_MESSAGE_MAP ()

BOOL CmfcswpApp::Initlnstance() {

m_pMainWnd = new CMainWndO;

m_pMainWnd->ShowWindow(m_nCmdShow);

m_pMainWnd->UpdateWindow() ;
return TRUE;

Файл MFCSWP.H

В MFC-приложениях обычно используются файлы заголовков двух типов. Файлы первого типа, наподобие рассматриваемых в этом параграфе, описывают порождение классов приложений от базовых классов MFC. Их имена совпадают с именами приложений и имеют расширение Н. Файлы второго типа содержат идентификаторы элементов меню и диалоговых окон, а также описания различных констант. Для распознавания файлов этого типа мы будем в их именах добавлять букву R, что означает "resource" (ресурс). Такие файлы встретятся нам в этой главе в двух последних примерах программ.

Итак, в нашем файле заголовков содержится описание двух классов: CMainWnd, порождаемого от CWinApp, и CmfcswpApp, потомка CFrameWnd.

class CMainWnd : public CFrameWnd

public:

CMainWnd () ;

afx_msg void OnPaint();

DECLARE_MESSAGE_MAP () ;

);

class CmfcswpApp : public CWinApp

public:

BOOL Initlnstance();
}

Класс CMainWndсодержит метод OnPaint() и макрос, связанный с обработкой сообщений. В случае обработчиков сообщений, например OnPaint(), вместо ключевого слова virtual используется afx_msg. Наличие метода OnPaint() позволяет изменять содержимое рабочей области окна приложения. Эта функция автоматически вызывается каждый раз, когда объекту CMainWndпередается сообщение WM_PAINT.

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

Файл MFCSWP.CPP

В основу нашего приложения положен код рассматривавшейся в главе 17 программы SIMPLE.CPP, однако между этими кодами имеется несколько существенных отличий. Они касаются, в частности, метода OnPaint(). Проанализируем показанный ниже фрагмент программы.

void CMainWnd::OnPaint()

{
CPaintDC do(this);
dc.TextOut(200,200, "Using the MFC Library", 21);

}

Сначала создается контекст устройства, в рамках которого может вызываться любая GDI-функция. Когда метод OnPaint() завершает свою работу, автоматически вызывается деструктор класса CPaintDC.

В данном приложении используется предельно короткая схема сообщений:

BEGIN_MESSAGE_MAP(CMainWnd,CFrameWnd)

ON_WM_PAINT;()

END_MESSAGE_MAP()    . .  ...

В параметрах макроса begin_message_map указаны два класса: CMainWndиCFrameWnd. Первому адресуются сообщения, а второй содержит стандартные обработчики, которые вызываются по умолчанию. Макрос on_wm_paint() управляет обработкой всех сообщений wm_paint и переадресовывает их только что рассмотренной функции OnPaint{). Макрос end_message_map завершает схему сообщений.

Существенное преимущество любой схемы сообщений состоит в том, что с ее помощью удается избежать использования тяжеловесных и чреватых ошибками конструкций с характерными для процедурных приложений операторами switch/case.

Запуск программы

Создав файл проекта в компиляторе VisualC++, вы должны убедиться в том, что установили опцию использования библиотеки MFC, иначе во время компиляции программы получите серию непонятных сообщений об ошибках. Окно данного приложения показано на рис. 19.1.


Рис. 19.1. Вывод текста в рабочую область окна

Если вы хотите поэкспериментировать с различными графическими примитивами, удалите из программы функцию TextOut() и вставьте вместо нее функцию Rectangle (), Ellipse () или, скажем, LineTo(). В следующем примере о применении этих функций мы поговорим более подробно.

Рисование графических примитивов в рабочей области окна

Вторая программа рисует в рабочей области окна различные графические фигуры. О том, как это происходит, речь шла в главе 17. Данное приложение также будет представлено двумя файлами: файлом заголовков GDI.H и программным файлом GDI.CPP. Файл заголовков, по сути, не отличается от используемого в предыдущем примере.

class CMainWnd : public CFrameWnd

{

public:

CMainWnd ();

afx_msg void OnPaintO;

DECLARE_MESSAGE_MAP() ; );

class CgdiAApp : public CWinApp { public:

BOOLInitlnstance();

}

Теперь приведем текст программного файла:

//
//       gdi.cpp
//  Расширенный вариант приложения mfcswp.cpp,
//  ориентированный на создание графических примитивов.
//

#include <afxwin.h>
#include "gdi.h"

CgdiAApp theApp;

CMainWnd::CMainWnd() {

Create(NULL, "Experimenting With GDI Primitives",

WSJDVERLAPPEDWINDOW, rectDefault, NULL, NULL); >>

void CMainWnd::OnPaint() {

static DWORD dwColor[9] = {RGB(0, 0, 0),       // черный


RGB(255,0, 0),      // красный
RGB (0,255., 0),     // зеленый
RGB(0,0, 255),      // синий
RGB(255,255, 0),    // желтый
RGB(255,0, 255),    // пурпурный
RGB(0,255,255),    // голубой
RGB(127,127, 127),  // серый
RGB(255,255, 255)};// белый

short   xcoord;

POINT   polylpts[4],polygpts[5] ;

CBrush   newbrush;

CBrush*  oldbrush;

CPen  newpen;

CPen*oldpen;

CPaintDC dc(this);

// рисование толстой черной диагональной линии

newpen.CreatePen(PS_SOLID, 6, dwColor[0]);

oldpen = dc.Selectobject(Snewpen);

dc.MoveTo(0, 0) ;

dc.LineTo(640,430);

dc.TextOut(70,20,"<-diagonal line",15);

// удаление пера

dc.Selectobject(oldpen);

newpen.DeleteObj ect() ;

// рисование синей дуги

newpen.CreatePen(PS_DASH, 1, dwColor[3]);

oldpen = dc.Selectobject(Snewpen) ;

dc.ArcdOO,100, 200, 200, 15X), 175, 175,150),

dc.TextOut (80,180, "small -arc->", 11);

// удаление пера

dc.SelectObjecb(oldpen);

newpen.DeleteObject();

// рисование зеленого сегмента с толстым контуром
newpen.CreatePen(PS_SOLID, 8, dwColor[2]);
oldpen = dc.Selectobject(Snewpen);
dc.Chord(550, 20,630,80, 555, 25,625,70);
dc.TextOut(485,30,"chord->", 7) ;
// удаление пера
dc.Selectobject(oldpen);
newpen.DeleteObject() ;

// рисование эллипса и заливка его краснымцветом
newpen.CreatePen(PS_SOLID, 1, dwColor[1]);
oldpen = dc.Selectobject(Snewpen);
newbrush.CreateSolidBrush(dwColor[1]);
oldbrush = dc.Selectobject(Snewbrush) ;
dc.Ellipse(180, 180, 285, 260);


dc.TextOut(210,215, "ellipse",7); // удаление кисти
dc.SelectObject(oldbrush) ; newbrush.DeleteObject(); // удаление пера
dc.SelectObject(oldpen) ; newpen.DeleteObj ect{);

// рисование круга и заливка его синим цветом

newpen.CreatePen(PS_SOLID, I, dwColor[3]);

oldpen = dc.SelectObject(Snewpen);

newbrush.CreatesolidBrush(dwColor[3]) ;

oldbrush = dc.SelectObject(Snewbrush);

dc.Ellipse(380,180, 570, 370);

dc.TextOut(450,265,"circle",6);

// удаление кисти

dc.SelectObject(oldbrush) ;

newbrush.DeleteObject(); 

// удаление пера

dc.SelectObject(oldpen);

newpen.DeleteObject() ;

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

newpen.CreatePen(PS_SOLID, I, dwColor[0]);

oldpen = dc.SelectObject(Snewpen);

newbrush.CreatesolidBrush(dwColor[2]);

oldbrush = dc.SelectObject(Snewbrush);

dc.Pie(300,50,400, 150, 300, 50,300, 100);

dc.TextOut(350,80,"<-pie wedge", 11);

// удаление кисти

dc.SelectObject(oldbrush);

newbrush.DeleteObject();

// удаление пера

dc.SelectObject(oldpen);

newpen.DeleteObj ect() ;

// рисование черного прямоугольника и заливка его серым цветом

newbrush.CreateSolidBrush(dwColor[7]) ;

oldbrush = dc.SelectObject(Snewbrush);

dc.Rectangle(50,300, 150, 400);

dc.TextOut(160,350,"<-rectangle", 11);

// удаление кисти  

dc.SelectObject(oldbrush);

newbrush.DeleteObject() ;

// рисование черного закругленного прямоугольника

//изаливка его синим цветом

newbrush.CreateHatchBrush(HS_CROS5, dwColor[3]);

oldbrush = dc.SelectObject(Snewbrush) ;

dc.RoundRect(60, 310, 110,350, 20,20);

dc.TextOut(120,310, "<————rounded rectangle", 24);

// удаление кисти
dc.SelectObject(oldbrush);
newbrush.DeleteObject();

// рисование нескольких зеленых точек

for(xcoord = 400; xcoord < 450; xcoord += 3)

dc.SetPixel(xcoord, 150, OL); dc.TextOut(455, 145, "<-pixels", 8);

// рисование толстой ломаной линии пурпурного цвета

newpen.CreatePen (PS_SOLID, 3, dwColor [5-] );

oldpen = dc.SelectObject(Snewpen);

polylpts[0].x = 10;

polylpts[0].y = 30;

polylpts[l].x = 10;

polylpts[l].y = 100;   ;

polylpts[2].x = 50;

polylpts[2].y = 100;

polylpts[3].x = 10;

polylpts[3].y = 30;

dc.Polyline(polylpts, 4);

dc.TextOut(10,110, "polyline", 8);

// удалениепера

dc.SelectObject(oldpen);

newpen.DeleteObject();

// рисование голубого многоугольника

//изаливка его диагональной желтой штриховкой

newpen.CreatePen(PS_SOLID, 4, dwColor[6]);

oldpen = dc.SelectObject(Snewpen);

newbrush.CreateHatchBrush(HS_FDIAGONAL, dwColor[4]);

oldbrush = dc.SelectObject(Snewbrush);

polygpts[0].x = 40;

polygpts[0].y = 200;

polygptsfl].x = 100;

polygpts[l].y = 270;

polygpts[2].x = 80;

polygpts[2].y = 290;

polygpts[3].x = 20;

polygpts[3].y = 220;

polygpts[4].x = 40;

polygpts[4].y = 200;

dc.Polygon(polygpts, 5);

dc.TextOut(70,210,"<-polygon", 9) ;

// удаление кисти

dc.SelectObject(oldbrush);

newbrush.DeleteObject();

// удаление пера

dc.SelectObject(oldpen);

newpen.DeleteObject(); } BEGIN MESSAGE MAP(CMainWnd, CFrameWnd)

ON_WM_PAINT () END_MESSAGE_MAP()

BOOL CgdiAApp::Initlnstance() {

m_jpMainWnd = new CMainWnd () ;

m_pMainWnd->ShowWindow(m_nCmdShow) ;

m_pMainWnd->UpdateWindow();

return TRUE;
}           
  .

Файл GDI.CPP

В функции OnPaint( ) создается массив dwColor, в котором хранятся девять RGB-значений цветов для используемых кистей и перьев.

static DWORD dwColor[9] = {RGB(0,0, 0),   //   черный

RGB(255, 0, 0),//   красный

RGB(0,255, 0),    //   зеленый

RGB(О,О, 255),    //   синий

RGB(255,255, 0),  //   желтый

RGB(255, 0, 255),  //   пурпурный

RGB(0,255,255),  //   голубой

RGB(127,127, 127),//   серый

RGB(255,255, 255)};   //   белый

Классы CBrush и СРеn позволяют создавать объекты кистей и перьев, которые могут использоваться любыми функциями класса cdcи его потомков (классов, связанных с контекстами устройств). Кисти могут иметь сплошную и штриховую заливку, а также заливку с применением растрового узора. Перья рисуются сплошной (ps_solid), штриховой (ps_dash), пунктирной (ps_dot), штрихпунктирной (ps_dashdot) и штрихточечной (ps_dashdotdot) линиями. Ниже показан синтаксис создания объектов кистей и перьев:

CBrush newbrush;
CBrush* oldbrush;
CPen   newpen;
CPen*  oldpen;

Поскольку для рисования различных графических примитивов применяются аналогичные алгоритмы, мы рассмотрим лишь два типичных примера. В первом на экран выводится толстая черная диагональная линия:

// рисование толстой черной диагональной линии

newpen. CreatePen(PS_SOLID, 6, dwColor[0]);

oldpen = dc.SelectObject (Snewpen) ;

dc.MoveTo(0, 0);

dc.LineTo(640, 430);

dc.TextOut (70,   20,   "odiagonal  line",   15);

// удаление пера

dc.SelectObject (oldpen) ;

newpen. DeleteObject() ;

Объект пера создается функцией CreatePen() „которая задает рисование черных сплошных линий толщиною в шесть логических единиц. Сразу после этого функция SelectObject() загружает созданный объект в контекст устройства и возвращает указатель на предыдущий объект пера. Функции Moveто() и LineTo() формируют линию, которая рисуется выбранным пером. Наконец, функция Textout() выводит надпись рядом с нарисованной фигурой. В завершение снова вызывается функция Selectobject(), которая восстанавливает прежнее перо, а функция DeleteObject() удаляет ненужное перо.

Работа с кистями организуется аналогичным образом. В следующем фрагменте кода создается кисть с заливкой горизонтальными и вертикальными штрихами (hs_cross) синего цвета.

// рисование черного закругленного прямоугольника

//и заливка его синим цветом

newbrush.CreateHatchBrush(HS_CROSS, dwColor[3]);.

oldbrush = dc.Selectobject(Snewbrush);

dc.RoundRect(60, 310, 110, 350, 20,20);

dc.TextOut(120,310,"<—————rounded rectangle", 24);

// удаление кисти

dc.Selectobject(oldbrush);

newbrush.DeleteObject();

Функция RoundRect() рисует закругленный прямоугольник с черным контуром, который заливается с помощью выбранной кисти.

Запуск программы

Окно аналогичной программы представлено на рис. 19.2.


Рис. 19.2. Построение различных GDI объектов

Возможно, вы заметили, что в программе имеется один недостаток. Заключается он в том, что все координаты графических объектов, выводимых GDI-функциями, задаются в пикселях. А что произойдет, если изменится разрешение экрана, например вместо режима 800x600 будет установлен режим 1024x768? В этом случае выводимое изображение займет только верхнюю левую часть окна.

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

Построение ряда Фурье

Следующая программа выводит в рабочей области окна графическое представление ряда Фурье. В данном приложении используются ресурсы двух видов: меню и диалоговые окна. По мере возрастания сложности программы увеличивается и число файлов, вовлеченных в проект. Данное приложение включает файл заголовков FOURIER.H, файл ресурсов FOURIERR.H(обратите внимание на дополнительное 'R' в конце имени файла), файл сценариев ресурсов FOURIER. RCи программный файл FOURIER.CPP.

Ниже показан текст файла FOURIER. H:

class CMainWnd : public CFrameWnd

{

public:

CMainWnd();

afx_msg void OnPaintO;

afx_msg void OnSize(UINT, int, int);

afx_msg int  OnCreate(LPCREATESTRUCT cs);

afx_msg void OnAboutO;

afx_msg void OnFourierData() ;

afx_msg void OnExit();

DECLARE MESSAGE MAP() 

);

class CTheApp : public CWinApp

{

public:

virtual BOOL Initlnstance(); );

class CFourierDataDialog : public CDialog

{

public:

CFourierDataDialog(CWnd* pParentWnd=NULL) : CDialog("FourierData", pParentWnd)

{  }

virtual void OnOK(); };

Файл FOURIERR.H содержит идентификаторы элементов меню и диалоговых окон.

#define IDM_FOUR 100
#define IDM_ABOUT 110

#define IDM_EXIT 120
#define IDD_TERMS 200
#define IDD_TITLE 201

Файл FOURIER.RC включает описания меню и двух диалоговых окон. В приложении используются довольно простые диалоговые окна, а именно About и окно ввода данных.

#include "fourierr.h"

#define APSTUDIO_READONLY_SYMBOLS

////////////////////////////////////////////////////

#define APSTUDIO_HIDDEN_SYMBOLS

#include "windows. h"  

#undef APSTUDIO_HIDDEN_SYMBOLS #include "afxres.h"

/////////////////////////////////////////

tundef APSTUDIO_READONLY_SYMBOLS

//////////////////////////////////////////
// Ресурсы для английского (США) языка

#if !defined(AFX_RESOURCE_DLL) || defined (APX_TARG_ENU) . -tifdef _WIN32 LANGUAGE LANG_ENGLISH, SUBLANG_ENGLISH_US

#pragma code_page (1252)
#endif
//_WIN32

//////////////////////////////////////////
//

// Меню//

FOURIERMENU MENU DISCARDABLE BEGIN

POPUP "Fourier Data" BEGIN

MENUITEM "Fourier Data...",IDM_FOUR MENUITEM "Fourier About...",IDM_ABOUT MENUITEM "Exit",IDM_EXIT END END

/////////////////////////////////////////

//

// Диалоговые окна

//

ABOUTBOX DIALOG DISCARDABLE 14,22,200, 75

STYLE WS_POPUP | WS_CAPTION

CAPTION "About Box"

BEGIN  

CTEXT "A Fourier Series Waveform", -1, 30, 5, 144, 8

CTEXT "A MFC Application", -1,30, 17,144,8

CTEXT "By William H. Murray and Chris H. Pappas", -1,28,28, 144, 8

CTEXT "(c)Copyright'1998",201, 68,38, 83, 8

DEFPUSHBUTTON "ОК",IDOK, 84,55,32,14,WS_GROUP END

FOURIERDATA DIALOG DISCARDABLE 74,21,142, 70 STYLE WS_POPUP | WS_CAPTION CAPTION "Fourier Data" BEGIN

LTEXT "Title: ", -1,6, 5, 28,8, NOT WS_GROUP

EDITTEXT IDDJTITLE, 33,1, 106,12

LTEXT "Number of terms: ", -1,6, 23,70/ 8, NOT WS_GROUP

EDITTEXT IDDJTERMS, 76,18, 32, 12

PUSHBUTTON "ОК", IDOK, 25,52,24,14

PUSHBUTTON "Cancel", IDCANCEL, 89,53,28,14 END

tifdef APSTUDIO_INVOKED

//////////////////////////////////////

1 TEXTINCLUDE DISCARDABLE BEGIN

"resource.hNO" END

2 TEXTINCLUDE DISCARDABLE

BEGIN      

"#define APSTUDIO_HIDDEN_SYMBOLSNrNn" 

"#include ""windows.h""NrNn"

"tundef APSTUDIO_HIDDEN_SYMBOLSNr\n"

"#include ""afxres.h""NrNn"

"NO" END

3 TEXTINCLUDE DISCARDABLE BEGIN

"NrNn"

"NO"

END  

#endif    // APSTUDIO_INVOKED

#endif   .11 Ресурсы для английского (США) языка

Текст файла FOURIER.CPP несколько сложнее, чем в предыдущем примере, поскольку программа должна поддерживать работу меню и двух диалоговых окон.

//

//   fourier.cpp

//   Построение ряда Фурье.

//

#include <afxwin.h>
#include <string.h>

#include <math.h>

#include"fourierR.h"   // идентификаторы ресурсов

#include "fourier.h"

int m_cxClient, m_cyClient;
char mytitle[80] = "Title";
int nterms = 1;

CTheApp theApp;

CMainWnd::CMainWnd() {

Create(AfxRegisterWndClass(CS_HREDRAW I CS_VREDRAW,

LoadCursor(NULL, IDC_CROSS), (HBRUSH) (GetStockObject>{WlJJTE._BRUSH) ) NULL),

"Fourier Series Application with the MFC", WS OVERLAPPEDWINDOW, rectDefault, NULL, "FourierMenu");

}

void CMainWnd::OnSize(UINT,int x, int y) {

m_cxClient = x;

m_cyClient = y; }

void CMainWnd::OnPaint() {

CPaintDC dc(this);

static DWORD dwColor[9J= {
RGB(0,0, 0),       // черный
RGB (245,0, 0),       // красный
RGB(0, 245, 0),       // зеленый
RGB(0,0,245),       // синий
RGB(245,245, 0),     // желтый
RGB(245,0, 245),     // пурпурный
RGB(0, 245, 245),     //голубой
RGB(127,127, 127),   // серый
RGB(245, 245, 245)};  // белый

int i, j, Ititle, ang;

double y, yp;

CBrush newbrush;

CBrush* oldbrush;

CPen newpen;

CPen* oldpen;

// задание области просмотра

dc. SetMapMode (MM_ISOTROPIC) ;

dc.SetWindowExt (500,500);

dc. SetViewportExt (m_cxClient, -m_cyClient) ;

dc.SetViewportOrg(m_cxClient/20, m_cyClient/2) ;

ang = 0; yp = 0 . 0 ;

newpen.CreatePen(PS_SOLID, 2, RGB(0,0, 0) ) ;
oldpen = dc.SelectObject (Snewpen) ;

// рисование осей координат х и у

dc.MoveTo(0, 240);

dc.LineTo(0, -240);

dc.MoveTo(0, 0) ;

dc.LineTo(400, 0) ;

dc.MoveTo(0, 0) ;

// рисование ряда Фурье

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

for(j = 1; j <= nterms; JH-+) (

y= (150.0/{ (2.0*j)-1.0)) * sin(((j*2.0)-1.0)*0.015708*ang); УР = УР + у;

}

dc.LineTo(i, (int)yp);

УР -= УР; ang'+t;

}

// заливка внутренних областей графика серым цветом
newbrush.CreateSolidBrush(dwColor [7]) ;
oldbrush = dc.SelectObject (Snewbrush) ;

dc.ExtFloodFill (150,10,dwColorfO],FLOODFILLBORDER) ;
dc.ExtFloodFill (300,-10, dwColor[0], FLOODFILLBORDER)

// вывод заголовка графика

Ititle= strlen(mytitle) ;

dc.TextOut (200-(ltitle*8/2) , 185, mytitle, Ititle);

// удаление кистей

dc. SelectObject (oldbrush) ;

newbrush.DeleteObject () ;

}

int CMainWnd::OnCreate(LPCREATESTRUCT) {

UpdateWindow();

return (0); }

void CMainWnd::OnAbout() {

CDialog about("AboutBox", this);

about.DoModal(); }

void CFourierDataDialog: :OnOK() {

GetDlgltemText (IDDJTITLE, mytitle, 80);
nterms = GetDlgItemInt(IDD_TERMS, NULL, 0) CDialog: :OnOK();

}   

void CMainWnd::OnFourierData() {

CFourierDataDialog dlgFourierData(this); if (dlgFourierData.DoModal() == IDOK) ( InvalidateRect(NULL, TRUE);
UpdateWindow(); } };

void CMainWnd::OnExit() {

DestroyWindowO ; }

BEGIN_MESSAGE_MAP (CMainWnd,   CFrameWnd)

on_wm_paint() on_wm_size'o on_wm_create ( )

ON_COMMAND(IDM_ABODT, OnAbout) '! '"6Sf_%OMMAND(IDM_FOUR., OnFourierData)

O^COMMAND(IDM_EXIT, OnExit) END_MESSAGE_MAP ( )

BOOL CTheApp: : Initlnstance () {

m_pMainWnd = new CMainWndO;

m_pMainWnd->ShowWindow(m_nCmdShow) ;

m_pMainWnd->UpdateWindow() ;

return TRUE;

}


Файл
FOURIER.H

Класс CMainWnd теперь содержит несколько обработчиков сообщений: OnPait (), OnSize(), OnCreate(), OnAbout(), OnFourierData(} И OnExit():

afx_msg void OnPaint();

afx_msg void OnSize(UINT,   int,   int);

afx_msg  int OnCreate(LPCREATESTRUCT  cs);

af x_msg void OnAbout ( ) ;
afx_msg void OnFourierData ();
afx_msg void OnExit();

Функция OnPait() вызывается автоматически, когда объекту CMainWnd поступает сообщение wm_paint от Windows или от самого приложения. Функция OnSize() вызывается в ответ на сообщение wm_size, которое генерируется при изменении размеров окна приложения. Информация о размерах необходима для динамического масштабирования содержимого окна. Функции OnCreate( ) передается структура, содержащая информацию о создаваемом окне: его размерах, стиле и других атрибутах. Функции OnAbout( ) , OnFourierData ( ) и OnExit ( ) определяются пользователем и вызываются при получении сообщений wm_command, генерируемых при выборе соответствующих команд меню.

Для работы с диалоговыми окнами в MFC существует класс CDialog. Его можно непосредственно использовать для вывода простейших диалоговых окон, например окна About. В случае более сложных окон потребуется создать собственный класс. В нашем примере используется диалоговое окно, в котором пользователь может ввести заголовок для графика и целочисленное значение, задающее число членов ряда Фурье. Класс CFourierDataDialogпорождается от CDialog. Для ввода данных создается модальное окно, т.е. с приложением невозможно будет продолжать работу до тех пор, пока пользователь не введет данные и не закроет диалоговое окно. Объявление класса выглядит следующим образом:

class CFourierDataDialog : public CDialog

{

public:

CFourierDataDialog (CWnd* pParentWnd=NULL) : CDialog ("FourierData", pParentWnd)

{  }

virtual void OnOK();

};

Классы диалоговых окон, порождаемые от CDialog, могут иметь собственную схему сообщений. Правда, если переопределяются только функции OninitDialog(), ОnОК () и OnCancel(), ее можно не создавать. В нашем простом примере конструктор CFourierDataDialog() передает конструктору родительского класса имя диалогового окна, FourierData, и указатель на родительское окно.

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

Файлы ресурсов

Файл FOURIERR.H содержит пять идентификаторов. Константы idm_four, idm_aboutи Idm_exitслужат идентификаторами команд меню, тогда как константы IDD_TERMS и IDD_TITLE используются для описания элементов диалогового окна. Файл FOURIER. RC содержит описания меню и диалоговых окон.

Файл FOURIER.CPP

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

Создание окна

Одновременно с созданием окна выполняется регистрация класса окна с помощью функции AfxRegisterWndciass() . В качестве указателя мыши устанавливается перекрестие (idc_cross), для заливки фона выбирается белая кисть (white_brush), а значком приложения становится стандартный системный значок (последний параметр равен null).

CMainWnd::CMainWnd() (

Create(AfxRegisterWndciass(CS_HREDRAW I CS_VREDRAW,

LoadCursor(NULL, IDC_CROSS), (HBROSH)(GetStockObject(WHITE_BRUSH)), NULL),

"Fourier Series Application with the MFC", WSJDVERLAPPEDWINDOW, rectDefault, NULL, "FourierMenu"); 1

Обратите также внимание, что в функции Create() задается название меню приложения.

Определение текущих размеров окна

Сообщение wm_sizeгенерируется всякий раз, когда изменяются размеры окна приложения. В функцию OnSizet)передаются текущие размеры окна, которые сохраняются в переменных m_cxdientи m_cyClient:

void CMainWnd::OnSize(UINT, int x, int y) {

m_cxClient = x;

m_cyClient = y;

}

Значения этих переменных используются для масштабирования содержимого окна в соответствии с его размерами.

Построение ряда Фурье

Чтобы избежать проблем с масштабированием выводимого изображения, о которых упоминалось выше, создается свободно масштабируемая поверхность рисования. Сначала с помощью функции SetMapMode() устанавливается режим отображения MM_ISOTROPIC:

dc.SetMapMode(MM_ISOTROPIC);

Далее логические размеры окна задаются равными по вертикали и горизонтали 500 единицам:

dc.SetWindowExt(500,500);

Это означает, что какими бы ни были действительные размеры окна, программа будет рассматривать его как квадрат со сторонами по 500 единиц. В следующей строке размеры области просмотра задаются равными текущим размерам рабочей области окна:

dc.SetViewportExt(ra_cxClient, -m_cyClient);

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

dc.SetViewportOrg(m_cxClient/20, m_cyClient/2);

Затем строятся оси координат х и у.

//рисование осей координат х и у
dc.MoveTo(0, 240);
dc.LineTo(0, -240);
dc.MoveTo(0, 0);
dc.LineTo(400, 0);
dc.MoveTo(0,0);

Показанный ниже фрагмент программы, ответственный за выведение на экран ряда Фурье, содержит два цикла for. В переменной цикла iхранится значение координаты х, я в переменной цикла j— значение текущей.гармоники. Координата у каждой точки представляет собой сумму значений всех гармоник для заданного угла.

// рисование ряда Фурье
for(i= 0; i <= 400; i++) {

for (j = 1; j <= nterms; j++){

y=   (150.0/((2.0*j)-1.0))   *  sin(((j*2.0)-1.0)*0.015708*ang);
yp = yp +  y; }
dc.LineTo (i,    (int)yp);

yp -= ур; 

ang++;

}  

Для связи всех точек графика в единую кривую используется функция LineTo(). Внутренняя область графика будет закрашена серым цветом с помощью функции ExtFloodFill(). Эта функция требует указания координат произвольной точки внутри закрашиваемой области и цвета границы области. Установка параметра floodfillborder означает, что функция будет закрашивать всю область до границы, заданной указанным цветом.

// заливка внутренних областей графика серым цветом

newbrush.CreateSolidBrushfdwColor[7]) ;

oldbrush = dc.SelectObject(Snewbrush);

dc.ExtFloodFill (150,10, dwColor[0], FLOODFILLBORDER);

dc.ExtFloodFill(300,-10,dwColorfO],FLOODFILLBORDER);

Построение графика завершается выведением надписи и удалением из памяти созданного ранее объекта кисти:

// вывод заголовка графика Ititle = strlen(mytitle);

dc.TextOut(200-(ltitle*8/2), 185, mytitle, Ititle);

// удаление кистей
dc.SelectObject(oldbrush);
newbrush.DeleteObjeot() ;

Диалоговое окно About

Окна типа About служат для выдачи пользователю информации о программе, ее авторах, авторских правах и т.д. Модальное диалоговое окно About появляется на экране при выборе в меню команды Fourier About..., в результате чего вызывается функция OnAbout():

void CMainWnd::OnAbout() {

CDialog about("AboutBox", this);

about.DoModal(); }

Конструктор класса CDialogвоспринимает текущее окно как родительский объект. С этой целью используется указатель this, который всегда указывает на текущий объект. Метод DoModal() выводит диалоговое окно About на экран. После щелчка на кнопке ОК Окно About удаляется с экрана и становится доступным окно приложения, которое при этом перерисовывается.

Окно ввода данных

Диалоговое окно, принимающее данные от пользователя, вызывается путем выбора в меню команды FourierData.... В нем пользователь получает возможность ввести заголовок графика и задать количество членов ряда Фурье. После щелчка на кнопке ОК окно удаляется с экрана и обновляется рабочая область окна приложения.

void CMainWnd::OnFourierData()
CFourierDataDialog dlgFourierData(this);

if (dlgFourierData.DoModal() == IDOK)  
{

InvalidateRect(NULL, TRUE); UpdateWindow();

} };

В файле FOURIER.H классCFourierDataDialog объявлен потомком класса CDialog. Обратите внимание, что к моменту завершения функции DoModal()введенные пользователем данные уже переданы в программу. Это происходит в методе ОnОК (), вызываемом путем щелчка на кнопке ОК:

void CFourierDataDialog::ОnОК() 1

GetDlgItemText(IDD_TITLE, mytitle, 80);

nterms = GetDlgItemInt(IDD_TERMS, NULL, 0); 

CDialog: :OnOK();  
}

Функция GetDlgitemText() записывает строку заголовка графика (определяемую по идентификатору iddjtitle) в переменную mytitle. Аналогичным образом с помощью функции GetDlgltemlntО возвращается введенное пользователем количество членов ряда Фурье. Поле, в котором вводится это число, идентифицируется константой IDD_TERMS. Второй параметр функции GetDlgltemlnt() позволяет проконтролировать успешность преобразования введенной строки в число, но в нашем приложении эта возможность не используется. Если третий параметр не равен нулю, функция должна проверять наличие знака минус и в случае необходимости возвращать знаковое число. Но поскольку в нашем случае значения должны быть только положительными, этот параметр установлен равным 0.

Вызов функции OnExit( )

В меню также содержится команда Exit, при вызове которой с экрана удаляется окно приложения, для чего вызывается функция DestroyWindow():

void CMainWnd::OnExit() {

DestroyWindow () ; }

Схема сообщений

ВмакросеBEGIN_MESSAGE_MAP указываютсядвакласса: CMainWnd иCFrameWnd. Принимающей стороной является класс CMainWnd, а в классе CFrameWndсодержатся базовые обработчики. Макрос on_hm_paint() управляет обработкой всех сообщений WM_PAINT, направляя их методу OnPaint(). Другой макрос, ON_WM_SIZE(), управляет сообщениями WM_SIZE и направляет их методу OnSize(). Макрос ON_WM_CREATE() связан с сообщениями wm_create, которые передаются методу OnCreate(). И еще один макрос, on_command(), поддерживает обработку сообщений, связанных с выбором команд меню. Здесь для каждой команды указывается свой обработчик.

BEGIN_MESSAGE_MAP(CMainWnd, CFrameWnd)

ON_WM_PAINT()

ON_WM_SIZE()

ON_WM_CREATE ()

ON_COMMAND(IDM_ABOUT, OnAbout)

ON_COMMAND(IDM_FOUR, OnFourierData)

ON_COMMAND(IDM_EXIT, OnExit) END_MESSAGE_MAP()

Как уже было сказано, наличие схемы сообщений позволяет избежать использог вания в программе сложных и чреватых ошибками конструкций с операторами switch/case.

Запуск программы

После запуска программы в окне приложения будет построен заданный по умолчанию ряд Фурье, состоящий из одного члена, что соответствует синусоиде (рис. 19.6). На рис. 19.7 показан ряд Фурье.


Рис. 19.6. График, выводимый по умолчанию


Рис. 19.7. Ряд Фурье из n членов

Построение гистограмм

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

В проект входят четыре файла: файл заголовков BARCHART.H, файл ресурсов BARCHARTR.H, файл сценариев ресурсов BARCHART.RCи программный файл BARCHART.CPP. Файл BARCHART.Hсодержит описания классов CMainWnd, CTheAppИ CBarDataDialog:

class CMainWnd : public CFrameWnd

{

public:

CMainWnd () ; 

afx_msg void OnPaintO; 

afx_msg void OnSizeCUINT, int, int);

afx_msg int  OnCreate(LPCREATESTRUCT cs);

afx_msg void OnAboutO;

afx_msg void OnBarData ();

afx_msg void OnExitO;

DECLARE_MESSAGE_MAP ()

};

class CTheApp : public CWinApp

{

public:

virtual BOOL Initlnstance (); };

class CBarDataDialog : public CDialog

<

public:

CBarDataDialog (CWnd* pParentWnd=NULL)

: CDialog ("BarDlgBox", pParentWnd)

{  }  

virtual void OnOK(); };

Файл BARCHARTR.Hсодержит идентификаторы команд меню и элементов управления диалоговых окон:

#define IDM_ABOUT 10 #define IDM_INPUT 20

#define IDM_EXIT  30 #define DM_TITLE  300 #define DM_XLABEL 301 .....,„__
#define DM_YLABEL 302  ' #define DM_P1     303

#define DM_P2 304 #define DM_P3 305 Idefine DM_P4 306

#define DM_P5 307
#define DM_P6 308
#define DM_P7 309
#define DM_P8 310
f define DM_P9 311
#define DM_P10 312 

Описания меню и двух диалоговых окно представлены в файле BARCHART.RC:

#include "resource. h"

# include "barchartr .h"

#define APSTODIO_READONLY_SYMBOLS

//////////////////////////////////////////////////////
#define APSTODIO_HIDDEN_SYMBOLS

#include "windows.h"

#undef APSTUDIO_HIDDEN_SYMBOLS

#include "afxres.h"

//////////////////////////////////////////////////////
#undef APSTUDIO_READONLY_SYMBOLS

////////////////////////////////////////////////////
// Ресурсы для английского (США) языка

#if !defined(AFX_RESOURCE_DLL) || defined(AFX_TARG_ENU)

#ifdef _WIN32

LANGUAGE LANG^ENGLISH, SUBLANG_ENGLISH_US tpragma code_page(1252)

#endif // WIN32

//////////////////////////////////////////////////////

//

// Меню

//

BARMENU MENU DISCARDABLE

BEGIN

POPUP "Bar_Chart" BEGIN

MENUITEM "About Box...",IDM_ABOUT MENUITEM "Bar Values...",IDM_INPUT MENUITEM "Exit", IDM_EXIT END END

//////////////////////////////////////////////////////

//

// Диалоговые окна

//

ABOUTDLGBOX DIALOG DISCARDABLE 14,22, 200, 75

STYLE WS_POPUP I WS_CAPTION

CAPTION "About Box"

BEGIN

CTEXT "A Bar Chart Application", -1,30,5, 144,8

CTEXT "A Simple MFC Windows Application1;, -1,30,17,144, 8

CTEXT "By William H. Murray and Chris H. Pappas",

-1,28/ 28,144, 8

CTEXT "(c)Copyright 1998",-1, 68,38, 83, 8 DEFPUSHBUTTON "ОК", IDOK, 84, 55, 32, 14, WS_GROUP

END

BARDLGBOX DIALOG DISCARDABLE 42,65526,223,209

STYLE WS_POPUP I WS_CAPTION

CAPTION "Bar Chart Data"

BEGIN

GROUPBOX "Bar Chart Title:",100, 5, 11,212,89,WSJTABSTOP

GROUPBOX "Bar Chart Heights", 101, 5, 105, 212, 90,WSJTABSTOP

LTEXT "Title:", -1,43,35,28,8, NOT WS_GROUP

EDITTEXT DM_TITLE, 75,30,137,12

LTEXT "x-axis label:",-1,15,55, 55,8, NOT WS_GROUP

EDITTEXT DM_XLABEL, 75,50,135, 12

LTEXT     "y-axis     label:",-1,15,    75,  60,8, NOT WS_GROUP
EDITTEXT.DM_YLABEL, 75,70,135,12

LTEXT     "Bar #1     ", -1,45,125,  40,  8, NOT WS_GROUP

LTEXT     "Bar #2     ", -1,45,140,  40,  8, NOT WS_GROUP

LTEXT     "Bar #3     ", -1,45,155,  40,  8, NOT WS_GROUP

LTEXT     "Bar #4     ", -1,45,170,  40,  8, NOT WS_GROUP

LTEXT     "Bar #5     ", -1,45,185,  40,  8, NOT WS GROUP

LTEXT "Bar #6  ", -1,130, 125> 40,8, NOT WS_GROOP

LTEXT "Bar #7  ", -1,130, 140, 40,8, NOT WS_GROUP

LTEXT "Bar #8  ", -1,130, 155, 40,8, NOT WS_GROUP

LTEXT "Bar #9  ", -1,130, 170,40,8, NOT WS_GROUP

LTEXT "Bar #10:",-1,130, 185, 45,8, NOT WS_GROUP

EDITTEXT DM_P1, 90,120, 30,12

EDITTEXT DM_P2, 90,135,30, 12

EDITTEXT DM_P3, 90,150, 30,12

EDITTEXT DM_P4, 90,165,30, 12

EDITTEXT DM_P5, 90,180, 30,12

EDITTEXT DM_P6, 180, 120, 30,12

EDITTEXT DM_P7, 180, 135, 30,12

EDITTEXT DM_P8, 180, 150, 30,12

EDITTEXT DM_P9, 180, 165,30,12

EDITTEXT DM_P10, 180, 180, 30,12

PUSHBUTTON "ОК", IDOK, 54,195,24,14

END

PUSHBUTTON "Cancel", IDCANCEL, 124, 195,34,14

#ifdef APSTUDIO_INVOKED

//////////////////////////////////////////////////////

1 TEXTINCLUDE DISCARDABLE BEGIN

"resource.h\0" END

2 TEXTINCLUDE DISCARDABLE BEGIN

#define APSTUDIO_HIDDEN_SYMBOLS\r\n"

#include ""windows.h""\r\n"

#undef APSTUDIO_HIDDEN_SYMBOLS\r\n"

"#include ""afxres.h""\r\n"

"\0" END

3 TEXTINCLUDE DISCARDABLE BEGIN

"\r\n"

"\0" END

#endif        //  APSTUDIO_INVOKED

#endif         //  Ресурсы для  английского   (США)   языка

Ниже приведен текст файла BARCHART.CPP.

//

//  barchart.cpp

//  Построение гистограмм.

//

//

#include <afxwin.h>
#include <string.h>
#include <math.h>
#include <stdlib.h>
#include "barchartr.h"  // идентификаторы ресурсов
#include "barchart.h"
#define maxnumbar 10

char szTString[80] = "(barchart title area)";

char szXString[80]= "x-axis label";

char szYString[80] = "y-axis label";

int iBarSize[maxnumbar] = {20,10, 40, 50};

int m_cxClient, m_cyClient;

CTheApp theApp;

CMainWnd::CMainWnd() {

Create(AfxRegisterWndClass(CS_HREDRAW | CS_VREDRAW,LoadCursor(NULL, IDC_CROSS),

(HBRDSH)'GetStockObject (WHITE_BRUSH) , NULL) , "Bar Chart Application with the MFC", WS_OVERLAPPEDWINDOW, rectDefault, NULL, "BarMenu");

}

void CMainWnd::OnSize(OINT, int x, int y) (

m_cxClient = x;  

m_cyClient = y; )

void CMainWnd::OnPaint()

CPaintDC dc(this);

static DWORD dwColorflO] = {
RGB(0,0, d),        // черный

RGB(245,0, 0),     // красный
RGB(0,245,0),     // зеленый
RGB(0,0, 245),      // синий
RGB(245,245, 0),    // желтый
RGB(245,0, 245),    //пурпурный
RGB(0,245,245),    // голубой
RGB(0,80,80),      // голубовато-серый
RGB(80,80,80),     // темно-серый
RGB(245, 245, 245)};// белый

CFont newfont;

CFont* oldfont;

CBrush newbrush;

CBrush* oldbrush;

int i, iNBars, iBarWidth, iBarMax;

int ilenMaxLabel;

int xl,x2,yl,y2;

int iBarSizeScaled[maxnumbar] ; char sbuffer[10],*strptr;

iNBars = 0;

for(i=0;i < maxnumbar; i++){ iffiBarSize [i]!= 0) iNBars++;

}

iBarWidth = 400/iNBars;

// Поиск столбца, имеющего максимальную высоту
iBarMax = iBarSize[0]; for(i=. 0; i < iNBars; i++)

if(iBarMax < iBarSize[i])iBarMax = iBarSize [i];

// Преобразование максимального значения по у встроку
strptr = _itoa(iBarMax, sbuffer, 10); ilenMaxLabel = strlen(sbuffer);

// Масштабирование столбцов в массиве.
// Максимальная высота столбца — 270.
for(i= 0; i < iNBars; i++)

iBarSizeScaled[i] = iBarSize[i]* (270/iBarMax);

// Задание режима отображения

//и создание области просмотра

dc.SetMapMode(MM_ISOTROPIC) ;

dc.SetWindowExt(640,400);

dc.SetViewportExt(m_cxClient, m_cyClient);

dc.SetViewportOrg(0, 0) ;

// Выводтекставокно, еслидлянегохватаетместаif(m_cxClient > 200) ( ' newfont.CreateFont(12,12, О, О, FW_BOLD,

FALSE, FALSE, FALSE, OEM_CHARSET, OOT_DEFAULT_PRECIS, CLIP_DEFAULT_PRECIS, DEFAOLT_QUALITY, VARIABLE_PITCH | FF_ROMAN, "Roman");

oldfont = dc.SelectObject(Snewfont); dc.TextOut((300-(strlen(szTString)*10/2) ) ,

15,szTString, strlen(szTString));

dc.TextOut((300-(strlen(szXString)* 10/2)) ,

365,szXString, strlen(szXString));

dc.TextOut((90- ilenMaxLabel*12), 70,strptr, ilenMaxLabel); // удалениеобъекташрифтаdc.SelectObject(oldfont); newfont.DeleteObject();

newfont.CreateFont(12,12,900,900,FW_BOLD,

FALSE, FALSE,.FALSE, OEM_CHARSET, OUT_DEFAULT_PRECIS, CLIP_DEFAULT_PRECIS, DEFAULT_QUALITY, VARIABLE_PITCH | FF_ROMAN, "Roman");

oldfont = dc.SelectObject(Snewfont) ;

dc.TextOut(50,200+ (strlen(szXString)*10/2), szYString, strlen(szYString));

// удаление объекта шрифта

dc.SelectObject(oldfont) ;

newfont.DeleteObj ect() ; }

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

dc.MoveTo(99,49);

dc.LineTo(99,350);

dc.LineTo(500, 350);

dc.MoveTo(99,350);

// Начальные значения

xl = 100;

yl = 350;

x2 = xl + iBarWidth;

// Рисование столбцов

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

newbrush.CreateSolidBrush(dwColor[i]),

oldbrush = dc.SelectObject(Snewbrush)

y2 = 350 - iBarSizeScaled[i];

dc.Rectangle(xl,yl,x2, y2);

xl = x2;

x2 += IBarWidth;

// удаление кисти

dc.SelectObject(oldbrush);

newbrush.DeleteObject(); }

int CMainWnd::OnCreate(LPCREATESTRUCT) {

UpdateWindow();

return (0);

}

void CMainWnd::OnAbout() {

CDialog about("AboutDlgBox", this);

about.DoModal(); }

void CBarDataDialog::OnOK()

{

GetDlgltemText(DMJTITLE, szTString, 80);
GetDlgltemText(DM_XLABEL, szXString, 80);
GetDlgltemText(DM_YLABEL, szYString, 80);
iBarSize[0] = GetDlgltemlnt(DM_P1, NULL, 0)
iBarSize[l]= GetDlgltemlnt(DM_P2, NULL, 0)
iBarSize[2] = GetDlgltemlnt(DM_P3, NULL, 0)
iBarSize[3] = GetDlgltemlnt(DM_P4, NULL, 0)
iBarSize[4] = GetDlgltemlnt(DM_P5, NULL, 0)
iBarSize[5] = GetDlgltemlnt(DM_P6, NULL, 0)
iBarSize[6]= GetDlgltemlnt(DM_P7, NULL, 0)
iBarSize[7] = GetDlgltemlnt(DM_P8, NULL, 0)
iBarSize[8] = GetDlgltemlnt(DM_P9, NULL, 0)
iBarSize[9]= GetDlgltemlnt(DM_P10, NULL, 0)
CDialog: :OnOK();

}

void CMainWnd::OnBarData() {

CBarDataDialog dlgBarData(this); if(dlgBarData.DoModal{) == IDOK) InvalidateRect(NULL, TRUE); UpdateWindow(); ) };

void CMainWnd::OnExit() {

DestroyWindowO ; }

BEGIN_MESSAGE_MAP(CMainWnd, CFrameWnd) ON_WM_PAINT() ON_WM_SIZE() ON_WM_CREATE ()

ON_COMMAND(IDM_ABOUT, OnAbout) ON_COMMAND(IDM_INPUT, OnBarData) ON_COMMAND(IDM_EXIT, OnExit)

END_MESSAGE_MAP()

BOOL CTheApp::Initlnstance() {

m_pMainWnd = new CMainWnd ();

m_pMainWnd->ShowWindow(m_nCmdShow);

m_pMainWnd->UpdateWindow() ;

return TRUE;

}

Файл BARCHART.H

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

CMainWnd:

afx_msg void OnPaint();

afx_msg void OnSize(UINT, int, int);

afx_msg int  OnCreate(LPCREATESTROCT cs);

afxjnsg void OnAbout();

afx_msg void OnBarData();

afx_msg void OnExit();

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

Файлы ресурсов

Два исходных файла, BARCHARTR.H и BARCHRT.RC, объединяются компилятором ресурсов Microsoft в единый файл ресурсов BARCHART.RES.

Файл заголовков BARCHARTR.Hсодержит идентификаторы трех команд меню: idm_about, idm_inputи IDM_EXIT. Еще тринадцать идентификаторов относятся к элементам управления диалогового окна ввода данных. Три из них, dm_title, DM_XLABLEи dm_ylable, связаны с заголовком и подписями к осям. Остальные десять констант, от DM_P1 до ом_Р10, указывают на целочисленные значения, введенные для отдельных столбцов гистограммы.

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

Файл BARCHART.CPP

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

В константе maxnumbarхранится информация о максимально допустимом числе столбцов гистограммы:

#definemaxnumbar  10

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

char szTString[80] = "(barchart title area)";

char szXString[80] = "x-axis label";

char szXString[80] - "y-axis label";

int iBarSize[maxnumbar] = (20,10,40,50};

Размеры рабочей области окна приложения также сохраняются в глобальных переменных:

int i^i_cxClient, m_cyClient;

Наличие этих переменных позволяет масштабировать содержимое окна в соответствии с его текущими размерами.

Цвета столбцов гистограммы выбираются из массива dwColor в определенном порядке. Например, если гистограмма состоит из трех столбцов, то им будут назначены черный, красный и зеленый цвета.

Классы CFont и CBrush позволяют передавать объекты шрифтов и кистей любым функциям класса CDC (базовый класс для работы с контекстами устройств). Новые шрифты требуются для вывода заголовка гистограммы и подписей к осям. Как объявляются такие объекты, показано ниже:

CFont newfont;
CFont* oldfont;
CBrush newbrush;
CBrush* oldbrush;

Масштабирование столбцов

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

iNBars = 0;

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

{

if (IBarSizefi]    !=  0)   iNBars++;

}

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

IBarWidth = 400/iNBars;

Высота столбцов регулируется с учетом максимального введенного значения. Сначала определяется, какова будет высота самого большого столбца:

// Поиск столбца с максимальной высотой
IBarMax = iBarSize[0]; for(i= 0; i< iNBars; i++)

if(iBarMax < iBarSize [i])iBarMax = iBarSize [i];

Максимальное значение будет выведено слева от оси у. Для преобразования числового значения в строку вызывается функция __itoa( ) :

// Преобразование максимального значения по оси у в строку
strptr = _itoa (iBarMax, sbuffer, 10); ilenMaxLabel= strlen (sbuffer) ;

Размеры остальных столбцов масштабируются в соответствии с максимальным значением:

// Масштабирование столбцов в массиве.

// Максимальная высота столбца — 270.

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

iBarSizeScaled[i]   =  iBarSize[i]   *    (270/iBarMax);

Подготовка окна

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

// Задание режима отображения

//исоздание области просмотра

dc.SetMapMode(MM_ISOTROPIC) ;

dc.SetWindowExt(640,400);

dc.SetViewportExt(m_cxClient, m_cyClient);

dc.SetViewportOrg(0, 0) ;

Благодаря этому коду изображение гистограммы при изменении размеров окна будет автоматически масштабироваться.

Вывод текста в окно

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

Что такое шрифт

Под шрифтом понимают набор печатных символов, имеющих одинаковые начертание и размер. Шрифт включает символы букв, знаков препинания и других вспомогательных знаков. В качестве примеров различных шрифтов можно привести Arial размером в 12 пунктов, TimesNewRoman в 12 пунктов, TimesNewRomanв 14 пунктов и т.д. Пункт — это наименьшая единица в типографской системе мер, равная 0,376 мм. 72 пункта составляют один дюйм.

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

Функция CreateFont( )

Функция CreateFontOобъявлена в файле WINDOWS.H. Она выбирает из набора физических GDI-шрифтов тот шрифт, который наиболее точно соответствует характеристикам, указанным при ее вызове. Созданный логический шрифт может использоваться любыми устройствами. Функция CreateFontOимеет следующий синтаксис:

CreateFont(высота, ширина, ориентация,  наклон, толщина, курсив, подчеркивание, зачеркивание,   набор_символов, точность_вывода, точность_отсечения, качество, интервал_и_семейство, название)

Краткое описание параметров функции CreateFont() дано в табл. 19.1.

Таблица 19.1. Параметры функции CreateFont()
Параметр  Описание
(int)   высота Высота символов в логических единицах
(int)  ширина Средняя ширина символов в логических единицах
(int)   ориентация Угол наклона строки (в десятых долях градуса) относительно горизонтальной оси; 0 — слева направо, 900 (90°) — по вертикали снизу вверх, 1800 (180°) — справа налево, 2700 (270°) — по вертикали сверху вниз
(int) наклон Угол наклона символов (в десятых долях градуса) относительно горизонтальной оси; 0 — нормальное отображение, 900 — поворот на 90° против часовой стрелки, 1800 — перевернутое отображение, 2700 — поворот на 90° по часовой стрелке
(int)   толщина   Толщина   шрифта   (от   0   до    1000);   значение   400 (fw_normal) соответствует нормальному начертанию, 700 (fw_bold) — полужирному
(DWORD) курсив Курсивное начертание, если параметр не равен нулю
(DWORD) подчеркивание Подчеркивание символов, если параметр не равен нулю
(DWORD)  зачеркивание Зачеркивание символов, если параметр не равен нулю
(DWORD)   набор_символов Набор символов; возможные константы: ANSI_CHARSET— стандартные ANSI-символы, oem_charset — системно-зависимый набор, russian_charset— русские символы, hebrew_charset— иврит и т.д.
(DWORD)   точность_вывода  Допустимая степень соответствия подобранного системой физического шрифта заданным установкам; некоторые из возможных констант: out_default_precis — стандартный режим подстановки, out_tt_precis— при наличии нескольких шрифтов с одинаковым названием выбирается контурный (TrueType), out_device_precis— при наличии нескольких шрифтов с одинаковым названием выбирается аппаратный
(DWORD)   точность_отсечения Способ отсечения символов, частично выходящих за границы области отсечения; возможные константы: clip_default_precis— стандартный режим, clip_character_precis— отсекается весь символ, clip_stroke_precis— отсекается часть символа (с точностью до штриха) и т.д.
(DWORD)   качество Требуемое качество выводимого шрифта; возможные константы: default_quality— вид шрифта не имеет значения, draft_quality— качество вывода играет минимальную роль, допускается масштабирование растровых шрифтов, PROOF_QUALITY— качество вывода важнее, чем соответствие логическим атрибутам шрифта, а масштабирование растровых шрифтов недопустимо — выбирается наиболее близкий по размеру шрифт
(DWORD)   интервал_и_семейство В двух младших битах задается межсимвольный интервал шрифта; возможные константы: default_pitch — интервал не известен или не имеет значения, fixed_pitch— фиксированный интервал, fixed_pitch— переменный интервал (пропорциональный шрифт). В четырех старших битах определяется семейство, к которому относится шрифт; возможные константы: ff_dontcare— семейство не известно или не имеет значения, ff_roman— шрифт с засечками, с переменной шириной символов, FF_SWISS— шрифт без засечек, с переменной шириной символов, ff_modern— шрифт с постоянной шириной символов, с засечками или без таковых, FF_SCRIPT— шрифт, напоминающий рукописный, ff_decorative— декоративный шрифт
(LPCTSTR)   название Строка, содержащая название гарнитуры шрифта

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

Во второй раз функция CreateFont() вызывается следующим образом:

newfont.CreateFont(12,12,900,900,FW_BOLD,
FALSE, FALSE, FALSE,
OEM_CHARSET, OUT_DEFAULT_PRECIS,
CLIP_DEFAOLT_PRECIS,
DEFAULT_QDALITY,
VARIABLE_PITCH | FF_ROMAN,
"Roman");

Как видите, изменены только параметры ориентации строки и наклона символов. Оба параметра измеряются в десятых долях градуса, поэтому наклону в 90 градусов соответствует значение 900. Этот шрифт используется для вывода подписи к верти­кальной оси гистограммы. А вот как осуществляется вывод строки:

oldfont = dc.SelectObject(Snewfont);
dc.TextOut(50,   200+(strlen(szXString)*10/2), szYString,   strlen(szYString));

Рисование осей координат и столбцов

Координатные оси х и у выводятся на экран с помощью функций MoveTo() и LineTo():

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

dc.MoveTo(99,49);

dc.LineTo(99,350);

dc.LineTo(500,350);

dc.MoveTo(99,350);

Затем выполняются действия по подготовке к выводу столбцов гистограммы. Как видно из следующего фрагмента программы, первый столбец всегда начинается с точки 100,350, на что указывают значения переменных x1 и y1. Горизонтальная координата всех последующих столбцов вычисляется на основании координаты предыдущего столбца и ширины столбцов.

// Начальные значения

xl= 100;

yl = 350;

х2 = xl + iBarWidth;

Информация о высоте столбцов хранится в массиве iBarSizeScaled. Поскольку функция Rectangle() создает замкнутые фигуры, то полученный столбец можно заполнить цветом, воспользовавшись текущей кистью. Цвет кисти выбирается из массива dwColor, причем номер столбца соответствует номеру цвета в массиве.

// Рисование столбцов

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

{

newbrush.CreateSolidBrush(dwColor[ i ]) ;

oldbrush = dc.SelectObject(Snewbrush);

y2 = 350 - iBarSizeScaledfi];

dc.Rectangle(xl,yl,x2,y2);

xl = x2;

x2 +- iBarWidth;

// удаление кисти

dc.SelectObject(oldbrush);

newbrush.DeleteObject() ; }

После того как программа выведет один столбец, значения переменных x1 и х2 будут модифицированы таким образом, чтобы указывать на начальную точку следующего столбца. Цикл, for выполняется столько раз, сколько столбцов содержит гистограмма.

Запуск программы

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


Рис. 19.11. Гистограмма, выводимая аналогичной программой по умолчанию


Рис. 19.12. Гистограмма, созданная на основании пользовательских данных