Настройка
стартового кода
Просмотрите
плоды работы мастера в окне Class View. С помощью контекстного меню задайте
в этом окне режим просмотра Sort By Type, так как он компактнее, а классов у
нас будет достаточно много. Приятным моментом является то, что класс CRightView
теперь действительно потомок CScrollView, как мы это определили в окне мастера.
В сходной ситуации Visual Studio 6 отказывалась менять родителя, и это приходилось
делать вручную. Отметьте также, что во всех отношениях стартовые заготовки Studio.Net
7.0 более компактны, чем их прототипы Visual Studio 6. Тем не менее в них есть
лишние детали, которые я с неизменным упорством убираю. Так, каждое из двух
представлений имеет по две версии метода GetDocument. Один работает в отладочной
(debug) версии проекта, а другой — в окончательной (release). Класс CLef tview,
который будет демонстрировать файловое дерево, не нуждается в поддержке вывода
на принтер, как и представление CRightView, которое предполагается использовать
для предварительного просмотра содержимого файлов документов. Виртуальную функцию
preCreateWindow мы также не будем использовать в некоторых классах. То же следует
сказать о наследии класса CObject: функциях Assertvalid и Dump. Об особой культуре
их использования я говорил в предыдущей книге (Visual C++6 и MFC, «Питер», 2000),
а здесь просто рекомендую молча убрать их из всех классов. Если возникнет необходимость
вывести в окно Debug отладочную информацию, то можно обойтись без этих функций
и в любом методе класса с успехом пользоваться глобально определенным объектом
afxDump.
Обычно, перед
тем как приступить к разработке приложения, я провожу генеральную чистку стартовой
заготовки. При выбрасывании лишнего кода, как и при прополке, важно не забывать
о корнях. Удалять функцию следует как в срр-файле (реализации класса), так и
в h-файле (интерфейса класса). При этом удобной оказывается команда, а точнее
ее аналог в виде кнопки на инструментальной панели Edit > Find and
Replace > Find in Files. Попробуйте использовать ее для того, что
бы найти и удалить с корнем все версии функции GetDocument. Убирайте объявления
и тела этой функции, но не ее вызовы. Затем в h-файлы классов CLef tview и CRightview
и только в них вставьте такую достаточно надежную версию этой функции:
CTreeDoc*
GetDocument()
{
return
dynamic_cast<CTreeDoc*>(m_pDocument);
}
Замены такого
рода, когда в h-файл вставляется код, а не только декларации, сопряжены с некоторыми
неожиданными сообщениями со стороны компилятора. Здесь важно проявить терпение
и не опускать руки раньше времени. Если вы правильно сделали замены, то после
компиляции проекта получите предупреждение и сообщение об ошибке. С предупреждением
справиться просто, если посмотреть справку по его коду (С4541). Выяснится, что
для использования информации о типе указателей на этапе выполнения (run-time
type information, которой пользуется выражение dynamic_cast<type-id>(expression)),
необходимо предварительно сделать установку специального режима компиляции.
В Studio.Net это делается так:
- Поставьте фокус в узел
Tree окна Class View или окна Solution Explorer и дайте команду View >
Property Pages (Alt+Enter).
- В появившемся диалоге
Property Pages раскройте узел дерева C/C++ и выберите элемент Language.
- В таблице окна справа
найдите свойство Enable Runtime Type Info и задайте для него значение Yes
(/GR).
Аббревиатура
/GR соответствует опции, задаваемой в командной строке компилятора. После повторной
компиляции предупреждения исчезнут, однако ошибка останется. В такие моменты
важно обратить внимание на имя файла, при компиляции которого была обнаружена
ошибка. В нашем случае — это TreeFrm.cpp. Раскройте этот файл и просмотрите
его начало, где стоят директивы #include. Сбой произошел в месте включения файла
#include "Lef tview.h". Именно в него мы вставили новое тело функции GetDocument.
Компилятор сообщает, что при анализе строки
return
dynamic_cast<CTreeDoc*
>(m_pDocument);
он обнаружил
неверный тип для преобразования (invalid target type for dynamic_ cast). Но
тип CTreeDoc* (указатель на класс документа) задан верно. Проблема всего лишь
в том, что компилятор пока не знает о том, что CTreeDoc происходит от
известного ему класса CDocument. Решение этой проблемы — вставить директиву
#include "TreeDoc.h" перед директивой #include "Lef tview.h". В сложных
проектах, состоящих из множества файлов, неверная последовательность включения
файлов заголовков может привести к дополнительной головной боли. Для выявления
причины отказа в таких случаях нужен серьезный анализ этой последовательности.Теперь,
запустив приложение, вы должны увидеть заготовку приложения, которое соответствует
выбору (флажку) Windows Explorer, сделанному нами в окне мастера AppWizard.
Мы имеем два окна, разделенных перегородкой (split bar). Левое окно (рапе) предстоит
наполнить ветвями файлового дерева, а в правом — показывать в виде «картинок»
файлы документов приложения, обнаруженные в текущей папке — той папке, которая
выбрана в левом окне, — дереве файлов. Возвращаясь к сокращениям кода стартовой
заготовки, отметим, что многие файлы, будучи уменьшенными в объеме, значительно
выигрывают в читабельности и выглядят не так страшно для новичков. В качестве
примера приведем текст файла TreeFrm.h после указанной операции
1:
class
CTreeFrame :
public
CMDIChildWnd
{
DECLARE_DYNCREATE
(CTreeFrame)
public:
CTreeFrame();
virtual
~CTreeFrame();
//======
Создание панелей расщепленного (split) окна
virtual
BOOL OnCreateClient(LPCREATESTRUCT Ipcs,
CCreateContext*
pContext);
virtual
BOOL PreCreateWindow(CREATESTRUCT& cs) ;
protected:
//======
Объект для управления расщепленным окном
CSplitterWnd
m_wndSplitter;
DECLARE_MESSAGE_MAP()
};
Кроме методов,
рассмотренных выше, мы убрали за ненадобностью метод GetRightPane, который добывает
адрес представления, расположенного в правой части (рапе) расщепленного окна.
Аналогичной редакции (редукции) подвергся и файл Lef tview.h, который, тем не
менее, справляется с начальной задачей — показ пустого окна, и в редуцированном
виде. Однако этот класс необходимо начать развивать уже сейчас, придавая ему
способность управлять деревом файлов. Введите в него объявления новых данных
и методов так, чтобы файл LeftView.h приобрел вид:
#pragma
once
class
CTreeDoc; // Упреждающее объявление
class
CLeftView :
public CTreeView
{
protected:
//======
Ссылка на объект элемета управления деревом
CTreeCtrlS
m_Tree;
//======
Список значков узлов дерева
CImageList
*m_pImgList;
CLeftView()
;
virtual
void OnlnitialUpdate();
DECLARE_DYNCREATE(CLeftView)
public:
virtual
~CLeftView(); CTreeDoc* GetDocument()
{
return
dynamic_cast<CTreeDoc*>(m_pDocument);
}
//======
Выбор системных значков
void
GetSysImgList ();
//======
Вставка нового узла (ветви)
void
AddltemfHTREEITEM h, LPCTSTR s) ;
//======
Поиск своих документов
void
SearchForDocs(CString s) ;
//======
Проверка отсутствия файлов
bool
NotEmpty(CString s);
//======
Вычисляет полный путь текущего узла дерева
CString
GetPath (HTREEITEM hCur);
DECLARE_MESSAGE_MAP()
};
Мы не собираемся
поддерживать вывод на принтер, поэтому в файле реализации класса CLef tview
(LeftView.cpp) уберите из карты сообщений класса все макросы, связанные с печатью.
Удалите также заготовки тех функций, прототипы которых удалили в файле интерфейса
класса (LeftView.h). Это функции PreCreateWindow, OnPreparePrinting, OnBeginPrinting,
OnEndPrinting. AssertValid, Dump, GetDocument. Кроме директив препроцессора
в файле должен остаться такой код:
IMPLEMENT_DYNCREATE(CLeftView,
CTreeView) ,
BEGIN_MESSAGE_MAP(CLeftView,
CTreeView) END_MESSAGE_MAP()
CLeftView::CLeftView(){}
CLeftView::~CLeftView(){}
void
CLeftView: : OnlnitialUpdate {}
{
CTreeView::OnInitialUpdate();
}
Аналогичные
упрощения рекомендуем проделать и в классе CRightView. Теперь приступим к анализу
и развитию кода класса CLeftView. Внутри каждого объекта класса, производного
от CTreeView, содержится объект класса CTreeCtrl, ссылку на который мы объявили
в классе CLef tview. Как вы знаете (из курса ООП), единственным способом инициализировать
ссылку на объект вложенного класса является ее явная инициализация в заголовке
конструктора объемлющего класса. Поэтому измените тело конструктора (в файле
LeftView.cpp) так, чтобы он был:
CLeftView::CLeftView()
{
:
m Tree(GetTreeCtrl())
//
Пустое тело конструктора
}
Метод GetTreeCtrl
класса cireeView позволяет добыть нужную ссылку, а вызов конструктора mjrree
(GetTreeCtrl ()) инициализирует ее. Теперь мы будем управлять деревом на экране
с помощью ссылки m_Tree. Начальные установки для дерева производятся в уже существующей
версии виртуальной функции OnlnitialUpdate:
::SetWindowLongPtr
(m_Tree.m_hWnd, GWL_STYLE,
::GetWindowLong(m_Tree.m_hWnd,
GWL_STYLE)
|
TVS_HASBUTTONS | TVS_HASLINES
|
TVS_L1NESATROOT | TVS_SHOWSELALWAYS);
Вставьте эту
строку в тело OnlnitialUpdate после строки с вызовом родительской версии. Функция
SetWindowLongPtr имеет универсальное употребление. Она позволяет внести существенные
изменения в поведение приложения, например, с ее помощью можно изменить адрес
оконной процедуры или стиль окна. Второй параметр определяет одну из 9 категорий
изменений. Задание индекса GWL_STYLE указывает системе на желание изменить стиль
окна. Симметричная функция GetWindowLong позволяет добыть переменную, биты которой
определяют набор действующих стилей. С помощью побитовой операции ИЛИ мы добавляем
стили, специфичные для окна типа Tree view. Префикс TVS означает Tree view styles,
а префикс GWL — GetWindowLong. Смысл используемых констант очевиден. Если нет,
то он легко выясняется с помощью эксперимента. Вы можете вставить, вслед за
обсуждаемой строкой кода, такую:
m_Tree.Insertltem("Item",
0, 0);
и запустить
приложение. Несмотря на отсутствие тел новых методов, объявленных в интерфейсе
класса, вы увидите одну ветвь дерева с именем «Item».
Примечание
C
помощью функций SetWindowLong и SetWindowLongPtr можно перемещать окна вверх
или вниз внутри иерархии окон, определяемой отношением, которое называется
Z-order. Дело в том, что окна на экране упорядочены в соответствии с Z-order.
Считается, что ось Z направлена на нас. Z-order служит механизмом, определяющим,
видимо ли окно в данный момент или скрыто другими окнами, которые располагаются
выше в иерархии Z-order. Вы можете программно изменять этот порядок.