Студопедия

КАТЕГОРИИ:

АвтоАвтоматизацияАрхитектураАстрономияАудитБиологияБухгалтерияВоенное делоГенетикаГеографияГеологияГосударствоДомЖурналистика и СМИИзобретательствоИностранные языкиИнформатикаИскусствоИсторияКомпьютерыКулинарияКультураЛексикологияЛитератураЛогикаМаркетингМатематикаМашиностроениеМедицинаМенеджментМеталлы и СваркаМеханикаМузыкаНаселениеОбразованиеОхрана безопасности жизниОхрана ТрудаПедагогикаПолитикаПравоПриборостроениеПрограммированиеПроизводствоПромышленностьПсихологияРадиоРегилияСвязьСоциологияСпортСтандартизацияСтроительствоТехнологииТорговляТуризмФизикаФизиологияФилософияФинансыХимияХозяйствоЦеннообразованиеЧерчениеЭкологияЭконометрикаЭкономикаЭлектроникаЮриспунденкция

Создание и уничтожение GDI-объектов




Вы еще ни разу не создавали объекты класса CGdiObject — вместо этого Вы конструировали объекты производных классов. Конструкторы для некоторых классов, например, CPen или CBrush, позволяют указывать достаточно информации, чтобы создать объект за одну операцию. Создание других объектов, например, CFont или CRgn, требует второй операции. Объекты этих классов создаются конструктором по умолчанию, после чего вызывается соответствующая функция, например, CreateFont или CreatePolygonRgn.

Класс CGdiObject содержит виртуальный деструктор. Деструкторы производных классов удаляют GDI-объекты, связанные с соответствующими C++ объектами. Если Вы создали объект класса, производного от CGdiObject, то обязаны удалить его до завершения программы. Для удаления GDI-объекта его следует сначала отделить от контекста устройства. Пример мы рассмотрим в следующем разделе.

Примечание. Завершение программы без удаления GDI-объекта в Win16 несло серьезную опасность. Память GDI не освобождалась до момента перезапуска Windows. В Win32 память GDI принадлежит процессу и освобождается при завершении программы. Тем не менее, объект, который Вы забыли освободить, может напрасно занимать значительный объем памяти.

 

Управление GDI-объектами

Итак, Вы знаете, что GDI-объекты нужно удалять, предварительно отсоединив от контекста устройства. Как же это сделать? Функции семейства CDC::SelectObject() выбирают объект GDI в контекст устройства и возвращают указатель на объект, который был выбран в контекст до этого (и теперь отсоединен). Но возникает проблема: нельзя отсоединить старый объект, не выбрав в контекст новый. Простое решение — «сохранить» первоначальный GDI-объект при выборе своего объекта в контекст и восстановить его по завершении работы. После этого Вы можете удалить свой GDI-объект. Пример приведен ниже:

void CMyView::OnDraw(CDC* pDC)

{

CPen newPen(PS_DASHDOTDOT, 2, (COLORREF) 0); /* создание нового

объекта: черное перо шириной 2 пиксела*/

CPen* pOldPen = pDC->SelectObject(&newPen); /* выбираем новое перо

 в контекст и запоминаем старое */

pDC->MoveTo(10, 10);

pDC->Lineto(110, 10);

pDC->SelectObject(pOldPen); // newPen освобождается

} // newPen автоматически уничтожается при выходе

 

При уничтожении контекста устройства все его GDI-объекты отсоединяются. Таким образом, если известно, что контекст устройства будет уничтожен до того, как будут уничтожены выбранные в него объекты, отсоединять эти объекты не нужно. Например, если Вы объявили перо как переменную-член класса «вид» (и инициализировали его при инициализации объекта «вид»), то не надо отсоединять перо внутри OnDraw(): контекст устройства, временем жизни которого управляет обработчик OnPaint() в базовом классе, будет уничтожен раньше.

Стандартные GDI-объекты

