Задание 1. Разработка программы редактирования файлов специализированного формата.
Исходные данные:
- Программа является оконным Windows32 приложением.
- Программа написана на языке Си++ в среде Visual C++ 6.0, VS.NET или VS2005
- При написании программы используются функции, классы и структуры MFC и Win API32(если понадобиться). Никаких других технологий или «надстроек» не должно быть использовано.
Требования к внешнему виду программы:
- Типовой рекомендуемый внешний вид приложения:
- Основное назначение программы:
- отображение BMP файлов в двух форматах по выбору: как рисунок и как информация о рисунке (реальный размер);
- в случае просмотра рисунка его размер должен изменяться автоматически с изменением окна просмотра;
- количество одновременно открытых файлов не ограничено;
- если файл уже открыт и производится попытка его повторного открытия, то соответствующий файл вновь не открывается, а окно просто перемещается на передний план.
Рекомендации по написанию программы.
- Начальные действия.
Запустите Visual C++.
Menu -> File -> New -> Projects -> MFC AppWizard (exe)
Задайте Имя проекта и его расположение.
Выберите:
- Multiple Documents (step 1 of 6)
- Document/View architecture support (step 1 of 6)
- No database support (step 2 of 6)
- No compound document support (step 3 of 6)
- No Automation (step 3 of 6)
- Not an ActiveX control (step 3 of 6)
- Docking Toolbar (step 4 of 6)
- Initial Status bar (step 4 of 6)
- Printing and Print preview (step 4 of 6)
- NO Context-sensitive Help (step 4 of 6)
- 3D Controls (step 4 of 6)
- NO MAPI (step 4 of 6)
- NO Windows Socket (step 4 of 6)
- Toolbar looks Normal (step 4 of 6)
- MFC Standard project style (step 5 of 6)
- Do not change any file names (step 6 of 6)
В средах VS.NET, VS2005 последовательность действий практически идентична.
- Создание класса для поддержки BMP файлов.
Добавление чего-то, что вносит существенные изменения, требует создания дополнительного класса. В нашем случае это поддержка BMP файлов: загрузка и отображение.
Для добавления классов удобно использовать ClassWizard
- запускаем ClassWizard (Menu -> View -> ClassWizard)
- нам нужен новый класс, поэтому выбираем Add Class ->New…
- называем его MyBMP
- в качестве базового нам подходит самый простой класс CObject, однако такого в списке нет и приходится выбирать наипростейший, например, CStatic
В VS.NET и VS2005 ClassWizard отсутствует в явном виде, но функциональность вся тем не менее поддерживается. В частности добавить класс здесь можно в окне Solution Explorer в закладке Class View выбрать корневой элемент (название проекта) и в меню по правой кнопке мыши выбрать элемент “Add” -> “Add Class…”, далее в Categories выбрать MFC, в правой части окна MFC Class. Далее Open, задаем имя класса и в качестве базового выбираем CObject.
В результате в проекте появятся два файла MyBMP.cpp и MyBMP.h
Теперь «упростим» наш класс, т.е. уберем все лишнее.
- в качестве базового класса укажем CObject вместо CStatic
- удалим карту сообщений и оставим только Конструктор и деструктор
Теперь при любой попытке войти в ClassWizard, он будет ругаться, что не может найти информацию о данном классе и предложить найти ее вручную или исключить класс из списка классов, контролируемых Wizardом. Нам подходит последнее, т.к. все, что надо мы и вручную добавим.
Проблема не появилась бы, если использовать среду Visual C++ 7.0, т.к. там есть CObject класс в качестве непосредственного базового (так и есть).
Другим путем создания класса (впрочем с теми же трудностями) является использование закладки ClassView в проекте. Там можно также использовать контекстное меню Add Class.
Заметьте также, что добавление данного класса можно было осуществить и вручную, а не с использованием ClassWizard и результат был бы таким же.
- Наполнение MyBMP класса и первый тест
Первым делом убедимся в работоспособности нашего класса. Для этого добавим в него функцию прорисовки:
BOOL Draw(CDC *pDC,RECT *pRect);
Типы параметров должны быть вам знакомы. Первый – класс контекста дисплея и второй – прямоугольная область (где осуществлять прорисовку).
В качестве тела (в cpp файл) добавим прорисовку прямоугольника (пока):
pDC->FillSolidRect(0,0,100,100,0xFF0000); return TRUE;
Класс документа должен содержать структуру нашего объекта, коим является BMP рисунок. Так как поддержка BMP у нас возложена на MyBMP класс, то логично добавить в класс формата документа (…Doc) объект класса MyBMP:
MyBMP ozbmp; // в публичную секцию (не забудьте также про h файл)
Теперь у нас две задачи. В классе Doc осуществить загрузку информации, а в классе View отображать ее корректно. В этом основной принцип Documrnt/View модели. С загрузкой пока подождем, а прорисовку добавим. Нам необходим класс …View. Там есть метод OnDraw (вы можете найти его напрямую или через ClassWizard).
Там уже есть код получения объекта документа – это тот объект, который описан в Doc классе. Добавим прорисовку:
pDoc->ozbmp.Draw(pDC,NULL);
Проверьте, теперь в каждом окне при запуске программы будет прорисован квадрат синего цвета 100 на 100.
- Уточнение области прорисовки.
Теперь займемся определением области вывода. Проблема в том, что вывод может осуществляться как в окно, так и на внешнее растровое устройство, например принтер. Пути «прохождения» сообщений для прорисовки в этих случаях различны. Попробуем это решить.
Добавим в публичную секцию класса View прямоугольник:
RECT m_rectDraw;
В методе прорисовки класса View чуть поменяем код:
GetClientRect(&m_rectDraw);
pDoc->ozbmp.Draw(pDC, &m_rectDraw);
и, соответственно в методе прорисовки класса MyBMP:
pDC->FillSolidRect(pRect,0xFF0000);
Проверим. Теперь при смене габаритов окна прямоугольник всегда принимает нужный размер. Однако, если вы попытаетесь просмотреть как рисунок будет выглядеть на странице принтера (Menu->File->Print Preview), то увидите, что там рисуется лишь маленький прямоугольник и он не масштабируется на всю страницу. Займемся этим.
При печати на принтере (и при просмотре макета страницы) используется метод OnPrint, обрабатывающий соответствующее сообщение от Windows. У нас в классе View такого метода нет. Добавим его. Запустите ClassWizard, выбирите класс View, в списке Messages выберите OnPrint. Теперь нажмите кнопку AddFunction. Достаточно просто.
В VS.NET и VS2005 это следует делать так. Переходим в обзор Class View, выбираем наш класс, открываем его и там находим элемент Maps. Это карты класса MFC. Нам нужна карта сообщений MESSAGE (она там единственная). Выберите ее. Теперь вам надо перейти к реализации карты в исходном файле (участок кода в *.cpp файле-реализации класса между макросами BEGIN_MESSAGE_MAP и END_MESSAGE_MAP). Обычно это можно сделать двойным кликом на элементе MESSAGE. Ваша задача расположить курсор ввода внутри кода карты сообщений. Тогда в тулбаре окна Properties появится кнопка Messages. Нажмите на нее и выберите сообщение, обработчик которого надо добавить/изменить. Однако, если обработчик сообщения уже реализован в базовом классе, вам следует искать обработчик в списке методов базового класса, а не обработчиков сообщений. Для этого нажмите другую кнопку в тулбаре окна Properties. Она называется Overrides. В списке найдите нужный метод и в опциях выберите <Add> … В нашем случае найдите метод OnPrint и выберите опцию <Add> OnPrint. В ваш код будут добавлен соответствующий метод.
До добавления нами функции, тем не менее, какая-то обработка была – мы ведь видели, что прямоугольник рисуется. В данном случае мы подменили функцию базового класса нашей собственной и должны либо вызвать функцию базового класса, либо написать свою. Последнее нам подходит, т.к. тело функции достаточно просто. Установим нужный размер поля вывода и вызовем функцию прорисовки:
m_rectDraw = pInfo->m_rectDraw;
OnDraw(pDC);
Вызов же базовой функции закоментируем.
Однако, в этом случае мы получаем m_rectDraw здесь, а не в функции прорисовки, поэтому и там необходимо внести изменения:
if(pDC->IsPrinting()){ pDoc->ozbmp.Draw(pDC, &m_rectDraw); }
else{ GetClientRect(&m_rectDraw); pDoc->ozbmp.Draw(pDC, &m_rectDraw); }
- Загрузка и отображение BMP файлов.
Добавим функцию загрузки BMP файла в наш класс MyBMP:
BOOL ZBmp::Load(const char *szFileName);
параметр – имя файла для загрузки (с путем)
Для подгрузки и дальнейшего отображения BMP файла удобно поступать так:
- загрузить BMP файл с помощью LoadImage (вам уже знакомо)
- преобразовать хэндл в класс CBitmap с помощью метода CBitmap::FromHandle()
- запросить совместимый контекст дисплея CreateCompatibleDC()
- выбрать объект CBitmap в качестве активного SelectObject()
- использовать функцию StretchBlt для масштабирования CBitmap до нужных размеров вывода
Кроме метода Load вам понадобиться изменить и метод Draw класса MyBMP. Сделайте это самостоятельно. Добейтесь эффекта автоматического масштабирования картинки под окно вывода
Последнее, что необходимо – вызвать функцию подгрузки в нужном месте. Для этого существует метод Serialize. Нам нужна его часть, отвечающая за загрузку, а не запись. Добавим туда соответствующий код:
ozbmp.Load(ar.GetFile()->GetFilePath());
- Добавление второго типа окна просмотра.
Document/View модель позволяет добавлять любое количество просмотрщиков (View) к любому документу (Doc). Добавим дополнительный View класс для отображения технической информации о BMP файле (размер по X и по Y).
Для этого легче всего скопировать cpp и h файлы уже существующего класса View, добавить их в проект и изменить там везде имя класса на новый, скажем View2. Того же эффекта можно добиться путем добавления класса с помощью ClassWizard, однако это может потребовать больше усилий. Выберите любой вариант и добавьте класс самостоятельно. Добейтесь компиляции проекта без ошибок. Не забудьте, что, если вы копируете вручную, то вы должны заменять имя класса не только к коде C++, но и в спец Макро и спес Комментариях.
Теперь нам необходимо добавить еще одну константу в ресурсах. В строковых ресурсах скопируйте текущую строку с описание типа ресурса (IDR_…TYPE) под новым именем (IDR_…TYPE2). В тексте представлена спец. Последовательность, которая определяет тип документа, его расширение, его название и т.д. Подробную информацию о формате данной строки вы можете найти в Помощи по методу GetDocString класса CdocTemplate. Попробуйте варьировать значения строки, чтобы типы отображения были различны.
В основном файле проекта найдите место, где формируется шаблон данного Document/View взаимодействия и добавьте туда еще один шаблон для того же документа:
CMultiDocTemplate* pDocTemplate2;
pDocTemplate2 = new CMultiDocTemplate(
IDR_…TYPE2,
RUNTIME_CLASS(C…Doc),
RUNTIME_CLASS(CChildFrame), // custom MDI child frame
RUNTIME_CLASS(C…View2));
AddDocTemplate(pDocTemplate2);
здесь многоточие означает часть имени классов, зависящее от имени вашего проекта.
Теоретически теперь будут работать оба вьюера. Проверить это легко. При запуске программы она спросит у вас какой тип вьюера использовать для отображения файла.
В дальнейшем нам понадобится получать доступ к зарегистрированным шаблонам. Для этого следует шаблонные переменные сделать не локальными, а членами класса приложения. Перенесите их описание в класс C…App:
CMultiDocTemplate* pDocTemplate;
CMultiDocTemplate* pDocTemplate2;
Последний необязательный шаг – добавление иконки для нового вьюера. В ресурсах добавьте иконку с идентификатором, соответствующем нашему новому типу вьюера.
- Отображение текстовой информации.
В новом классе-вьюере достаточно лишь сменить вызов функции в функции перерисовки с Draw на DrawText. Все остальное может оставаться в нем так же как и в первом вьюере (не зря же мы его писали).
Понятно, что необходимо добавить эту функцию в MyBMP класс:
BOOL DrawText(CDC *pDC, RECT *pRect);
Здесь вам понадобиться вывести текст (форматированный). Для этого вам могут понадобиться:
- CDC::GetTextMetrics()
- wsprintf()
- CDC::DrawText()
- Последние шаги.
Запретим открытие пустого окна при старте и, напротив, будем открывать окно, если файл указан в командной строке. Найдите след. строку в главном файле проекта:
if (!ProcessShellCommand(cmdInfo)) return FALSE;
закомментируйте его и поместите туда другой код:
if (m_lpCmdLine[0]!= '\0'){
// open an existing document
OpenDocumentFile(m_lpCmdLine);
}
// Enable drag/drop open
m_pMainWnd->DragAcceptFiles();
Попутно мы подключили поддержку Drag&Drop. Просто, верно?
Добавим поддержку в меню переключения с одного вьюера на другой. В редакторе ресурсов скопируйте меню под именем IDR_…TYPE и установите имя нового меню в IDR_…TYPE2. Как видите константа меню соответствует типу вьюера (см. параграф 6).
Теперь в обоих этих меню добавьте по два элемента:
Menu->View->Show Picture с константой ID_VIEW_PIC и
Menu->View->Show Info с константой ID_VIEW_TEXT
Теперь надо добавить функции – реакции на новые элементы меню.
В ClassWizard выберите класс CMainFrame, в поле Object IDs найдите ID_VIEW_PIC, в поле Messages выберите COMMAND, нажмите Add Function и добавьте функцию OnViewPic. Аналогичные действия проведите для второго элемента и добавьте функцию OnViewText.
Теперь несколько «нечеткая» последовательность.
Заполните ваши обработчики новых команд меню так:
void CMainFrame::OnViewPic(){
CreateOrActivateFrame(theApp.pDocTemplate,RUNTIME_CLASS(CTest2View));
}
void CMainFrame::OnViewText(){
CreateOrActivateFrame(theApp.pDocTemplate2,RUNTIME_CLASS(CTextBView));
}
Как видите – они только вызывают некую функцию, которую также необходимо вручную добавить в класс CMainFrame:
void CMainFrame::CreateOrActivateFrame(CDocTemplate* pTemplate,CRuntimeClass* pViewClass)
{
CMDIChildWnd* pMDIActive = MDIGetActive(); ASSERT(pMDIActive!= NULL);
CDocument* pDoc = pMDIActive->GetActiveDocument(); ASSERT(pDoc!= NULL);
CView* pView;
POSITION pos = pDoc->GetFirstViewPosition();
while (pos){
pView = pDoc->GetNextView(pos);
if (pView->IsKindOf(pViewClass))
{
pView->GetParentFrame()->ActivateFrame();
return;
}
}
CMDIChildWnd* pNewFrame=(CMDIChildWnd*)(pTemplate->CreateNewFrame(pDoc, NULL));
if (pNewFrame == NULL) return; // not created
ASSERT(pNewFrame->IsKindOf(RUNTIME_CLASS(CMDIChildWnd)));
pTemplate->InitialUpdateFrame(pNewFrame, pDoc);
}
Если вы разберете текст функции, то увидите, что основное ее назначение – избежать повторного открытия окна для файла, который уже открыт в одном из окон. Разберитесь с этим самостоятельно.
И последний штрих. Как видите здесь используется переменная theApp, однако она не видна от сюда. Сделаем ее видимой – добавим ее описание (она уже создается в главном файле проекта) прямо за описанием класса – в h файл:
extern C…App theApp;