Реакция на уведомляющие сообщения CTreeCtrl

Когда пользователь раскрывает узел дерева, то встроенный в класс CTreeView объект класса CTreeCtrl посылает родительскому окну (нашему представлению CLef tview) уведомляющее сообщение. Оно работает по схеме WM_NOTIFY, которую мы уже рассматривали. Наш класс CLef tview должен реагировать на это сообщение, сканировать раскрываемую папку или логический диск и изменять дерево, вставляя в раскрываемую ветвь новые объекты файловой системы, которые обнаружены внутри папки или диска. Для того чтобы ввести в класс способность реагировать на рассматриваемое сообщение, вы должны:

  1. Открыть окно Class View, установить фокус на имя класса CLeftView и перейти в окно Properties.
  2. В окне Properties нажать кнопку с подсказкой Messages, а затем кнопку Categorized.
  3. Нажать маркер (-) Common в верхнем левом углу прокручиваемого списка сообщений так, чтобы он изменился на (+). Тем самым вы скрываете часть списка с перечнем всех обычных сообщений Windows.
  4. В оставшейся части списка найти сообщение =TVN_ITEMEXPANDING и в выпадающем списке справа выбрать действие <Add>.
  5. Повторить действия пункта 4 для сообщений =TVN_ITEMEXPANDED и =TVN_ SELCHANGED.

Буква N в имени сообщения говорит о том, что сообщение является уведомляющим, а знак равенства перед ним означает, что оно принадлежит к особой группе отражаемых (reflected) сообщений. В версиях MFC (до 4.0), не было обработчиков отражаемых сообщений. Теперь к старому механизму обработки уведомляющих сообщений от дочерних {child) элементов добавился новый, который позволяет произвести обработку уведомляющего сообщения в классе самого элемента. Уведомляющее сообщение как бы отражается {reflects) назад в класс дочернего окна (элемента управления CTreeCtrl). Этот сценарий мог бы быть реализован и в классе, производном от CTreeCtrl, но нам нет смысла создавать такой класс, так как возможности класса CLef tview вполне достаточны для обработки обоих сообщений. Здесь важен лишь тот факт, что можно перехватить управление в те моменты, когда пользователь манипулирует с деревом.

Первое сообщение (=TVN_ITEMEXPANDING) поступает в момент нажатия маркера (+). Дерево в этот момент еще не раскрылось. Здесь мы должны притормозить процесс перерисовки дерева до того момента, пока не получена вся информация о содержимом раскрываемого узла.

Саму информацию мы будем добывать в теле функции, обрабатывающей второе сообщение (=TVN_ITEMEXPANDED). Оно приходит после того, как узел дерева раскрылся (но не обязательно перерисовался). Здесь мы должны реализовать два варианта развития событий: узел открывается впервые и узел открывается повторно.

Третье сообщение (=TVN_SELCHANGED) приходит в момент, когда пользователь нажал кнопку в пределах самого узла, то есть он выбрал (select) узел. Начнем с обработки первого сообщения. Измените тело функции Onltemexpanding так, чтобы оно имело вид:

void CLeftView::0nltemexpanding (NMHDR* pNMHDR, LRESULT* pResult)

{

//====== Преобразование типа указателя

NM_TREEVIEW* p = (NM_TREEVIEW*)pNMHDR;

//====== Если узел не раскрыт

if ( !(p->itemNew.state & TVIS_EXPANDED))

//====== тормозим перерисовку

SetRedraw(FALSE); *pResult = 0;

}

Бит состояния TVIS_EXPANDED не равен нулю, когда узел уже раскрыт. Мы хотим выделить обратный случай, поэтому пользуемся операцией логического отрицания. Метод cwnd:: SetRedraw позволяет установить флаг перерисовки. Если он снят, то система не будет перерисовывать содержимое окна. Вставьте изменения В тело функции обработки Onltemexpanded:

void CLeftView::OnItemexpanded (NMHDR* pNMHDR, LRESULT* pResult) {

NMJTREEVIEW* p = (NMJTREEVIEW*)pNMHDR;

//====== Создаем курсор ожидания

CWaitCursor wait;

//====== Признак раскрытия узла (а не его закрытия)

if (p->itemNew.state & TVIS_EXPANDED)

{

// Описатели раскрываемого и 1-го вложенного узла

HTREEITEM hCur = p->itemNew.hltem,

h = m_Tree.GetChildItem(hCur);

//====== Если имя вложенного узла пусто,

//====== то ветвь еще не раскрывалась

if (m_Tree.GetItemText(h) == "")

{

//====== Удаляем муляж

m_Tree.DeleteItem(h);

//====== Вычисляем полный путь

CString s = GetPath(hCur) + "*.*";

//====== Наполнение раскрытой ветви

CFileFind cff; BOOL bFound = cff.FindFile(s);

while (bFound) {

bFound = cff.FindNextFile();

if (cff.IsDirectory() && !cff.IsDots())

AddItem(hCur, cff.GetFilePath ()); } }

// ====== Разрешаем перерисовку

SetRedraw(TRUE); }

*pResult = 0;

}

Здесь реализованы два варианта развития событий: узел открывается впервые и узел отрывается повторно. Признаком первого варианта является наличие пустого элемента с нулевым индексом изображения и пустой строкой текста внутри раскрываемой ветви. Мы удаляем такой элемент, определяем полный файловый путь раскрываемого узла (папки или диска), сканируем файловый адрес и наполняем дерево новыми элементами. Алгоритм заполнения содержимого папки сходен с алгоритмом заполнения логического диска. Также воспользуемся пустым узлом для пометки папок, которые имеет смысл раскрывать, так как в них есть вложенные папки или файлы. Функция GetPath должна пройти вверх по иерархической структуре дерева и вычислить полный файловый путь узла, заданного параметром. Введите коды этой функции в файл LeftView.cpp:

CString CLeftView::GetPath (HTREEITEM hCur)

{

//====== Вычисляет полный файловый путь узла hCur

CString s = "";

for (HTREEITEM h=hCur; h; h=m_Tree.GetParentItem(h))

s = m_Tree.GetItemText(h) + '\\' + s;

return s ; }

Размеры левого окна были заданы в момент создания стартовой заготовки и они, пожалуй, маловаты. Исправьте начальные размеры окна, которые задаются при вызове CreateView внутри функции CTreeFrame::OnCreateClient. Посмотрите справку по этой функции и задайте горизонтальный размер окна равным 200.

Запустите приложение и протестируйте работу дерева. Теперь его поведение должно соответствовать тем требованиям, которые были сформулированы в начале разработки проекта. В такие моменты полезно провести эксперименты, чтобы лучше уяснить смысл некоторых действий. Например, временно уберите битовый флаг SHGFI_SMALLICON при вызове SHGetFileinf о и посмотрите, как изменится вид узлов дерева. Затем временно исключите вызов функции SetRedraw в обработчике Onitemexpanding и пронаблюдайте поведение дерева при раскрытии папки, содержащей большое количество вложенных объектов, например winNT.