Windows предоставляет Вам ряд стандартных GDI-объектов (stock GDI objects). Так как эти объекты — часть Windows, заботиться об их удалении не нужно (Windows игнорирует запросы на удаление стандартных объектов). Функция библиотеки MFC CDC::SelectStockObject() выбирает стандартный объект в контекст устройства и возвращает указатель на выбранный ранее объект, который отсоединяется от контекста. Стандартные объекты удобны, когда Вам нужно отсоединить свой собственный нестандартный перед его удалением. Стандартный объект можно применять вместо «старого» объекта, который использовался в предыдущем примере:

void CMyView::OnDraw(CDC* pDC)

{

CPen newPen(PS_DASHDOTDOT, 2, (COLORREF) 0); /* черное перо

                 шириной 2 пиксела*/

pDC->SelectObject(&newPen);

pDC->MoveTo(10, 10);

pDC->Lineto(110, 10);

pDC->SelectStockObject(BLACK_PEN); // newPen отсоединяется

} // newPen автоматически уничтожается при выходе

 

Стандартные перья, шрифты и палитры перечислены в описании функции CDC::SelectStockObject() в MSDN.

Время жизни контекста устройства

В случае контекста устройства «дисплей» в начале каждой функции-обработчика сообщения Вы получаете «свежий» контекст. Набор выбранных объектов (а также режим преобразования координат и другие параметры контекста) теряется после завершения работы функции. Таким образом, контекст устройства необходимо всякий раз настраивать заново. Виртуальная функция-член OnPrepareDC класса CView удобна для установки режима преобразования координат, но своими GDI-объектами Вы управляете сами.

Для других контекстов устройств, таких, как принтеры или буферы памяти, назначенные Вами параметры могут сохраняться дольше. С этими долгоживущими контекстами устройств возникают сложности из-за временной природы указателей на С++ оъекты GDI, которые возвращаются функцией SelectObject. (Временный «объект» будет удален каркасом приложения во время обработки цикла простоя приложения, некоторое время спустя после возврата управления обработчиком.) Вы не можете просто сохранить указатель в переменной-члене класса — вместо этого его необходимо преобразовать в описатель Windows (единственный постоянный идентификатор GDI-объекта) с помощью функции-члена GetSafeHandle. Пример приведен ниже:

// m_pPrintFont указывает на объект CFont, созданный в конструкторе CMyView

// m_hOldFont – переменная-член CMyView типа HFONT, инициализированная 0

 

void CMyView::SwitchToCourier(CDC* pDC)

{

m_pPrintFont->CreateFont(30, 10, 0, 0, 400, FALSE, FALSE,

                        0, ANSI_CHARSET, OUT_DEFAULT_PRECIS,

                        CLIP_DEFAULT_PRECIS, DEFAULT_QUALITY,

                        DEFAULT_PITCH | FF_MODERN,

                        "Courier New"); // TrueType

CFont* pOldFont = pDC->SelectObject(m_pPrintFont);

 

//m_hOldFont – переменная-член типа HFONT, в которой хранится указатель

m_hOldFont = (HFONT) pOldFont->GetSafeHandle();

}

 

void CMyView:SwitchToOriginalFont(CDC* pDC)

{

// FromHandle – статическая функция-член, возвращающая

// указатель на объект

if (m_hOldFont) {

   pDC->SelectObject(CFont::FromHandle(m_hOldFont));

}

}

// m_pPrintFont удаляется в деструкторе CMyView

Примечание. Будьте осторожны при удалении объекта, указатель на который возвращает SelectObject. Если Вы создали этот объект сами, то имеете право удалить его. Если же указатель временный, что бывает с объектами, первоначально выбранными в контекст устройства, то объект удалять нельзя.

 


9. Виды приложений, создаваемые с помощью мастеров Visual C++

Приложение, основанное на диалоговом окне

MFC AppWizard (exe) → Dialog Based.

