|
||||
|
Программирование на Visual C++Выпуск №48 от 1 июля 2001 г. Здравствуйте, уважаемые подписчики! Прежде всего, конечно, хочу попросить прощения на трехнедельный перерыв. Защита выпускной работы – дело серъезное, особенно если на выполнение этой работы осталась всего пара недель ;) Но я успешно защитился, с чем себя и поздравляю. А так же поздравляю вас, дорогие друзья, поскольку сей факт дал вам возможность вновь лицезреть свежие выпуски любимой рассылки на своих мониторах. Сегодня хочу предложить вашему вниманию одну забавную статью , которая, хотя и написана достаточно давно, во многом не потеряла своей актуальности. А живой язык автора делает чтение по-настоящему приятным. СТАТЬЯЯ никогда не буду использовать MFC Почему я? Программирование всегда доставляло мне удовольствие. То есть, программирование на C. Кто может сказать, что C не может дать человеку все, что нужно для выполнения работы? Мне казалось, что вся эта рекламная шумиха вокруг C++ и MFC – не более чем старание хитрецов из отдела по маркетингу оправдать свою зарплату. Ведь как бы то ни было, а я и во сне мог писать подпрограммы для Windows. Мне нравились изящные, плавные отступы массивных операторов switch, которые могли позаботиться обо всем, и даже больше, что могло прийти в голову любому Windows-приложению. Я был полон решимости не попасть в водоворот. Черная дыра абстракции меня не получит. Нет уж. Но я почувствовал, что я как будто был один. Создал в интернете группу для тех, кто интересуется C, – и был единственным, кто в нее записался! Все мои коллеги уже носили костюмчики C++ с завязками из MFC для работы. Когда я программировал, мне часто стали говорить: "В MFC это было бы гораздо проще". Но мне не нужно было "проще"! Мне был нужен полный контроль! А комбинация C++ и MFC уводила еще дальше от уже достаточно абстракного мира Windows на C. Меня стала мучить депрессия. Я начал задаваться вопросом о своей компетентности. (Не правда ли, это зашло достаточно далеко?) Как раз тогда вмешался мой друг Найджел. Он попросил меня написать программу на основе MFC, использующую архитектуру документ/представление. Очень нехотя, я дал ему положительный ответ. И тут же его уточнил, сказав: "И зачем только я хочу заниматься этим скучным делом?" Ниже следует отчет о процессе написания программы. А точнее, это обсуждение тех областей, где мне пришлось повозиться с некоторыми исследованиями, чтобы получить нужную для приложения функциональность. Можете считать это свидетельством в пользу способности MFC освободить программистов от той рутинной черновой работы, с которой нам приходилось иметь дело еще со времен Windows версии 1.0. Я немного запоздал? Возможно. Но я уверен, что многие из вас тоже задержались в пути. Постановка задачиКак я заметил выше, Найджел попросил меня написать программу, использующую архитектуру документ/представление в MFC. Конечной целью этого испытания было мое полное вовлечение в мир MFC. Как гласило техническое задание, это Win32 приложение должно было использовать расширенные метафайлы (enhanced metafiles) в качестве документа (более подробную иноформацию о расширенных метафайлах см. в статье "Enhanced Metafiles in Win32" в MSDN), и выводить документ на экран в различных представлениях: либо в виде изображения, либо в виде заголовка метафайла отображаемого как текст. Кроме того, программа должна была позаботиться о печати вместе с предварительным просмотром. Имея все это в виду, я приступил к работе. Как вы увидите, в процессе пришлось воспользоваться некоторыми искусственными приемами. Хотя они и не были неотъемлемой частью приложения, они помогли реализовать нужную функциональность. Можете считать их полезными советами. Создание проекта: программа для просмотра метафайловЗдесь нет ничего сверхсложного. Я просто воспользовался мастером AppWizard Microsoft Visual C++. Я выбрал многодокументный интерфейс (MDI), поставил галочку напротив опции панели инструментов (toolbar), строки состояния (status bar), и предварительного просмотра (print preview). В результирующем проекте были созданы класс документа и представления. Но погодите, я что, сказал "класс представления" вместо "классы представлений"? Почему AppWizard не создал второе представление? Если вы помните, программа должна осуществлять два представления одного документа. Но AppWizard создает только один класс представления. "Вот бездельник", подумал я. Очевидно, мне придется добавить второе представление самому. Ах, ну да, зато предварительный просмотр прилагается бесплатно. Но мы еще вернемся к этому второму представлению документа. Открытие документаХорошо, и где же в такой системе происходит открытие документа? Я неверно предположил, что это будет происходить в ответ на выбор пользователя команды Открыть из меню Файл. Ну и глупец же я. Это было бы слишком очевидно. В течение некоторого времени я шел этой дорогой, пока не понял, что фактически делаю всю работу сам. Я-то думал, что MFC будет мне помогать! "Неважно", подумал я, "просто спущусь в офис к Найджелу и спрошу его". Как оказалось, мне нужно было обрабатывать открытие файла в функции Serialize, принадлежащей классу документа. Следующий код иллюстрирует мою первую попытку сделать это. void CMetavw1Doc::Serialize(CArchive& ar) { if (ar.IsStoring()) {} else { UINT uiSig; ar.Read(&uiSize, sizeof(UINT)); if (uiSiz == EMR_HEADER) { m_hemf = GetEnhMetaFile(m_szPathName); } } } Интересующий нас код находится в блоке else. Этот код просто читает первые sizeof(UINT) байт из файла, чтобы убедиться что они являются сигнатурой расширенного метафайла. Если это так, данные загружаются с помощью GetEnhMetaFile. Так как я пока не сохраняю никаких документов, в ответ на IsStoring нет никакого кода. А не правда ли, было бы неплохо просто вызвать что-то вроде Load(m_szPathName) чтобы загрузить файл? Ага! Эта будет наш первый хитрый прием! Я решил написать класс, который будет иметь дело непосредственно с метафайлами, загружать их и воспроизводить (об этом подробнее см. дальше). Использование этого класса уменьшило необходимый код загрузки до следующего: void CMetavw1Doc::Serialize(CArchive& ar) { if (ar.IsStoring()) {} else { cemf.Load(m_szPathName); } } Заметьте, что в обоих фрагментах я использовал переменную m_szPathName как аргумент при вызове функций GetEnhMetaFile и Load. Поначалу я думал, что можно получить полное имя файла из параметра типа CArchive функции Serialize. Ведь CArchive содержит переменную-член m_pDocument, которая указывает на сериализуемый в данный момент объект типа CDocument. Отлично, у CDocument есть очень удобная переменная-член, которая выглядела как раз как то, что мне было нужно: m_strPathName. К сожалению, m_pDocument->strPathName инициализируется нулем при открытии файла. Так что я решил получить имя файла и путь к нему перекрыв фукцию OnOpenDocument. Путь напрямую передавался в OnOpenDocument, так что я просто сделал копию внутри класса CMetavw1Doc в той самой переменной, которая передавалась в качестве параметра функциям GetEnhMetaFile и Load. BOOL CMetavw1Doc::OnOpenDocument(LPCTSTR lpszPathName) { m_szPathName = lpszPathName; if (!CDocument::OnOpenDocument(lpszPathName)) return FALSE; return TRUE; } Итак, что я получил практически ничего не делая? На этот вопрос легко ответить. Все, что перечислено в следующем списке (плюс многое другое, что я еще просто не успел оценить, я уверен) было предоставлено MFC: • Стандартное диалоговое окно открытия файла • Список недавно открытых файлов в меню Файл • Возможность перетаскивать файлы из Проводника в мое приложение (они даже открываются!) • Невообразимое ощущение легкости. Прием №1: Класс расширенного метафайла: CEMFПосле заполнения моего класса представления (METAVVW.CPP) кодом, необходимым для успешной отрисовки расширенного метафайла, мне стало ясно что я возвращаюсь к своим старым привычкам неорганизованного кодирования на C. Так что я решил убрать весь этот код из класса представления и создать класс, который будет заниматься загрузкой и воспроизведением метафайлов. Для целей моего маленького приложения, Load и Draw были самыми важными функциями этого класса. Полностью в духе C++, я также написал несколько дополнительных функций для доступа к различным атрибутам метафайла, таким как дескриптор (handle), строка описания, и указатель на заголовок. Следующий код (взятый из CEMF.H) дает хорошее представление о том, что я сделал из этого класса. Заметьте, что я унаследовал класс от CObject, а не от CDC или CMetaFileDC. CDC включает в себя функции PlayMetaFile и AddMetaFileComment, и в ретроспективе, возможно, было бы более удобно унаследовать класс от CDC. Наследование от CMetaFileDC казалось неправильным, потому что я не создавал метафайлы, а просто просматривал уже существующие. Тем не менее, полностью функциональный класс метафайла мог бы быть унаследован и от CMetaFileDC. Да, есть много способов содрать с кота шкуру (прошу прошения у любителей кошек!) class CEMF : public CObject { // Операции public: CEMF(); ~CEMF(); BOOL Load(const char *szFileName); BOOL Draw(CDC* pDC, RECT* pRect); LPENHMETAHEADER GetEMFHeader() { return ((m_pEMFHdr) ? m_pEMFHdr : NULL); } LPTSTR GetEMFDescString() { return ((m_pDescStr) ? m_pDescStr : NULL); } HENHMETAFILE GetEMFHandle() { return ((m_hemf) ? m_hemf : NULL); } protected: BOOL GetEMFCoolStuff(); BOOL LoadPalette(); // Данные protected: CString m_szPathName; HENHMETAFILE m_hemf; LPENHMETAHEADER m_pEMFHdr; LPTSTR m_pDescStr; LPPALETTEENTRY m_pPal; UINT m_palNumEntries; LPLOGPALETTE m_pLogPal; LOGPALETTE m_LogPal; HPALETTE m_hPal; }; Функция Load подозрительно смотрит на начало файла, как и мой предыдущий код в функции Serialize. Но теперь нет объекта типа CArchive со всеми его преимуществами. Нет проблем. Использование объекта типа CFile позволяет прочитать сигнатуру. Функции GetEMFCoolStuff и LoadPalette взяты из моей программы-примера EMFDCODE в MSDN. Они получают копии заголовка метафайла, строки описания, и палитры внедренной в метафайл. Конечно, они теперь находятся в классе CEMF. BOOL CEMF::Load(const char *szFileName) { UINT uiSig; // Сохранить имя файла. m_szPathName = szFileName; // Проверить сигнатуру CFile cfEMF; cfEMF.Open(m_szPathName, CFile::modeRead | CFile::shareDenyWrite); cfEMF.Read(&uiSig, sizeof(UINT)); cfEMF.Close(); // Если это EMF, получить его дескриптор. if (uiSig == EMR_HEADER) { m_hemf = GetEnhMetaFile(m_szPathName); GetEMFCoolStuff(); LoadPalette(); } else m_hemf = NULL; // Возвращаем результат. return ((m_hemf) ? TRUE : FALSE); } Функция Draw вызывается из функции OnDraw класса представления. Особо интересного там ничего не происходит. Если есть палитра, на что указывает не равное NULL значение переменной-члена m_hPal, палитра выбирается в контекст устройства (DC). Я был сильно озадачен, когда узнал что CDC::SelectPalette требует указатель на объект типа CPalette. Но я был так же заинтригован, когда вдруг обнаружил функцию CPalette::FromHandle. Я мог легко преобразовать дескриптор палитры в объект типа CPalette. Далее это было уже просто делом воспроизведения метафайла с помощью CDC::PlayMetaFile. BOOL CEMF::Draw(CDC *pdc, RECT *pRect) { ASSERT(m_hemf); BOOL fRet = FALSE; CRect crect; CPalette cpalOld = NULL; if (m_hemf) { if (m_hPal) { CPalette cpal; if ((cpalOld = pdc->SelectPalette(cpal.FromHandle(m_hPal), FALSE))) pdc->RealizePalette(); } fRet = pdc->PlayMetaFile(m_hemf, pRect); if (cpalOld) pdc->SelectPalette(cpalOld, FALSE); } return (fRet); } Что же еще находится в классе CEMF? Как я упоминал выше, есть две закрытых (private) функции, которые управляются с палитрой и заголовком. Я их не буду обсуждать в этой статье, кому интересно см. статьи "Enhanced Metafiles in Win32" и "EMFDCODE.EXE: An Enhanced Metafile Decoding Utility", обе доступны в MSDN. Конечно, вы вероятно захотите также взглянуть на файлы CEMF.CPP и CEMF.H программы METAVIEW, приложенной к статье! Если вы захотите сделать из этого класса что-либо серьезное, предлагаю добавить следующую дополнительную функциональность: возможность делать нумерованые последовательности метафайлов и работать с метафайлами, содержащимися в буфере обмена. Опять же, эти темы описываются в вышеупомянутых статьях. Отображение документаЕсли я понял Найджела правильно, задача состояла в том, чтобы отображать документ тремя различными способами. Во-первых, как картинку в дочернем окне; потом, в виде текста описывающего заголовок метафайла (тоже в дочернем окне), и, наконец, как картинку в окне предварительного просмотра. К тому же, два представления в дочерних окнах нужно было реализовать через архитектуру многодокументного интерфейса (MDI) предоставленную MFC. Рассмотрим каждую из этих задач по отдельности. Вывод изображения в дочернем окнеЗдесь никаких проблем. Надо просто вызвать функцию-член Draw из класса CEMF. Посмотрите поближе на функцию OnDraw в файле METAVVW.CPP. void CMetavw1View::OnDraw(CDC* pDC) { CMetavw1Doc* pDoc = GetDocument(); // Флаг для предотвращения рисования во время // изменения размера окна, см. OnSize() и FullDragOn() в этом модуле. if (m_fDraw) { // Если мы печатаем или находимся в режиме предварительного // просмотра, рабочий прямоугольник узнается из CPrintInfo в OnPreparePrinting if (pDC->IsPrinting()) { pDoc->m_cemf.Draw(pDC, &m_rectDraw); } else { GetClientRect(&m_rectDraw); pDoc->m_cemf.Draw(pDC, &m_rectDraw); } } } Этот код организован вокруг двух условных операторов о которых стоит сказать отдельно. Первое условие является тестом типа "все-или-ничего". Если m_fDraw равно FALSE, не делается никакой попытки что-то нарисовать. Так что же означает m_fDraw? Ну, это мой хитрый прием №2, и я скоро к нему вернусь. Второе условие проверяет ведется ли отрисовка на принтер (или предварительный просмотр) или в дочернее окно. Член-функция IsPrinting класса CDC – это встроенная (inline) функция, которая возвращает public-переменную CDC::m_bPrinting. Раньше, прежде чем воспользоваться этой функцией, я проверял m_bPrinting напрямую. Когда я обнаружил функцию IsPrinting, это меня озадачило. Ведь эта функция просто возвращала значение m_bPrinting и все. Но похоже это больше в духе C++. Если название переменной m_bPrinting в будущем изменилось бы, мой код перестал бы работать. Но это все еще меня немного беспокоит. Как-никак, а я бывал достаточно сообразителен, чтобы при необходимости залезать в отладчик, прослеживать за несколькими переменными и затем придумывать способ получения желаемого результата. И это приводит меня к моей первой (и возможно последней) гипотезе: инкапсуляция и сокрытие данных могут мстить за чрезмерный энтузиазм. Ладно, хватит о гипотезах. Вернемся к программе. Мы обсуждаем отрисовку документа в дочернем окне. Клиентскую область окна мы получили с помощью GetClientRect и поместили в m_rectDraw. if (pDC->IsPrinting()) { pDoc->m_cemf.Draw(pDC, &m_rectDraw); } else { GetClientRect(&m_rectDraw); pDoc->m_cemf.Draw(pDC, &m_rectDraw); } Затем вызывается функция Draw и пуф! Появляется картинка. Вывод изображения в окно предварительного просмотра и на принтерДа, я схитрил. Я здесь сразу буду заниматься двумя представлениями, в окне предварительного просмотра и на принтере. Но я это делаю исключетельно потому, что для MFC между ними очень мало разницы. Если IsPrinting возвращает TRUE, вызывается функция Draw с тем, что поначалу кажется неинициализированным m_rectDraw. if (pDC->IsPrinting()) { pDoc->m_cemf.Draw(pDC, &m_rectDraw); } else { GetClientRect(&m_rectDraw); pDoc->m_cemf.Draw(pDC, &m_rectDraw); } К счастью, это все-таки не так. Библиотека опять приходит на помощь. Когда мы печатаем или находимся в режиме предварительного просмотра, несколько функций вызываются до OnDraw. Эти функции можно перекрыть своими. В данном случае я перекрыл функцию OnPrint (которая в конце концов вызывает OnDraw). В эту функцию передается указатель на объект типа CPrintInfo. Один из переменных-членов этого класса – объект типа CRect, определяющий доступную для печати область. Этот прямоугольник просто копируется в m_rectDraw до вызова OnDraw. void CMetavw1View::OnPrint(CDC* pDC, CPrintInfo* pInfo) { m_rectDraw = pInfo->m_rectDraw; OnDraw(pDC); } Остальное все уже достояние истории. Вызывайте Draw и дело сделано! Одно замечание касательно предварительного просмотра. Я потратил немного времени, пытаясь выянить размеры "страницы" предварительного просмотра. Почесывал задумчиво голову, пытаясь понять, как мне получить координаты точки отсчета и размеры центрированной в окне "страницы". Я даже дошел до того, что сделал копию объекта CPreviewDC (закрытого объекта AFX) просто чтобы узнать координаты точки отсчета. Но мне все равно никак не удавалось получить размеры. Слава богу, я вспомнил свою гипотезу-теперь-ставшую-аксиомой: инкапсуляция и сокрытие данных могут мстить за чрезмерный энтузиазм. Так что немного поворчав по поводу MFC, я наконец понял, что все масштабирование в окне предварительного просмотра будет осуществляться автоматически. А наблюдая при изменении размера за CPrintInfo::m_rectDraw, я заметил что он все время остается одинаковым. Точно как я хотел! Еще одно очко в пользу MFC. Что может быть легче?Я не знаю насчет вас, но мой код, отвечающий за рисование, обычно достаточно объемен. И я действительно впечатлен предварительным просмотром, обычным рисованием и печатью, умещенными в 19 строк кода (включая комментарии). Правда, мне пришлось написать класс CEMF. Но послушайте, я ведь могу теперь использовать его в своих будущих программах! И да, библиотека MFC взяла на себя предварительный просмотр и печать. Но знаете что? Пусть Microsoft занимается сопровождением этого кода вместо меня! ПРИЕМ №2: РАЗБИРАЕМСЯ С ПОКАЗОМ СОДЕРЖИМОГО ОКНА ПРИ ПЕРЕТАСКИВАНИИТак что это был за флаг m_fDraw в функции OnDraw? Вспомните, что он связан в тестом типа "все-или-ничего". Если m_fDraw равняется FALSE, не делается никакой попытки что-либо нарисовать. Я придумал этот прием когда начал отображать большие метафайлы в клиентской области в ответ на изменение размера окна. В системе есть опция (которая устанавлявается в окне Экран (Desktop) панели управления), которая разрешает показывать содержимое окна при его перетаскивании и изменении размера. И я хочу вам сказать, что если вы воспроизводите огромный метафайл, который делает много сложных вещей, вы эту опцию просто возненавидите. Так как ее отключить? Нет, этого делать нельзя! Помните, эту опцию включил пользователь. Вам не стоит самостоятельно отключать ее. Вы можете сказать, "раньше это меня никогда не останавливало!". Ну, все равно удобного способа отключить ее нет. Чтобы справиться с ней, я решил воспользоваться "одноразовым" таймером. Что такое одноразовый таймер? Просто говоря, это таймер который используется один раз, а потом уничтожается. Основная реализация, в случае изменения размера окна, – запустить таймер при получении сообщения WM_SIZE. Если таймер уже существует (в случае последовательных сообщений WM_SIZE), уничтожить его и запустить еще один. Когда наконец сообщение WM_TIMER пробивается скозь очередь других сообщений, уничтожаем таймер. Помните, что вы не получите сообщения WM_TIMER, пока не прекратится изменение окна приложения. У сообщений WM_TIMER очень низкий приоритет. Следующие две функции, OnSize и OnTimer, иллюстрируют как я приспособил таймер для этого приложения. В дополнение к созданию и уничтожению таймеров, эти функции устанавливают значение m_fDraw. void CMetavw1View::OnSize(UINT nType, int cx, int cy) { CView::OnSize(nType, cx, cy); // Это нужно делать только если опция отображения содержимого // окна при перемещении и изменении размера включена if (m_fFullDragOn) { if (!m_uiTimer) KillTimer(1); m_uiTimer = SetTimer(1, 100, NULL); m_fDraw = FALSE; } } Когда мы наконец получаем сообщение WM_TIMER, функция OnTimer устанавливает значение m_fDraw в TRUE, уничтожает таймер и перерисовывает клиентскую область окна представления. void CMetavw1View::OnTimer(UINT nIDEvent) { m_fDraw = TRUE; m_uiTimer = 0; KillTimer(1); InvalidateRect(NULL); } Переменная m_fFullDragOn, встречавшаяся в OnSize, устанавливается вызовом функции FullDragOn в конструкторе класса представления. Эта функция смотрит в ключе HKEY_CURRENT_USER\Control Panel\Desktop реестра, включена ли опция показа содержимого окна. Если значение подключа DragFullWindows равно 1, функция возвращает TRUE, иначе она возвращает FALSE. [Расположение ключей в реестре сильно зависит от типа и версии системы. Используйте эту возможность с осторожностью. – прим. перев.] BOOL CMetavw1View::FullDragOn() { HKEY hkey = NULL; DWORD dwType; long lResult; LPSTR lpszDataBuf; DWORD cbData = 0; lResult = RegOpenKeyEx(HKEY_CURRENT_USER, "Control Panel\\Desktop", 0, KEY_READ, &hkey); if (hkey) { // Получить размер ключа. lResult = RegQueryValueEx(hkey, "DragFullWindows", NULL, &dwType, NULL, &cbData); // Зарезервировать память под значение ключа. lpszDataBuf = (LPSTR)malloc(cbData * sizeof(char)); // Получить значение ключа. lResult = RegQueryValueEx(hkey, "DragFullWindows", NULL, &dwType, (LPBYTE)lpszDataBuf, &cbData); return (*lpszDataBuf == '1'); } return FALSE; } Общий результат таков: когда окно изменяет размер, ничего не перерисовывается до тех пор, пока изменение размера не прекратится. Добавление второго представленияВы наверное помните, что в задаче Найджела было требование, что программа должна выводить документ на экран в различных представлениях: либо в виде изображения, либо в виде заголовка метафайла отображаемого как текст. Никаких проблем; наверняка для этого есть свой мастер наподобие AppWizard. К моему сильнейшему изумлению, никакого мастера не было! Так что я посмотрел статью Дейла Роджерсона (Dale Rogerson) "Multiple Views for a Single Document" ("Несколько представлений для одного документа") в MSDN. Она была очень полезной. Тем не менее, пока вы не проделаете это три или четыре раза, вы очень просто можете запутаться! Поверьте мне, я потерял несколько часов, когда брался то за добавление второго представления, то за написание класса CEMF. Я советую полностью сфокусироваться на втором представлении, пока оно не заработает как надо. Найджел добавил второе представление в одну из своих программ-примеров ("VIEWDIB: Views Multiple DIBs Simultaneously" Прим. редактора: к сожалению, это приложение больше не входит в состав библиотеки MSDN), основанную на статье Дейла. Он вывел следующий список, основанный на своем опыте. Имея статью Дейла и список Найджела, я смог добавить второе представление без особых хлопот. А если я могу это сделать, то вы тоже можете! 1. Воспользуйтесь ClassWizard чтобы создать новый класс представления; например, CAppSecondView. 2. Добавьте включение заголовочного класса нового представления в нужный cpp-файл (см. пункт 12). 3. Добавьте функцию GetDocument к коду класса представления и заголовку. (Скопируйте ее из другого класса представления.) 4. Напишите код функции OnDraw для нового представления, или по крайней мере поставьте простую заглушку, которая вам позволит его протестировать. Убедитесь, что код класса нового представления включен в проект, и откомпилируйте новый модуль. 5. Создайте новый ресурсный идентификатор, типа IDR_VIEW2TYPE. Этот идентификатор будет использоваться для всех ресурсов второго представления. 6. Создайте пиктограмму для нового представления, используйте ресурсный идентификатор из пункта 5. 7. Создайте меню для второго представления (можете скопировать старое), опять используйте ресурсный идентификатор из пункта 5. 8. Добавьте в каждое меню пункты для смены текущего представления. 9. В строковую таблицу добавьте новую строку шаблона для нового ресурсного идентификатора в виде: \nТип\n\n\n\nТип файла\nТип файла 10. В класс приложения добавьте public-переменную типа CMultiDocTemplate* для каждого представления, например m_pBasicViewTemplate и m_pNewViewTemplate. 11. В файл заголовка класса приложения включите следующую строку после объявления класса: extern C???App NEAR theApp; (замените ??? на название вашего приложения.) 12. Добавьте код создания каждого шаблона документа в реализацию класса приложения, напр.: m_pBasicViewTemplate = new CMultiDocTemplate(...); AddDocTemplate(m_pBasicViewTemplate); 13. С помощью ClassWizard'a добавьте в класс главного окна обработчики команд меню нового представления. Все обработчики в виде: CreateOrActivateFrame(theApp.m_p????ViewTemplate, RUNTIME_CLASS(C???View)); Также не забудьте включить заголочный файл нового представления в MAINFRM.CPP 14. Добавьте функцию CreateOrActivateFrame в MAINFRM.CPP и MAINFRM.H. ЗаключениеО'кей, это еще одна статья, прославляющая MFC. Что я могу сказать. Я действительно был скептиком до того, как приступил к работе. Если вы еще не решились, я советую вам попробовать. Я помню одного знакомого, который говорил: "Я никогда не прикоснусь к продукту Microsoft, так как они представляют собой все зло индустрии програмного обеспечения." Теперь он работает в Microsoft! Так что никогда не говорите никогда. Это тот подход, который я выбрал в отношении C++ и MFC. И кстати, вы слышали что новая версия Visual C++ будет называться Visual Cobol++? Вот тогда мне наверное придется уйти в отставку! Это все на сегодня. Пока! (Алекс Jenter jenter@rsdn.ru) (Красноярск, 2001. Рассылка является частью проекта RSDN.) |
|
||
Главная | В избранное | Наш E-MAIL | Прислать материал | Нашёл ошибку | Наверх |
||||
|