Практикум прикладного программирования на MFC и C++ в среде VS.NET

SDI-приложение MFC

Разбить на страницы
Показывать лекцию целиком

Спецификация задачи

Файлы к данной лабораторной работе, Вы можете скачать здесь.
  • При запуске SDI -приложения внутри окна представления в точке x=200, y=100 должен появиться круг радиусом 20 пикселов.
  • Круг перерисовывается с центром в месте щелчка мыши.
  • Можно сохранить круг в файле и считать его на экран.
  • Предусмотреть возможность печати круга на принтер.
  • Основные пункты меню должны дублироваться кнопками на панели инструментов.
  • Пункты меню необходимо русифицировать.
  • Оформить требуемые подcказки в строке состояния главного окна-рамки и всплывающие подсказки.
  • Вывести текст в центре окна документа.
  • Приступим к решению поставленной задачи.

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

  • Командой меню
  • Установите вкладку
  • Состояние остальных вкладок пусть останется установленным по умолчанию, а именно
  • На последней вкладке цифра 4 означает размер списка последних обработанных документов.

  • Просмотрите все вкладки мастера и нажмите кнопку Finish, чтобы мастер создал заготовку приложения
  • В раскрывающемся списке Solution Configurations поменяйте режим работы оболочки с Debug на Release, чтобы отключить генерацию отладочного кода при сборке проекта
  • Запустите заготовку, созданную мастером, на компиляцию и линковку командой Build/Build Solution, а затем на выполнение командой Debug/Start Debugging (F5)
  • Появится полноценное окно Windows с однодокументным интерфейсом ( SDI )

    Конструирование данных класса документа

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

  • Вызовите панель View/Solution Explorer и в дереве файлов проекта двойным щелчком откройте на редактирование заголовочный файл CircleDoc.h
  • Найдите объявление класса CCircleDoc и добавьте в него объявления полей, которые будут хранить координаты центра круга
    Объявления полей в классе документа CCircleDoc 
    class CCircleDoc : public CDocument
    {
    protected: // create from serialization only
        CCircleDoc();
        DECLARE_DYNCREATE(CCircleDoc)
    	
    // Attributes
    public:
        int m_PosX; // координата x (по горизонтали) центра круга
        int m_PosY; // координата y (по вертикали) центра круга
    		
    // Operations
    public:
    	...................................................
    }
  • В объявлении класса язык C++ не допускает инициализацию членов-данных (кроме перечисления enum ). Поэтому инициализацию поместим в функцию-член класса, которая выполняется при создании нового документа. Для этого

  • В раскрывающемся списке Functions in CCircleDoc (сведения о CCircleDoc ) выберите функцию-метод OnNewDocument()
  • Именно функция OnNewDocument() срабатывает при создании нового документа.

  • Внесите в функцию OnNewDocument() код инициализации объявленных координат круга
    Инициализация полей в функции OnNewDocument() класса CCircleDoc
    BOOL CCircleDoc::OnNewDocument()
    {
    	if (!CDocument::OnNewDocument())
    		return FALSE;
    	
    	// TODO: add reinitialization code here
    	// (SDI documents will reuse this document)
    	m_PosX = 200;
    	m_PosY = 100;
    	
    	return TRUE;
    }
  • Конструирование данных класса представления

    Схема "Документ-Представление" реализует отношение "один ко многим", т.е. одни и те же данные класса Документа можно по разному отображать пользователю в классах Представления. В нашем случае будет только одно представление данных Документа. Класс Представления CCircleView будет выполнять непосредственное взаимодействие с пользователем и в нем тоже понадобятся поля для хранения координат центра круга.

  • Через панель Solution Explorer откройте заголовочный файл CircleView.h, найдите объявление класса CCircleView и внесите в него объявления переменных-членов, которые будут хранить координаты центра круга
    Объявления полей в классе представления CCircleView
    class CCircleView : public CView
    {
    protected: // create from serialization only
    	CCircleView();
    	DECLARE_DYNCREATE(CCircleView)
    	
    // Attributes
    public:
    	CCircleDoc* GetDocument() const;
    	int m_PosX; // координата x (по горизонтали) центра круга
    	int m_PosY; // координата y (по вертикали) центра круга
    	
    // Operations
    public:
    	.....................................................
    };
  • Значения полей, хранящих координаты центра круга, в классе Документа и в классе Представления всегда должны совпадать, поэтому удобнее назначить им одинаковые имена. Обратите внимание на то, что здесь уже имеется прототип функции GetDocument(), которая будет возвращать указатель на объект класса CCircleDoc, чтобы можно было вызывать его члены . Эта функция нам понадобится дальше, чтобы адресоваться из класса-представления CCircleView к данным, хранящимся в документе, представленном классом CCircleDoc. Мастер уже создал и реализацию функции GetDocument(), которая имеет вид

    Реализация функции GetDocument()
    CCircleDoc* CCircleView::GetDocument() const // non-debug version is inline
    {
    	ASSERT(m_pDocument->IsKindOf(RUNTIME_CLASS(CCircleDoc)));
    	return (CCircleDoc*)m_pDocument;
    }

    Инициализацию объявленных в классе представления CCircleView переменных поместим в функцию-член OnInitialUpdate() класса представления, которая выполняется один раз при первом отображении документа на экране. В ней мы предусмотрим начальную перекачку данных из класса Документа в класс Представления. Для этого

  • Откройте панель
  • В панели Properties нажмем кнопку Overriders и в появившемся списке виртуальных функций класса CCircleView, унаследованных от базового класса CView, выделите функцию OnInitialUpdate()
  • В появившемся раскрывающемся списке выполните команду Add, чтобы мастер добавил в файл CCircleView.cpp заготовку для переопределения виртуальной функции OnInitialUpdate()
  • Дополните функцию OnInitialUpdate() кодом, копирующим содержимое переменных из объекта Документ в объект Представление
    Копирование координат центра круга из Документа в Представление
    void CCircleView::OnInitialUpdate()
    {
    	CView::OnInitialUpdate();
    	
    	// TODO: Add your specialized code here and/or call the base class
    	m_PosX = GetDocument()->m_PosX;
    	m_PosY = GetDocument()->m_PosY;
    }
  • Отображение круга на экране

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

  • В панели Solution Explorer откройте файл CircleView.cpp и через раскрывающийся список в верхней части рабочей области редактора позиционируйтесь на метод OnDraw()
  • Освободите от комментариев указатель на контекст устройства в аргументах метода OnDraw() и дополните тело кодом рисования круга
    Код рисования круга в объекте Документа
    void CCircleView::OnDraw(CDC* pDC)
    {
    	CCircleDoc* pDoc = GetDocument();
    	ASSERT_VALID(pDoc);
    	if (!pDoc)
    		return;
    	
    	// TODO: add draw code for native data here
    	
    	// Задать границы описывающего прямоугольника, 
    	// в котором будет нарисован круг
    	CRect rect;
    	rect.left = m_PosX - 20;
    	rect.top = m_PosY - 20;
    	rect.right = m_PosX + 20;
    	rect.bottom = m_PosY + 20;
    		
    	// Нарисовать круг на контексте устройства
    	pDC->Ellipse(rect);
    }
  • Постройте приложение и убедитесь, что получился примерно такой результат
  • Рисование круга в произвольной точке экрана

    Результатом предыдущих действий было появление круга в фиксированной точке с программно заданными координатами x=200, y=100. Сделаем так, чтобы по щелчку мыши в произвольном месте клиентской области окна, представленного объектом CCircleView, круг перерисовывался с центром в месте щелчка.

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

  • В панели Class View вызовите контекстное меню для класса CCircleView и выполните команду Properties для вызова одноименной панели
  • Щелкните на пиктограмме Messages в верхней части панели Properties, найдите событие WM_LBUTTONDOWN, которое генерируется операционной системой всякий раз при щелчке по объекту класса представления CCircleView, и в раскрывающемся списке добавьте обработчик OnLButtonDown()

    Оболочка сразу внесет необходимые изменения в файлы CCircleView.h и CCircleView.cpp: добавит записи в объявление класса и таблицу сообщений, а также создаст заготовку обработчика. Эти автоматически созданные изменения будут следующими

    Объявление обработчика в файле CCircleView.h
    class CCircleView : public CView
    {
    	...............................................................
    // Generated message map functions
    protected:
    	DECLARE_MESSAGE_MAP()
    public:
    	virtual void OnInitialUpdate();
    public:
    	afx_msg void OnLButtonDown(UINT nFlags, CPoint point);
    };
    Регистрация события в таблице сообщений класса CircleView
    BEGIN_MESSAGE_MAP(CCircleView, CView)
    	// Standard printing commands
    	ON_COMMAND(ID_FILE_PRINT, CView::OnFilePrint)
    	ON_COMMAND(ID_FILE_PRINT_DIRECT, CView::OnFilePrint)
    	ON_COMMAND(ID_FILE_PRINT_PREVIEW, CView::OnFilePrintPreview)
    	ON_WM_LBUTTONDOWN()
    END_MESSAGE_MAP()
    Заготовка обработчика в файле CircleView.cpp
    void CCircleView::OnLButtonDown(UINT nFlags, CPoint point)
    {
    	// TODO: Add your message handler code here and/or call default
    	
    	CView::OnLButtonDown(nFlags, point);
    }
  • Дополните обработчик OnLButtonDown() класса CCircleView следующим кодом
    Код обработчика щелчка мыши на Представлении
    void CCircleView::OnLButtonDown(UINT nFlags, CPoint point)
    {
    	// TODO: Add your message handler code here and/or call default
    	
    	// Обновить координаты центра круга
    	// координатами места щелчка курсора 
    	m_PosX = point.x;
    	m_PosY = point.y;
    	
    	// Инициировать перерисовку экрана вызовом функции OnDraw()
    	Invalidate();
    	
    	// Передать новые координаты щелчка в класс CCircleDoc 
    	CCircleDoc* pDoc = GetDocument();
    	pDoc->m_PosX = m_PosX;			// Через промежуточный указатель
    	GetDocument()->m_PosY = m_PosY; // Напрямую
    	
    	// Установить флажок изменения Документа
    	pDoc->SetModifiedFlag(TRUE);
    	
    	CView::OnLButtonDown(nFlags, point);	// Выполнить базовый метод
    }
  • Постройте приложение и убедитесь, что круг перерисовывается по каждому щелчку мыши на новом месте
  • Сохранение и загрузка данных Документа

    Напишем код, который будет отвечать за сохранение Документа в файле и загрузку ранее сохраненных Документов. Этот процесс основан на функции Serialize(), которая обеспечивает сериализацию (десериализацию) данных при их сохранении на диске и вызывается автоматически, когда вибирается пункт меню Save, Save As, Open или закрепленные за ними кнопки на панели инструментов. Сериализация и десериализация - это специальные методы упаковки/распаковки данных, чтобы они однозначно восстанавливались в соответствующие значения переменных класса документа.

  • панели Class View выделите класс CCircleDoc двойным щелчком мыши
  • В результате таких действий откроется файл CircleDoc.h в редакторе кода, курсор станет на имени класса в его объявлении и вверху появятся два раскрывающихся списка: в левом будет имя класса, а в правом - список его функций-членов.

  • Раскройте правый список методов класса и двойным щелчком мыши позиционируйтесь на методе Serialize()
  • В результате откроется файл CircleDoc.cpp, где находится этот метод, и курсор установится на реализации метода.

    Заготовка функции Serialize() выглядит так

    Заготовка функции Serialize() в файле CircleDoc.cpp
    void CCircleDoc::Serialize(CArchive ar)
    {
    	if (ar.IsStoring())
    	{
    		// TODO: add storing code here
    		// Сюда добавить код сохранения
    	}
    	else
    	{
    		// TODO: add loading code here
    		// Сюда добавить код загрузки
    	}
    }

    Ссылка ar соответствует потоку (открытому каналу связи), связанному с архивом (файлом), открытым на запись или чтение. Заготовка пока ничего не читает и ничего не сохраняет.

  • Модифицируйте код заготовки метода Serialize() следующим образом
    Код функции Serialize() в файле CircleDoc.cpp
    void CCircleDoc::Serialize(CArchive ar)
    {
    	if (ar.IsStoring())
    	{
    		// TODO: add storing code here
    		// Сюда добавить код сохранения
    		ar << m_PosX << m_PosY;	}
    	else
    	{
    		// TODO: add loading code here
    		// Сюда добавить код загрузки
    		ar >> m_PosX >> m_PosY;
    	}
    }
  • Существует строгое правило сериализации и десериализации: последовательность данных, перечисленных для сохранения, должна строго соответствовать данным, перечисленным для загрузки

    Изменение заголовка окна приложения и назначение фильтра

  • Выполните команду
  • В верхней части таблицы редактора ресурсов имеется строка с идентификатором IDR_MAINFRAME

    Значением этой строки является

    Circle\n\nCircle\n\n\nCircle.Document\nCircle.Document

    Символы \n служат разделителями 7 разделов, некоторые из которых обозначают следующее

  • Circle - определяет заголовок окна приложения
  • Null
  • Circle
  • Null - определяет наименование фильтра в строке Files of type диалогового окна Save As или Open
  • Null - определяет фильтр в строке File name диалогового окна Save As или Open
  • Circle.Document
  • Circle.Document
  • Измените прямо в таблице или через панель Properties содержимое разделов 1, 4 и 5 как показано ниже
  • 1.	Программа рисования окружности - лабораторная работа 
    4.	CIR Files (*.cir) 
    5.	.cir

    Новое значение строки должно быть таким

    Программа рисования окружности - лабораторная работа\n\nCircle\nCIR Files (*.cir)\n.cir\nCircle.Document\nCircle.Document
  • Постройте приложение и выполните программу. Должно получиться окно со следующим заголовком
  • Замечание. Возможно, что Ваш проект не настроен на корректное отображение русского языка и может получиться нечто следующее

    В этом случае нужно выполнить такие действия:

  • При открытом файле ресурсов в панели редактирования ресурсов для узла String Table вызвать контектсное меню Properties
  • В выпадающем списке Language выбрать язык Русский
  • Вернуться к таблице String Table со строкой заголовка, которая может оказаться испорченной после компиляции, и вновь восстановить строку заголовка
  • Сохранить измененный ресурс и выполнить программу
  • После таких действий должно получиться окно с нормальным заголовком.

    При начальной загрузке документа он безымянный, поэтому имеет в заголовке Untitled. При каждом новом выполнении команды New меню File генерируется безымянный документ. Щелчок мыши в клиентской области окна устанавливается флаг модификации документа в обработчике OnLButtonDown(), поэтому при закрытии документа должно выдаваться стандартное предупреждение

    Наши изменения строки ресурсов заметны не только в заголовке окна приложения. Команды File/Save As и File/Open приложения вызывают стандартные диалоговые окна с назначенным нами фильтром

    Изменение иконки приложения

    Среда проектирования Visual Studio .NET 2005 при создании нового MFC -проекта генерирует стандартную иконку, которая имеет имя, совпадающее с именем приложения Circle.ico и размещается в папке res каталога проекта. На эту иконку ссылается идентификатор IDR_MAINFRAME. Чтобы убедиться в этом

  • Через панель Solution Explorer раскройте узел Resource Files и для файла Circle.rc выполните команду View Code контекстного меню
  • Мы видим, что в секции Icon содержится код привязки файла иконки к идентификатору ресурсов

    Секция Icon в файле ресурсов Circle.rc
    /////////////////////////////////////////////////////////////////////////////
    //
    // Icon
    //
    
    // Icon with lowest ID value placed first to ensure application icon
    // remains consistent on all systems.
    IDR_MAINFRAME           ICON                    "res\\Circle.ico"
    IDR_CircleTYPE          ICON                    "res\\CircleDoc.ico"

    Заменить иконку главного окна приложения можно несколькими способами, например:

  • Физически скопировать файл нужной иконки в каталог res проекта приложения и заменить его имя на имя проекта Circle.ico
  • Физически скопировать файл нужной иконки в каталог res проекта приложения и зарегистрировать ее в файле Circle.rc
  • Без физического копирования установить связь с нужной иконкой и присвоить ей идентификатор Через Проводник
  • В панели
  • В появившемся диалоговом окне установите тип ресурса Icon и выполните кнопку Import, чтобы зарегистрировать в ресурсах нужный файл иконки

    Оболочка присвоит добавленному ресурсу идентификатор по умолчанию IDI_ICON1 и сделает запись в файле ресурсов

    Секция Icon в файле ресурсов Circle.rc 
    /////////////////////////////////////////////////////////////////////////////
    //
    // Icon
    //
    	
    // Icon with lowest ID value placed first to ensure application icon
    // remains consistent on all systems.
    IDR_MAINFRAME           ICON                    "res\\Circle.ico"
    IDR_CircleTYPE          ICON                    "res\\CircleDoc.ico"
    IDI_ICON1               ICON                    "res\\2.ICO
  • В панели Resource View выделите ресурс старой иконки с идентификатором IDR_MAINFRAME и через контекстное меню или клавишей Delete отсоедините его от проекта
  • В панели Resource View выделите ресурс IDI_ICON1 новой иконки и через панель Properties измените его имя на IDR_MAINFRAME
  • Постройте приложение и убедитесь в появлении новой иконки
  • Русификация меню

    Русифицируем меню приложения и удалим ненужные кнопки и пункты. Это мы сделаем для иллюстрации возможностей среды, а вообще-то английские названия данного приложения уместны и понятны.

  • В панели Resource View раскройте узел Menu и выделите ресурс IDR_MAINFRAME
  • Раскройте панель Properties и установите Language=Русский (если он не установлен)
  • В панели Resource View дважды щелкните по ресурсу IDR_MAINFRAME, чтобы открыть редактор меню
  • Вызовите и закрепите на экране панель Properties, которая отражает стандартные свойства каждого пункта меню
  • Последовательно выделяя пункты меню, замените значение свойства Caption (название пункта) и Prompt (длинная подсказка в строке состояния и всплывающая подсказка, отделенные символом новой строки) на русское содержимое, соблюдая синтаксис
  • Меню верхнего уровня Edit в нашем проекте не используется, как и соответствующие кнопки панели инструментов, поэтому удалите их
  • Панель инструментов на редактирование можно вызвать двойным щелчком на идентификаторе IDR_MAINFRAME в папке Toolbar панели Resource View, а ненужные пиктограммы для удаления просто стаскивать мышью с этой панели.

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

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

    Дополним нашу программу выводом текста в центре окна представления документа, которое является клиентской областью окна-рамки всего приложения.

  • Введите в класс документа объявление новой переменной m_strText

    Для быстрого позиционирования вызовите панель Class View и дважды кликните на классе CCircleDoc. В рабочей области откроется файл CircleDoc.h в режиме редактирования. Код объявления переменной класса будет таким

    Объявление новой переменной в файле CircleDoc.h
    class CCircleDoc : public CDocument
    {
    protected: // create from serialization only
        CCircleDoc();
        DECLARE_DYNCREATE(CCircleDoc)
    	
    // Attributes
    public:
        int m_PosX; // координата x (по горизонтали) центра круга
        int m_PosY; // координата y (по вертикали) центра круга
        CString m_strText; // для хранения текста
        ........................................
    };
  • В конструктор класса введите инициализацию текстовой переменной. Для этого в правом верхнем раскрывающемся списке выберите имя конструктора CCircleDoc, которое совпадает с именем класса Документа. Откроется файл CCircleDoc.cpp в режиме редактирования, в который внесите свое изменение

    Файл CircleDoc.cpp - инициализация новой переменной
    CCircleDoc::CCircleDoc()  // конструктор класса
    {
    	// TODO: add one-time construction code here
    	m_strText = "Привет студентам от Снеткова В.М.!!!";
    }
  • В файле CircleView.cpp выберите функцию OnDraw() и поместите в нее код вывода текста в центр клиентской области приложения. Для этого нужно определить
  • ширину и высоту клиентской области
  • ширину и высоту текста
  • вычислить начальную позицию для вывода текста
  • вывести сам текст
    Рисование текста в функции OnDraw() файла CircleView.cpp
    void CCircleView::OnDraw(CDC* pDC)
    {
    	CCircleDoc* pDoc = GetDocument();
    	ASSERT_VALID(pDoc);
    	if (!pDoc)
    		return;
    
    	// TODO: add draw code for native data here
    
    	// Задать границы прямоугольника, в которых
    	// будет нарисован круг
    	CRect rect;
    	rect.left = m_PosX - 20;
    	rect.top = m_PosY - 20;
    	rect.right = m_PosX + 20;
    	rect.bottom = m_PosY + 20;
    	
    	// Нарисовать круг
    	pDC->Ellipse(rect);
    
    // Вывод текста в центре клиентской области окна-рамки
        GetWindowRect(rect); // определение размеров клиентской области
        CSize size = pDC->GetTextExtent(pDoc->m_strText); // размеры текста
        int width = rect.right - rect.left; // ширина клиентской области
        int height = rect.bottom - rect.top;// высота клиентской области
        int x = (width - size.cx) / 2; // позиция вывода по ширине
        int y = (height - size.cy) / 2; // позиция вывода по высоте
    	
        pDC->TextOut(x, y, pDoc->m_strText); // вывод строки с текстом
    }
  • Запустите приложение и получите окончательный результат
  • Выводы

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

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

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

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

    На экране сосуществуют текст и графика потому, что все перед выводом преобразуется в растровую графику.

    Страницы:

    Спецификация задачи

    Файлы к данной лабораторной работе, Вы можете скачать здесь.
  • При запуске SDI -приложения внутри окна представления в точке x=200, y=100 должен появиться круг радиусом 20 пикселов.
  • Круг перерисовывается с центром в месте щелчка мыши.
  • Можно сохранить круг в файле и считать его на экран.
  • Предусмотреть возможность печати круга на принтер.
  • Основные пункты меню должны дублироваться кнопками на панели инструментов.
  • Пункты меню необходимо русифицировать.
  • Оформить требуемые подcказки в строке состояния главного окна-рамки и всплывающие подсказки.
  • Вывести текст в центре окна документа.
  • Приступим к решению поставленной задачи.

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

  • Командой меню
  • Установите вкладку
  • Состояние остальных вкладок пусть останется установленным по умолчанию, а именно
  • На последней вкладке цифра 4 означает размер списка последних обработанных документов.

  • Просмотрите все вкладки мастера и нажмите кнопку Finish, чтобы мастер создал заготовку приложения
  • В раскрывающемся списке Solution Configurations поменяйте режим работы оболочки с Debug на Release, чтобы отключить генерацию отладочного кода при сборке проекта
  • Запустите заготовку, созданную мастером, на компиляцию и линковку командой Build/Build Solution, а затем на выполнение командой Debug/Start Debugging (F5)
  • Появится полноценное окно Windows с однодокументным интерфейсом ( SDI )

    Конструирование данных класса документа

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

  • Вызовите панель View/Solution Explorer и в дереве файлов проекта двойным щелчком откройте на редактирование заголовочный файл CircleDoc.h
  • Найдите объявление класса CCircleDoc и добавьте в него объявления полей, которые будут хранить координаты центра круга
    Объявления полей в классе документа CCircleDoc 
    class CCircleDoc : public CDocument
    {
    protected: // create from serialization only
        CCircleDoc();
        DECLARE_DYNCREATE(CCircleDoc)
    	
    // Attributes
    public:
        int m_PosX; // координата x (по горизонтали) центра круга
        int m_PosY; // координата y (по вертикали) центра круга
    		
    // Operations
    public:
    	...................................................
    }
  • В объявлении класса язык C++ не допускает инициализацию членов-данных (кроме перечисления enum ). Поэтому инициализацию поместим в функцию-член класса, которая выполняется при создании нового документа. Для этого

  • В раскрывающемся списке Functions in CCircleDoc (сведения о CCircleDoc ) выберите функцию-метод OnNewDocument()
  • Именно функция OnNewDocument() срабатывает при создании нового документа.

  • Внесите в функцию OnNewDocument() код инициализации объявленных координат круга
    Инициализация полей в функции OnNewDocument() класса CCircleDoc
    BOOL CCircleDoc::OnNewDocument()
    {
    	if (!CDocument::OnNewDocument())
    		return FALSE;
    	
    	// TODO: add reinitialization code here
    	// (SDI documents will reuse this document)
    	m_PosX = 200;
    	m_PosY = 100;
    	
    	return TRUE;
    }
  • Конструирование данных класса представления

    Схема "Документ-Представление" реализует отношение "один ко многим", т.е. одни и те же данные класса Документа можно по разному отображать пользователю в классах Представления. В нашем случае будет только одно представление данных Документа. Класс Представления CCircleView будет выполнять непосредственное взаимодействие с пользователем и в нем тоже понадобятся поля для хранения координат центра круга.

  • Через панель Solution Explorer откройте заголовочный файл CircleView.h, найдите объявление класса CCircleView и внесите в него объявления переменных-членов, которые будут хранить координаты центра круга
    Объявления полей в классе представления CCircleView
    class CCircleView : public CView
    {
    protected: // create from serialization only
    	CCircleView();
    	DECLARE_DYNCREATE(CCircleView)
    	
    // Attributes
    public:
    	CCircleDoc* GetDocument() const;
    	int m_PosX; // координата x (по горизонтали) центра круга
    	int m_PosY; // координата y (по вертикали) центра круга
    	
    // Operations
    public:
    	.....................................................
    };
  • Значения полей, хранящих координаты центра круга, в классе Документа и в классе Представления всегда должны совпадать, поэтому удобнее назначить им одинаковые имена. Обратите внимание на то, что здесь уже имеется прототип функции GetDocument(), которая будет возвращать указатель на объект класса CCircleDoc, чтобы можно было вызывать его члены . Эта функция нам понадобится дальше, чтобы адресоваться из класса-представления CCircleView к данным, хранящимся в документе, представленном классом CCircleDoc. Мастер уже создал и реализацию функции GetDocument(), которая имеет вид

    Реализация функции GetDocument()
    CCircleDoc* CCircleView::GetDocument() const // non-debug version is inline
    {
    	ASSERT(m_pDocument->IsKindOf(RUNTIME_CLASS(CCircleDoc)));
    	return (CCircleDoc*)m_pDocument;
    }

    Инициализацию объявленных в классе представления CCircleView переменных поместим в функцию-член OnInitialUpdate() класса представления, которая выполняется один раз при первом отображении документа на экране. В ней мы предусмотрим начальную перекачку данных из класса Документа в класс Представления. Для этого

  • Откройте панель
  • В панели Properties нажмем кнопку Overriders и в появившемся списке виртуальных функций класса CCircleView, унаследованных от базового класса CView, выделите функцию OnInitialUpdate()
  • В появившемся раскрывающемся списке выполните команду Add, чтобы мастер добавил в файл CCircleView.cpp заготовку для переопределения виртуальной функции OnInitialUpdate()
  • Дополните функцию OnInitialUpdate() кодом, копирующим содержимое переменных из объекта Документ в объект Представление
    Копирование координат центра круга из Документа в Представление
    void CCircleView::OnInitialUpdate()
    {
    	CView::OnInitialUpdate();
    	
    	// TODO: Add your specialized code here and/or call the base class
    	m_PosX = GetDocument()->m_PosX;
    	m_PosY = GetDocument()->m_PosY;
    }
  • Отображение круга на экране

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

  • В панели Solution Explorer откройте файл CircleView.cpp и через раскрывающийся список в верхней части рабочей области редактора позиционируйтесь на метод OnDraw()
  • Освободите от комментариев указатель на контекст устройства в аргументах метода OnDraw() и дополните тело кодом рисования круга
    Код рисования круга в объекте Документа
    void CCircleView::OnDraw(CDC* pDC)
    {
    	CCircleDoc* pDoc = GetDocument();
    	ASSERT_VALID(pDoc);
    	if (!pDoc)
    		return;
    	
    	// TODO: add draw code for native data here
    	
    	// Задать границы описывающего прямоугольника, 
    	// в котором будет нарисован круг
    	CRect rect;
    	rect.left = m_PosX - 20;
    	rect.top = m_PosY - 20;
    	rect.right = m_PosX + 20;
    	rect.bottom = m_PosY + 20;
    		
    	// Нарисовать круг на контексте устройства
    	pDC->Ellipse(rect);
    }
  • Постройте приложение и убедитесь, что получился примерно такой результат
  • Рисование круга в произвольной точке экрана

    Результатом предыдущих действий было появление круга в фиксированной точке с программно заданными координатами x=200, y=100. Сделаем так, чтобы по щелчку мыши в произвольном месте клиентской области окна, представленного объектом CCircleView, круг перерисовывался с центром в месте щелчка.

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

  • В панели Class View вызовите контекстное меню для класса CCircleView и выполните команду Properties для вызова одноименной панели
  • Щелкните на пиктограмме Messages в верхней части панели Properties, найдите событие WM_LBUTTONDOWN, которое генерируется операционной системой всякий раз при щелчке по объекту класса представления CCircleView, и в раскрывающемся списке добавьте обработчик OnLButtonDown()

    Оболочка сразу внесет необходимые изменения в файлы CCircleView.h и CCircleView.cpp: добавит записи в объявление класса и таблицу сообщений, а также создаст заготовку обработчика. Эти автоматически созданные изменения будут следующими

    Объявление обработчика в файле CCircleView.h
    class CCircleView : public CView
    {
    	...............................................................
    // Generated message map functions
    protected:
    	DECLARE_MESSAGE_MAP()
    public:
    	virtual void OnInitialUpdate();
    public:
    	afx_msg void OnLButtonDown(UINT nFlags, CPoint point);
    };
    Регистрация события в таблице сообщений класса CircleView
    BEGIN_MESSAGE_MAP(CCircleView, CView)
    	// Standard printing commands
    	ON_COMMAND(ID_FILE_PRINT, CView::OnFilePrint)
    	ON_COMMAND(ID_FILE_PRINT_DIRECT, CView::OnFilePrint)
    	ON_COMMAND(ID_FILE_PRINT_PREVIEW, CView::OnFilePrintPreview)
    	ON_WM_LBUTTONDOWN()
    END_MESSAGE_MAP()
    Заготовка обработчика в файле CircleView.cpp
    void CCircleView::OnLButtonDown(UINT nFlags, CPoint point)
    {
    	// TODO: Add your message handler code here and/or call default
    	
    	CView::OnLButtonDown(nFlags, point);
    }
  • Дополните обработчик OnLButtonDown() класса CCircleView следующим кодом
    Код обработчика щелчка мыши на Представлении
    void CCircleView::OnLButtonDown(UINT nFlags, CPoint point)
    {
    	// TODO: Add your message handler code here and/or call default
    	
    	// Обновить координаты центра круга
    	// координатами места щелчка курсора 
    	m_PosX = point.x;
    	m_PosY = point.y;
    	
    	// Инициировать перерисовку экрана вызовом функции OnDraw()
    	Invalidate();
    	
    	// Передать новые координаты щелчка в класс CCircleDoc 
    	CCircleDoc* pDoc = GetDocument();
    	pDoc->m_PosX = m_PosX;			// Через промежуточный указатель
    	GetDocument()->m_PosY = m_PosY; // Напрямую
    	
    	// Установить флажок изменения Документа
    	pDoc->SetModifiedFlag(TRUE);
    	
    	CView::OnLButtonDown(nFlags, point);	// Выполнить базовый метод
    }
  • Постройте приложение и убедитесь, что круг перерисовывается по каждому щелчку мыши на новом месте
  • Сохранение и загрузка данных Документа

    Напишем код, который будет отвечать за сохранение Документа в файле и загрузку ранее сохраненных Документов. Этот процесс основан на функции Serialize(), которая обеспечивает сериализацию (десериализацию) данных при их сохранении на диске и вызывается автоматически, когда вибирается пункт меню Save, Save As, Open или закрепленные за ними кнопки на панели инструментов. Сериализация и десериализация - это специальные методы упаковки/распаковки данных, чтобы они однозначно восстанавливались в соответствующие значения переменных класса документа.

  • панели Class View выделите класс CCircleDoc двойным щелчком мыши
  • В результате таких действий откроется файл CircleDoc.h в редакторе кода, курсор станет на имени класса в его объявлении и вверху появятся два раскрывающихся списка: в левом будет имя класса, а в правом - список его функций-членов.

  • Раскройте правый список методов класса и двойным щелчком мыши позиционируйтесь на методе Serialize()
  • В результате откроется файл CircleDoc.cpp, где находится этот метод, и курсор установится на реализации метода.

    Заготовка функции Serialize() выглядит так

    Заготовка функции Serialize() в файле CircleDoc.cpp
    void CCircleDoc::Serialize(CArchive ar)
    {
    	if (ar.IsStoring())
    	{
    		// TODO: add storing code here
    		// Сюда добавить код сохранения
    	}
    	else
    	{
    		// TODO: add loading code here
    		// Сюда добавить код загрузки
    	}
    }

    Ссылка ar соответствует потоку (открытому каналу связи), связанному с архивом (файлом), открытым на запись или чтение. Заготовка пока ничего не читает и ничего не сохраняет.

  • Модифицируйте код заготовки метода Serialize() следующим образом
    Код функции Serialize() в файле CircleDoc.cpp
    void CCircleDoc::Serialize(CArchive ar)
    {
    	if (ar.IsStoring())
    	{
    		// TODO: add storing code here
    		// Сюда добавить код сохранения
    		ar << m_PosX << m_PosY;	}
    	else
    	{
    		// TODO: add loading code here
    		// Сюда добавить код загрузки
    		ar >> m_PosX >> m_PosY;
    	}
    }
  • Существует строгое правило сериализации и десериализации: последовательность данных, перечисленных для сохранения, должна строго соответствовать данным, перечисленным для загрузки

    Изменение заголовка окна приложения и назначение фильтра

  • Выполните команду
  • В верхней части таблицы редактора ресурсов имеется строка с идентификатором IDR_MAINFRAME

    Значением этой строки является

    Circle\n\nCircle\n\n\nCircle.Document\nCircle.Document

    Символы \n служат разделителями 7 разделов, некоторые из которых обозначают следующее

  • Circle - определяет заголовок окна приложения
  • Null
  • Circle
  • Null - определяет наименование фильтра в строке Files of type диалогового окна Save As или Open
  • Null - определяет фильтр в строке File name диалогового окна Save As или Open
  • Circle.Document
  • Circle.Document
  • Измените прямо в таблице или через панель Properties содержимое разделов 1, 4 и 5 как показано ниже
  • 1.	Программа рисования окружности - лабораторная работа 
    4.	CIR Files (*.cir) 
    5.	.cir

    Новое значение строки должно быть таким

    Программа рисования окружности - лабораторная работа\n\nCircle\nCIR Files (*.cir)\n.cir\nCircle.Document\nCircle.Document
  • Постройте приложение и выполните программу. Должно получиться окно со следующим заголовком
  • Замечание. Возможно, что Ваш проект не настроен на корректное отображение русского языка и может получиться нечто следующее

    В этом случае нужно выполнить такие действия:

  • При открытом файле ресурсов в панели редактирования ресурсов для узла String Table вызвать контектсное меню Properties
  • В выпадающем списке Language выбрать язык Русский
  • Вернуться к таблице String Table со строкой заголовка, которая может оказаться испорченной после компиляции, и вновь восстановить строку заголовка
  • Сохранить измененный ресурс и выполнить программу
  • После таких действий должно получиться окно с нормальным заголовком.

    При начальной загрузке документа он безымянный, поэтому имеет в заголовке Untitled. При каждом новом выполнении команды New меню File генерируется безымянный документ. Щелчок мыши в клиентской области окна устанавливается флаг модификации документа в обработчике OnLButtonDown(), поэтому при закрытии документа должно выдаваться стандартное предупреждение

    Наши изменения строки ресурсов заметны не только в заголовке окна приложения. Команды File/Save As и File/Open приложения вызывают стандартные диалоговые окна с назначенным нами фильтром

    Изменение иконки приложения

    Среда проектирования Visual Studio .NET 2005 при создании нового MFC -проекта генерирует стандартную иконку, которая имеет имя, совпадающее с именем приложения Circle.ico и размещается в папке res каталога проекта. На эту иконку ссылается идентификатор IDR_MAINFRAME. Чтобы убедиться в этом

  • Через панель Solution Explorer раскройте узел Resource Files и для файла Circle.rc выполните команду View Code контекстного меню
  • Мы видим, что в секции Icon содержится код привязки файла иконки к идентификатору ресурсов

    Секция Icon в файле ресурсов Circle.rc
    /////////////////////////////////////////////////////////////////////////////
    //
    // Icon
    //
    
    // Icon with lowest ID value placed first to ensure application icon
    // remains consistent on all systems.
    IDR_MAINFRAME           ICON                    "res\\Circle.ico"
    IDR_CircleTYPE          ICON                    "res\\CircleDoc.ico"

    Заменить иконку главного окна приложения можно несколькими способами, например:

  • Физически скопировать файл нужной иконки в каталог res проекта приложения и заменить его имя на имя проекта Circle.ico
  • Физически скопировать файл нужной иконки в каталог res проекта приложения и зарегистрировать ее в файле Circle.rc
  • Без физического копирования установить связь с нужной иконкой и присвоить ей идентификатор Через Проводник
  • В панели
  • В появившемся диалоговом окне установите тип ресурса Icon и выполните кнопку Import, чтобы зарегистрировать в ресурсах нужный файл иконки

    Оболочка присвоит добавленному ресурсу идентификатор по умолчанию IDI_ICON1 и сделает запись в файле ресурсов

    Секция Icon в файле ресурсов Circle.rc 
    /////////////////////////////////////////////////////////////////////////////
    //
    // Icon
    //
    	
    // Icon with lowest ID value placed first to ensure application icon
    // remains consistent on all systems.
    IDR_MAINFRAME           ICON                    "res\\Circle.ico"
    IDR_CircleTYPE          ICON                    "res\\CircleDoc.ico"
    IDI_ICON1               ICON                    "res\\2.ICO
  • В панели Resource View выделите ресурс старой иконки с идентификатором IDR_MAINFRAME и через контекстное меню или клавишей Delete отсоедините его от проекта
  • В панели Resource View выделите ресурс IDI_ICON1 новой иконки и через панель Properties измените его имя на IDR_MAINFRAME
  • Постройте приложение и убедитесь в появлении новой иконки
  • Русификация меню

    Русифицируем меню приложения и удалим ненужные кнопки и пункты. Это мы сделаем для иллюстрации возможностей среды, а вообще-то английские названия данного приложения уместны и понятны.

  • В панели Resource View раскройте узел Menu и выделите ресурс IDR_MAINFRAME
  • Раскройте панель Properties и установите Language=Русский (если он не установлен)
  • В панели Resource View дважды щелкните по ресурсу IDR_MAINFRAME, чтобы открыть редактор меню
  • Вызовите и закрепите на экране панель Properties, которая отражает стандартные свойства каждого пункта меню
  • Последовательно выделяя пункты меню, замените значение свойства Caption (название пункта) и Prompt (длинная подсказка в строке состояния и всплывающая подсказка, отделенные символом новой строки) на русское содержимое, соблюдая синтаксис
  • Меню верхнего уровня Edit в нашем проекте не используется, как и соответствующие кнопки панели инструментов, поэтому удалите их
  • Панель инструментов на редактирование можно вызвать двойным щелчком на идентификаторе IDR_MAINFRAME в папке Toolbar панели Resource View, а ненужные пиктограммы для удаления просто стаскивать мышью с этой панели.

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

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

    Дополним нашу программу выводом текста в центре окна представления документа, которое является клиентской областью окна-рамки всего приложения.

  • Введите в класс документа объявление новой переменной m_strText

    Для быстрого позиционирования вызовите панель Class View и дважды кликните на классе CCircleDoc. В рабочей области откроется файл CircleDoc.h в режиме редактирования. Код объявления переменной класса будет таким

    Объявление новой переменной в файле CircleDoc.h
    class CCircleDoc : public CDocument
    {
    protected: // create from serialization only
        CCircleDoc();
        DECLARE_DYNCREATE(CCircleDoc)
    	
    // Attributes
    public:
        int m_PosX; // координата x (по горизонтали) центра круга
        int m_PosY; // координата y (по вертикали) центра круга
        CString m_strText; // для хранения текста
        ........................................
    };
  • В конструктор класса введите инициализацию текстовой переменной. Для этого в правом верхнем раскрывающемся списке выберите имя конструктора CCircleDoc, которое совпадает с именем класса Документа. Откроется файл CCircleDoc.cpp в режиме редактирования, в который внесите свое изменение

    Файл CircleDoc.cpp - инициализация новой переменной
    CCircleDoc::CCircleDoc()  // конструктор класса
    {
    	// TODO: add one-time construction code here
    	m_strText = "Привет студентам от Снеткова В.М.!!!";
    }
  • В файле CircleView.cpp выберите функцию OnDraw() и поместите в нее код вывода текста в центр клиентской области приложения. Для этого нужно определить
  • ширину и высоту клиентской области
  • ширину и высоту текста
  • вычислить начальную позицию для вывода текста
  • вывести сам текст
    Рисование текста в функции OnDraw() файла CircleView.cpp
    void CCircleView::OnDraw(CDC* pDC)
    {
    	CCircleDoc* pDoc = GetDocument();
    	ASSERT_VALID(pDoc);
    	if (!pDoc)
    		return;
    
    	// TODO: add draw code for native data here
    
    	// Задать границы прямоугольника, в которых
    	// будет нарисован круг
    	CRect rect;
    	rect.left = m_PosX - 20;
    	rect.top = m_PosY - 20;
    	rect.right = m_PosX + 20;
    	rect.bottom = m_PosY + 20;
    	
    	// Нарисовать круг
    	pDC->Ellipse(rect);
    
    // Вывод текста в центре клиентской области окна-рамки
        GetWindowRect(rect); // определение размеров клиентской области
        CSize size = pDC->GetTextExtent(pDoc->m_strText); // размеры текста
        int width = rect.right - rect.left; // ширина клиентской области
        int height = rect.bottom - rect.top;// высота клиентской области
        int x = (width - size.cx) / 2; // позиция вывода по ширине
        int y = (height - size.cy) / 2; // позиция вывода по высоте
    	
        pDC->TextOut(x, y, pDoc->m_strText); // вывод строки с текстом
    }
  • Запустите приложение и получите окончательный результат
  • Выводы

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

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

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

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

    На экране сосуществуют текст и графика потому, что все перед выводом преобразуется в растровую графику.

    Вернуться к учебному плану