Приложение, имеющее одно диалоговое окно (рис.1.). В этом окне можно, естественно, располагать другие управляющие элементы: поля ввода, кнопки, списки и т.д. В качестве примера такого приложения можно привести проект Fire, который входит в состав примеров (samples), поставляемых вместе с Microsoft Visual Studio 6.0. Проект Fire можно также использовать как иллюстрацию использования вкладок (класс CTabCtrl) и других управляющих элементов. В такое приложение можно также добавлять обработчики событий, контекстное меню. В целом проект Fire весьма поучителен.

 

 

Рис.1. Приложение, основанное на диалоговом окне

Дадим краткий обзор приложения Fire как основанного на диалоговом окне. В проект входят такие основные файлы (и классы): Fire.cpp, FireDlg.cpp и FireWnd.cpp.

В файле Fire.cpp описывается и определяется объект theApp класса CFireApp, который –объект – и представляет собой собственно программу. Класс CFireApp является наследником класса CWinApp, который имеется в любом приложении, построенном на базе библиотеки MFC.

Когда приложение запускается на выполнение, то в первую очередь вызывается функция WinMain(), которая имеется в любом приложении Windows, но, как и в данном приложении, она скрыта в каркасе приложения. Вы можете увидеть ее вызов, если запустите приложение в режиме трассировки с заходом в функции (команда отладки F11). Еще раньше, до вызова функции WinMain(), в соответствии с семантикой языка С++ конструируется объект theApp, так как он описан в глобальной области, и, следовательно, вызывается его конструктор. Таким образом, первым после запуска программы на выполнение выполняется код конструктора класса CFireApp. Вы можете в этом убедиться сами, если добавите точку прерывания в этот конструктор или, еще лучше, выведете сообщение с использованием макроса TRACE.

В данном приложении Fire класс CFireApp объявлен в файле Fire.h, содержимое которого следующее (комментарии и несущественные макросы в листинг не включены):

// fire.h : главный заголовочный файл для приложения FIRE

#include <afxcmn.h>

#include "resource.h"

class CFireApp : public CWinApp

{

public:

CFireApp();

//{{AFX_VIRTUAL(CFireApp)

public:

virtual BOOL InitInstance();

//}}AFX_VIRTUAL

DECLARE_MESSAGE_MAP()

};

 

Как видно из листинга файла Fire.h, в классе CFireApp имеется только конструктор и важная виртуальная функция InitInstance(), которая вызывается функцией WinMain(). Отметим, что до сих пор никакого окна программы еще не создано.

Посмотрим на реализацию класса CFireApp, которая приведена в файле Fire.cpp (комментарии и несущественные макросы в листинг не включены):

// fire.cpp : определяет поведение приложения

#include "stdafx.h"

#include "fire.h"

#include "firedlg.h"

BEGIN_MESSAGE_MAP(CFireApp, CWinApp)

//{{AFX_MSG_MAP(CFireApp)

//}}AFX_MSG

ON_COMMAND(ID_HELP, CWinApp::OnHelp)

END_MESSAGE_MAP()

CFireApp::CFireApp()

{

// Добавьте сюда код конструирования объекта

// Всю важную инициализацию включите в функцию InitInstance

}

CFireApp theApp;

BOOL CFireApp::InitInstance()

{

DWORD dwVersion = ::GetVersion();

BOOL bWin31 = (dwVersion > 0x80000000 && (BYTE)dwVersion < 4);

if (bWin31)

{

   AfxMessageBox(IDP_WIN32S);

   return FALSE;

}

Enable3dControls();

LoadStdProfileSettings(); 

CFireDlg dlg;

m_pMainWnd = &dlg;

int nResponse = dlg.DoModal();

if (nResponse == IDOK)

{

   // Добавьте сюда код, который должен выполняться при закрытии  // диалогового окна с помощью клавиши ОК

}

else if (nResponse == IDCANCEL)

{

   // Добавьте сюда код, который должен выполняться при закрытии  // диалогового окна с помощью клавиши CANCEL

}

return FALSE;

}

 

Обратите внимание на то, что в функции InitInstance() определяется объект dlg класса CFireDlg диалогового окна и показывается окно путем вызова функции dlg.DoModal(). Если бы наше приложение базировалось не на диалоговом окне, то код функции InitInstance() был бы другим. До сих пор весь тот код, который мы рассмотрели (код в файлах Fire.h и Fire.cpp), был сгенерирован мастером и у нас не было необходимости в его модификации. Однако в каком-либо другом приложении у нас может появиться необходимость в модификации этого кода.

Теперь обратим свое внимание на файл firedlg.h, в котором объявляется класс диалогового окна нашей программы – самый важный класс:

// firedlg.h : заголовочный файл для класса окна диалога

#include "FireWnd.h"

class CFireDlg : public CDialog

{

public:

CFireDlg(CWnd* pParent = NULL); // стандартный конструктор

//{{AFX_DATA(CFireDlg)

enum { IDD = IDD_FIRE_DIALOG };

CSpinButtonCtrl m_SettingCtrl;

CEdit m_Setting;

CButton m_Apply;

CTreeCtrl m_Property;

CSliderCtrl m_Height;

CTabCtrl m_Color;

CProgressCtrl m_Burn;

CFireWnd m_FireWnd;

int m_nSetting;

//}}AFX_DATA

 

int m_nSettingMin;

int m_nSettingMax;

 

//{{AFX_VIRTUAL(CFireDlg)

protected:

virtual void DoDataExchange(CDataExchange* pDX); // DDX/DDV support

//}}AFX_VIRTUAL

 

protected:

HICON m_hIcon;

UINT m_uTimerID;

 

// Generated message map functions

//{{AFX_MSG(CFireDlg)

virtual BOOL OnInitDialog();

afx_msg void OnSysCommand(UINT nID, LPARAM lParam);

afx_msg void OnPaint();

afx_msg HCURSOR OnQueryDragIcon();

afx_msg void OnApply();

afx_msg void OnPropertySelchanged(NMHDR* pNMHDR, LRESULT* pResult);

afx_msg void OnColorSelchange(NMHDR* pNMHDR, LRESULT* pResult);

afx_msg void OnDestroy();

afx_msg void OnTimer(UINT nIDEvent);

afx_msg void OnSettingChange();

afx_msg void OnActivate(UINT nState, CWnd* pWndOther, BOOL bMinimized);

afx_msg void OnVScroll(UINT nSBCode, UINT nPos, CScrollBar* pScrollBar);

//}}AFX_MSG

DECLARE_MESSAGE_MAP()

};

 

Прежде всего отметим, что «вручную» в файл были добавлены только две строки: описания член-данных m_nSettingMin и m_nSettingMax, а также директива #include "FireWnd.h". Весь остальной код в этом файле был сгенерирован мастером.

В секции тела класса, заключенной между строками //{{AFX_DATA(CFireDlg) и //}}AFX_DATA, описаны переменные, ассоциированные с управляющими элементами диалогового окна с помощью мастера ClassWizard.

 В секции тела класса, заключенной между строками //{{AFX_MSG(CFireDlg) и //}}AFX_MSG, мастер разместил объявления функций таблицы сообщений, т.е. обработчиков сообщений Windows.

Реализация класса CFireDlg находится в файле Filedlg.cpp и, в контексте рассмотрения последовательности выполнения кода приложения, для нас важно то, что, после выполнения кода конструктора класса CFireDlg вызывается функция CFireDlg::OnInitDialog(). Если «забыть» о функции CFireDlg::OnPaint(), то можно сказать, что по завершении функции CFireDlg::OnInitDialog() на экране наконец-то появляется диалоговое окно и вот теперь уже можно говорить, что «программа выполняется».

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

В данном приложении имеется еще один интересный класс CFireWnd, который ассоциирован с управляющим элементом Picture (идентификатор IDC_FIRE) и предназначен для изображения пламени – «изюминки» данного приложения. Конструктор этого класса вызывается каркасом приложения при конструировании диалогового окна, так же как и конструкторы всех других управляющих элементов.

 

Приложение без поддержки архитектуры документ-вид

MFC AppWizard (exe) → Single document.

Выберем для приложения имя NoDoc и на шаге 1 сделаем выбор вида приложения в соответствии с рис.1. Далее оставим все установки по умолчанию, просто нажав клавишу Finish.

 

 

Рис.2. Выбор вида приложения

 

Дадим приложению имя NoDoc и на шаге 1 сделаем выбор вида приложения в соответствии с рис.2, т.е. отметим радиокнопку Single document и снимем флажок с переключателя Document/View architecture support. Далее просто оставим все остальные установки, предлагаемые мастером по умолчанию, просто нажав клавишу Finish. Мастер покажет, как обычно, окно с информацией о проекте, которое должно выглядеть так, как показано на рис. 3.

Назначение основных файлов данного проекта следующее:

NoDoc.cpp     – файл с программой, где определен объект CNoDocApp::theApp;

MainFrm.cpp  – файл с классом-рамкой окна приложения CMainFrame;

ChildView.cpp – файл с определением класса вид CChildView.

 

 

Рис.3. Приложение с окном вид без архитектуры документ-вид

 

Основную функциональность приложения мы, очевидно, можем реализовать в классе вид CChildView или в каком-либо дополнительном классе. Вид главного окна приложения показан на рис. 4. Активацию исполнения программного кода можно выполнить посредством меню, присоединенного к классу рамки приложения CMainFrame. В главное окно приложения можно выводить текст или графику.

 

 

 

Рис.4. Вид главного и единственного окна приложения NoDoc

 


Приложение с поддержкой архитектуры документ-вид

MFC AppWizard (exe) → Single document+ Document/View architecture support.

Выберем для приложения имя SDoc и на шаге 1 сделаем выбор вида приложения в соответствии с рис.5, т.е. выберем Single document и Document/View architecture support. Далее оставим все установки по умолчанию, просто нажав клавишу Finish.

 

 

Рис.5. Выбор вида приложения

 

Не нажимайте сразу клавишу Finish, а с помощью клавиши Next дойдите до шага 4 (Step 4) работы мастера и с помощью кнопки Advanced вызовите окно, в котором можно задать некоторые дополнительные свойства приложения (рис.6).

 

 

 

Рис.6. Окно для задания дополнительных свойств приложения

 

Далее дойдите до последнего, шестого шага работы мастера, на котором можно изменить имена почти всех классов и файлов и, главное, выбрать родительский класс (Base class) для класса вида (рис.7).

 

 

Рис.7. Окно для задания имен классов и файлов и выбора базового класса для класса вид

 

В качестве родительского класса для класса вида можно выбрать любой из классов CView, CFormView, CHtmlView, CListView, CRichEditView, CScrollView или CTreeView. Назначение этих классов будет рассмотрено чуть ниже, а пока завершим работу мастера, выбрав в качестве базового класс CView для окна вид. В результате мы получим такие основные файлы проекта:

SDoc.cpp       – файл с программой, где определен объект CSDocApp::theApp;

MainFrm.cpp  – файл с классом-рамкой окна приложения CMainFrame;

SDocDoc.cpp – файл с определением класса документа CSDocDoc;

SDocView.cpp – файл с определением класса вид CView.

 

В соответствии с принятыми в Microsoft соглашениями, которые нашли свое подкрепление и отражение в атрибутах и методах классов документа и вида (представления), данные предполагается описывать и сохранять в классе документа, а их отображением на экране должен заниматься класс вид.

 










Последнее изменение этой страницы: 2018-04-12; просмотров: 469.

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